├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── genReleaseNote.sh ├── release.sh ├── release │ ├── .fpm_systemd │ ├── config.yaml │ ├── mihomo.service │ └── mihomo@.service ├── rename-cgo.sh ├── rename-go120.sh ├── test.yml └── workflows │ └── build.yml ├── .gitignore ├── .golangci.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── Meta.png ├── README.md ├── adapter ├── adapter.go ├── inbound │ ├── addition.go │ ├── auth.go │ ├── http.go │ ├── https.go │ ├── ipfilter.go │ ├── listen.go │ ├── listen_notwindows.go │ ├── listen_windows.go │ ├── mptcp_go120.go │ ├── mptcp_go121.go │ ├── packet.go │ ├── socket.go │ └── util.go ├── outbound │ ├── anytls.go │ ├── base.go │ ├── direct.go │ ├── dns.go │ ├── http.go │ ├── hysteria.go │ ├── hysteria2.go │ ├── mieru.go │ ├── mieru_test.go │ ├── reality.go │ ├── reject.go │ ├── shadowsocks.go │ ├── shadowsocksr.go │ ├── singmux.go │ ├── snell.go │ ├── socks5.go │ ├── ssh.go │ ├── trojan.go │ ├── tuic.go │ ├── util.go │ ├── vless.go │ ├── vmess.go │ └── wireguard.go ├── outboundgroup │ ├── fallback.go │ ├── groupbase.go │ ├── loadbalance.go │ ├── parser.go │ ├── patch_android.go │ ├── relay.go │ ├── selector.go │ ├── urltest.go │ └── util.go ├── parser.go └── provider │ ├── healthcheck.go │ ├── parser.go │ ├── provider.go │ └── subscription_info.go ├── android_tz.go ├── check_amd64.sh ├── common ├── arc │ ├── arc.go │ ├── arc_test.go │ └── entry.go ├── atomic │ ├── type.go │ └── value.go ├── batch │ ├── batch.go │ └── batch_test.go ├── buf │ └── sing.go ├── callback │ ├── callback.go │ └── close_callback.go ├── cmd │ ├── cmd.go │ ├── cmd_other.go │ ├── cmd_test.go │ └── cmd_windows.go ├── contextutils │ ├── afterfunc_compact.go │ ├── afterfunc_go120.go │ ├── afterfunc_go121.go │ └── afterfunc_test.go ├── convert │ ├── base64.go │ ├── converter.go │ ├── converter_test.go │ ├── util.go │ └── v.go ├── lru │ ├── lrucache.go │ └── lrucache_test.go ├── murmur3 │ ├── murmur.go │ └── murmur32.go ├── net │ ├── addr.go │ ├── bind.go │ ├── bufconn.go │ ├── bufconn_unsafe.go │ ├── cached.go │ ├── context.go │ ├── context_test.go │ ├── deadline │ │ ├── conn.go │ │ ├── packet.go │ │ ├── packet_enhance.go │ │ ├── packet_sing.go │ │ ├── pipe.go │ │ └── pipe_sing.go │ ├── earlyconn.go │ ├── io.go │ ├── listener.go │ ├── packet.go │ ├── packet │ │ ├── packet.go │ │ ├── packet_posix.go │ │ ├── packet_sing.go │ │ ├── packet_windows.go │ │ ├── ref.go │ │ ├── ref_sing.go │ │ ├── thread.go │ │ └── thread_sing.go │ ├── refconn.go │ ├── relay.go │ ├── sing.go │ ├── tcpip.go │ ├── tls.go │ └── websocket.go ├── observable │ ├── iterable.go │ ├── observable.go │ ├── observable_test.go │ └── subscriber.go ├── once │ ├── once_go120.go │ └── once_go122.go ├── picker │ ├── picker.go │ └── picker_test.go ├── pool │ ├── alloc.go │ ├── alloc_test.go │ ├── buffer.go │ ├── buffer_low_memory.go │ ├── buffer_standard.go │ ├── pool.go │ └── sing.go ├── queue │ └── queue.go ├── singledo │ ├── singledo.go │ └── singledo_test.go ├── singleflight │ └── singleflight.go ├── sockopt │ ├── reuse_common.go │ ├── reuse_other.go │ ├── reuse_unix.go │ └── reuse_windows.go ├── structure │ ├── structure.go │ └── structure_test.go └── utils │ ├── callback.go │ ├── global_id.go │ ├── hash.go │ ├── manipulation.go │ ├── must.go │ ├── range.go │ ├── ranges.go │ ├── ranges_test.go │ ├── slice.go │ ├── string_unsafe.go │ ├── strings.go │ ├── uuid.go │ └── uuid_test.go ├── component ├── arp │ ├── arp.go │ ├── arp_linux.go │ ├── arp_other.go │ ├── arp_windows.go │ ├── get_ip_net_table2.go │ ├── mib_ipnet_row2.go │ └── mib_ipnet_table2.go ├── auth │ └── auth.go ├── ca │ ├── ca-certificates.crt │ ├── config.go │ └── fix_windows.go ├── cidr │ ├── ipcidr_set.go │ ├── ipcidr_set_bin.go │ └── ipcidr_set_test.go ├── dhcp │ ├── conn.go │ └── dhcp.go ├── dialer │ ├── bind.go │ ├── bind_darwin.go │ ├── bind_linux.go │ ├── bind_others.go │ ├── bind_windows.go │ ├── control.go │ ├── dialer.go │ ├── error.go │ ├── mark_linux.go │ ├── mark_nonlinux.go │ ├── mptcp_go120.go │ ├── mptcp_go121.go │ ├── options.go │ ├── reuse.go │ ├── socket_hook.go │ ├── tfo.go │ └── tfo_windows.go ├── fakeip │ ├── cachefile.go │ ├── memory.go │ ├── pool.go │ └── pool_test.go ├── generater │ ├── cmd.go │ └── types.go ├── geodata │ ├── attr.go │ ├── geodata.go │ ├── geodataproto.go │ ├── init.go │ ├── memconservative │ │ ├── cache.go │ │ ├── decode.go │ │ └── memc.go │ ├── package_info.go │ ├── router │ │ ├── condition.go │ │ ├── config.pb.go │ │ └── config.proto │ ├── standard │ │ └── standard.go │ ├── strmatcher │ │ ├── ac_automaton_matcher.go │ │ ├── matchers.go │ │ ├── mph_matcher.go │ │ ├── package_info.go │ │ └── strmatcher.go │ └── utils.go ├── http │ └── http.go ├── iface │ └── iface.go ├── keepalive │ ├── tcp_keepalive.go │ ├── tcp_keepalive_go122.go │ ├── tcp_keepalive_go123.go │ ├── tcp_keepalive_go123_unix.go │ └── tcp_keepalive_go123_windows.go ├── loopback │ └── detector.go ├── mmdb │ ├── mmdb.go │ └── reader.go ├── nat │ ├── proxy.go │ └── table.go ├── pool │ ├── pool.go │ └── pool_test.go ├── power │ ├── event.go │ ├── event_other.go │ └── event_windows.go ├── process │ ├── find_process_mode.go │ ├── process.go │ ├── process_darwin.go │ ├── process_freebsd_amd64.go │ ├── process_linux.go │ ├── process_other.go │ └── process_windows.go ├── profile │ ├── cachefile │ │ ├── cache.go │ │ ├── etag.go │ │ ├── fakeip.go │ │ └── subscriptioninfo.go │ └── profile.go ├── proxydialer │ ├── proxydialer.go │ ├── sing.go │ ├── slowdown.go │ └── slowdown_sing.go ├── resolver │ ├── enhancer.go │ ├── host.go │ ├── hosts │ │ ├── hosts.go │ │ └── hosts_windows.go │ ├── ip4p.go │ ├── local.go │ ├── relay.go │ ├── resolver.go │ └── system.go ├── resource │ ├── fetcher.go │ └── vehicle.go ├── slowdown │ ├── backoff.go │ └── slowdown.go ├── sniffer │ ├── base_sniffer.go │ ├── dispatcher.go │ ├── http_sniffer.go │ ├── quic_sniffer.go │ ├── sniff_test.go │ └── tls_sniffer.go ├── tls │ ├── reality.go │ └── utls.go ├── trie │ ├── domain.go │ ├── domain_set.go │ ├── domain_set_bin.go │ ├── domain_set_test.go │ ├── domain_test.go │ ├── ipcidr_node.go │ ├── ipcidr_trie.go │ ├── node.go │ └── trie_test.go └── updater │ ├── update_core.go │ ├── update_geo.go │ ├── update_ui.go │ └── utils.go ├── config ├── config.go ├── initial.go └── utils.go ├── constant ├── adapters.go ├── context.go ├── dns.go ├── features │ ├── cmfa.go │ ├── cmfa_stub.go │ ├── low_memory.go │ ├── low_memory_stub.go │ ├── no_fake_tcp.go │ ├── no_fake_tcp_stub.go │ ├── tags.go │ ├── version.go │ ├── version_windows.go │ ├── with_gvisor.go │ └── with_gvisor_stub.go ├── listener.go ├── matcher.go ├── metadata.go ├── path.go ├── provider │ └── interface.go ├── rule.go ├── sniffer │ └── sniffer.go ├── tun.go ├── tunnel.go └── version.go ├── context ├── conn.go ├── dns.go └── packetconn.go ├── dns ├── client.go ├── dhcp.go ├── dialer.go ├── doh.go ├── doq.go ├── edns0_subnet.go ├── enhancer.go ├── local.go ├── middleware.go ├── patch_android.go ├── policy.go ├── rcode.go ├── resolver.go ├── server.go ├── system.go ├── system_common.go ├── system_posix.go ├── system_windows.go └── util.go ├── docker └── file-name.sh ├── docs ├── config.yaml └── logo.png ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── hub ├── executor │ ├── concurrent_load_limit.go │ ├── concurrent_load_single.go │ ├── concurrent_load_unlimit.go │ └── executor.go ├── hub.go └── route │ ├── cache.go │ ├── common.go │ ├── configs.go │ ├── connections.go │ ├── ctxkeys.go │ ├── dns.go │ ├── doh.go │ ├── errors.go │ ├── external.go │ ├── groups.go │ ├── patch_android.go │ ├── provider.go │ ├── proxies.go │ ├── restart.go │ ├── rules.go │ ├── server.go │ └── upgrade.go ├── listener ├── anytls │ └── server.go ├── auth │ └── auth.go ├── config │ ├── anytls.go │ ├── auth.go │ ├── hysteria2.go │ ├── shadowsocks.go │ ├── shadowtls.go │ ├── trojan.go │ ├── tuic.go │ ├── tun.go │ ├── tunnel.go │ ├── vless.go │ └── vmess.go ├── http │ ├── client.go │ ├── hack.go │ ├── proxy.go │ ├── server.go │ ├── upgrade.go │ └── utils.go ├── inbound │ ├── anytls.go │ ├── anytls_test.go │ ├── auth.go │ ├── base.go │ ├── common_test.go │ ├── http.go │ ├── hysteria2.go │ ├── hysteria2_test.go │ ├── mixed.go │ ├── mux.go │ ├── mux_test.go │ ├── reality.go │ ├── redir.go │ ├── shadowsocks.go │ ├── shadowsocks_test.go │ ├── shadowtls.go │ ├── socks.go │ ├── tproxy.go │ ├── trojan.go │ ├── trojan_test.go │ ├── tuic.go │ ├── tuic_test.go │ ├── tun.go │ ├── tunnel.go │ ├── vless.go │ ├── vless_test.go │ ├── vmess.go │ └── vmess_test.go ├── inner │ └── tcp.go ├── listener.go ├── mixed │ └── mixed.go ├── parse.go ├── reality │ └── reality.go ├── redir │ ├── tcp.go │ ├── tcp_darwin.go │ ├── tcp_freebsd.go │ ├── tcp_linux.go │ ├── tcp_linux_386.go │ ├── tcp_linux_other.go │ └── tcp_other.go ├── shadowsocks │ ├── tcp.go │ ├── udp.go │ └── utils.go ├── sing │ ├── context.go │ ├── dialer.go │ ├── sing.go │ └── util.go ├── sing_hysteria2 │ └── server.go ├── sing_shadowsocks │ └── server.go ├── sing_tun │ ├── dns.go │ ├── iface.go │ ├── redirect_linux.go │ ├── redirect_stub.go │ ├── server.go │ ├── server_android.go │ ├── server_notandroid.go │ ├── server_notwindows.go │ ├── server_windows.go │ ├── tun_name_darwin.go │ ├── tun_name_linux.go │ └── tun_name_other.go ├── sing_vless │ └── server.go ├── sing_vmess │ └── server.go ├── socks │ ├── tcp.go │ ├── udp.go │ └── utils.go ├── tproxy │ ├── packet.go │ ├── setsockopt_linux.go │ ├── setsockopt_other.go │ ├── tproxy.go │ ├── tproxy_iptables.go │ ├── udp.go │ ├── udp_linux.go │ └── udp_other.go ├── trojan │ ├── packet.go │ └── server.go ├── tuic │ └── server.go └── tunnel │ ├── packet.go │ ├── tcp.go │ └── udp.go ├── log ├── level.go ├── log.go └── sing.go ├── main.go ├── ntp ├── service.go ├── time_stub.go ├── time_unix.go └── time_windows.go ├── rules ├── common │ ├── base.go │ ├── domain.go │ ├── domain_keyword.go │ ├── domain_regex.go │ ├── domain_suffix.go │ ├── dscp.go │ ├── final.go │ ├── geoip.go │ ├── geosite.go │ ├── in_name.go │ ├── in_type.go │ ├── in_user.go │ ├── ipasn.go │ ├── ipcidr.go │ ├── ipsuffix.go │ ├── mac.go │ ├── network_type.go │ ├── port.go │ ├── process.go │ └── uid.go ├── logic │ └── logic.go ├── logic_test │ └── logic_test.go ├── parser.go └── provider │ ├── classical_strategy.go │ ├── domain_strategy.go │ ├── ipcidr_strategy.go │ ├── mrs_converter.go │ ├── mrs_reader.go │ ├── parse.go │ ├── provider.go │ └── rule_set.go ├── test ├── .golangci.yaml ├── Makefile ├── README.md ├── clash_test.go ├── config │ ├── example.org-key.pem │ ├── example.org.pem │ ├── hysteria.json │ ├── snell-http.conf │ ├── snell-tls.conf │ ├── snell.conf │ ├── trojan-grpc.json │ ├── trojan-ws.json │ ├── trojan-xtls.json │ ├── trojan.json │ ├── vless-tls.json │ ├── vless-ws.json │ ├── vless-xtls.json │ ├── vmess-grpc.json │ ├── vmess-http.json │ ├── vmess-http2.json │ ├── vmess-tls.json │ ├── vmess-ws-0rtt.json │ ├── vmess-ws-tls.json │ ├── vmess-ws.json │ ├── vmess.json │ └── xray-shadowsocks.json ├── dns_test.go ├── docker_test.go ├── go.mod ├── go.sum ├── hysteria_test.go ├── snell_test.go ├── ss_test.go ├── trojan_test.go ├── util.go ├── util_darwin_test.go ├── util_other_test.go ├── vless_test.go └── vmess_test.go ├── transport ├── anytls │ ├── client.go │ ├── padding │ │ └── padding.go │ ├── pipe │ │ ├── deadline.go │ │ └── io_pipe.go │ ├── session │ │ ├── client.go │ │ ├── frame.go │ │ ├── session.go │ │ └── stream.go │ ├── skiplist │ │ ├── contianer.go │ │ ├── skiplist.go │ │ ├── skiplist_newnode.go │ │ └── types.go │ └── util │ │ ├── deadline.go │ │ ├── routine.go │ │ ├── string_map.go │ │ └── type.go ├── gost-plugin │ └── websocket.go ├── gun │ ├── gun.go │ ├── server.go │ ├── transport.go │ ├── transport_close.go │ └── utils.go ├── hysteria │ ├── congestion │ │ ├── brutal.go │ │ └── pacer.go │ ├── conns │ │ ├── faketcp │ │ │ ├── LICENSE │ │ │ ├── obfs.go │ │ │ ├── tcp_linux.go │ │ │ ├── tcp_stub.go │ │ │ └── tcp_test.go │ │ ├── udp │ │ │ ├── hop.go │ │ │ └── obfs.go │ │ └── wechat │ │ │ └── obfs.go │ ├── core │ │ ├── client.go │ │ ├── frag.go │ │ ├── frag_test.go │ │ ├── protocol.go │ │ └── stream.go │ ├── obfs │ │ ├── dummy.go │ │ ├── obfs.go │ │ ├── xplus.go │ │ └── xplus_test.go │ ├── pmtud_fix │ │ ├── avail.go │ │ └── unavail.go │ ├── transport │ │ └── client.go │ └── utils │ │ └── misc.go ├── restls │ └── restls.go ├── shadowsocks │ ├── README.md │ ├── core │ │ └── cipher.go │ ├── shadowaead │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go │ └── shadowstream │ │ ├── cipher.go │ │ ├── old_chacha20.go │ │ ├── packet.go │ │ └── stream.go ├── shadowtls │ └── shadowtls.go ├── simple-obfs │ ├── http.go │ └── tls.go ├── sing-shadowtls │ └── shadowtls.go ├── snell │ ├── cipher.go │ ├── pool.go │ └── snell.go ├── socks4 │ └── socks4.go ├── socks5 │ └── socks5.go ├── ssr │ ├── obfs │ │ ├── base.go │ │ ├── http_post.go │ │ ├── http_simple.go │ │ ├── obfs.go │ │ ├── plain.go │ │ ├── random_head.go │ │ └── tls1.2_ticket_auth.go │ ├── protocol │ │ ├── auth_aes128_md5.go │ │ ├── auth_aes128_sha1.go │ │ ├── auth_chain_a.go │ │ ├── auth_chain_b.go │ │ ├── auth_sha1_v4.go │ │ ├── base.go │ │ ├── origin.go │ │ ├── packet.go │ │ ├── protocol.go │ │ └── stream.go │ └── tools │ │ ├── bufPool.go │ │ ├── crypto.go │ │ └── random.go ├── trojan │ └── trojan.go ├── tuic │ ├── common │ │ ├── congestion.go │ │ ├── stream.go │ │ └── type.go │ ├── congestion │ │ ├── bandwidth.go │ │ ├── bandwidth_sampler.go │ │ ├── bbr_sender.go │ │ ├── clock.go │ │ ├── cubic.go │ │ ├── cubic_sender.go │ │ ├── hybrid_slow_start.go │ │ ├── minmax.go │ │ ├── minmax_go120.go │ │ ├── minmax_go121.go │ │ ├── pacer.go │ │ └── windowed_filter.go │ ├── congestion_v2 │ │ ├── bandwidth.go │ │ ├── bandwidth_sampler.go │ │ ├── bbr_sender.go │ │ ├── clock.go │ │ ├── minmax_go120.go │ │ ├── minmax_go121.go │ │ ├── pacer.go │ │ ├── packet_number_indexed_queue.go │ │ ├── ringbuffer.go │ │ └── windowed_filter.go │ ├── pool_client.go │ ├── server.go │ ├── tuic.go │ ├── v4 │ │ ├── client.go │ │ ├── packet.go │ │ ├── protocol.go │ │ └── server.go │ └── v5 │ │ ├── client.go │ │ ├── frag.go │ │ ├── packet.go │ │ ├── protocol.go │ │ └── server.go ├── v2ray-plugin │ ├── mux.go │ └── websocket.go ├── vless │ ├── config.pb.go │ ├── config.proto │ ├── conn.go │ ├── vision │ │ ├── conn.go │ │ ├── filter.go │ │ ├── padding.go │ │ └── vision.go │ └── vless.go └── vmess │ ├── aead.go │ ├── chunk.go │ ├── conn.go │ ├── h2.go │ ├── header.go │ ├── http.go │ ├── tls.go │ ├── user.go │ ├── vmess.go │ ├── websocket.go │ ├── websocket_go120.go │ └── websocket_go121.go └── tunnel ├── connection.go ├── dns_dialer.go ├── mode.go ├── statistic ├── manager.go ├── patch_android.go └── tracker.go ├── status.go └── tunnel.go /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: mihomo Community Support 4 | url: https://github.com/MetaCubeX/mihomo/discussions 5 | about: Please ask and answer questions about mihomo here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: checkboxes 7 | id: ensure 8 | attributes: 9 | label: Verify steps 10 | description: " 11 | 在提交之前,请确认 12 | Please verify that you've followed these steps 13 | " 14 | options: 15 | - label: " 16 | 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 17 | I have searched on the [issue tracker](……/) for a related feature request. 18 | " 19 | required: true 20 | - label: " 21 | 我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法找到这个功能 22 | I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue. 23 | " 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Description 28 | description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Mihomo Core 的行为是什麽? 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: Possible Solution 34 | description: " 35 | 此项非必须,但是如果你有想法的话欢迎提出。 36 | Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change 37 | " -------------------------------------------------------------------------------- /.github/genReleaseNote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts "v:" opt; do 4 | case $opt in 5 | v) 6 | version_range=$OPTARG 7 | ;; 8 | \?) 9 | echo "Invalid option: -$OPTARG" >&2 10 | exit 1 11 | ;; 12 | esac 13 | done 14 | 15 | if [ -z "$version_range" ]; then 16 | echo "Please provide the version range using -v option. Example: ./genReleashNote.sh -v v1.14.1...v1.14.2" 17 | exit 1 18 | fi 19 | 20 | echo "## What's Changed" > release.md 21 | git log --pretty=format:"* %h %s by @%an" --grep="^feat" -i $version_range | sort -f | uniq >> release.md 22 | echo "" >> release.md 23 | 24 | echo "## BUG & Fix" >> release.md 25 | git log --pretty=format:"* %h %s by @%an" --grep="^fix" -i $version_range | sort -f | uniq >> release.md 26 | echo "" >> release.md 27 | 28 | echo "## Maintenance" >> release.md 29 | git log --pretty=format:"* %h %s by @%an" --grep="^chore\|^docs\|^refactor" -i $version_range | sort -f | uniq >> release.md 30 | echo "" >> release.md 31 | 32 | echo "**Full Changelog**: https://github.com/MetaCubeX/mihomo/compare/$version_range" >> release.md 33 | -------------------------------------------------------------------------------- /.github/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILENAMES=$(ls) 4 | for FILENAME in $FILENAMES 5 | do 6 | if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then 7 | gzip -S ".gz" $FILENAME 8 | elif [[ $FILENAME =~ ".exe" ]];then 9 | zip -m ${FILENAME%.*}.zip $FILENAME 10 | else echo "skip $FILENAME" 11 | fi 12 | done 13 | 14 | FILENAMES=$(ls) 15 | for FILENAME in $FILENAMES 16 | do 17 | if [[ $FILENAME =~ ".zip" ]];then 18 | echo "rename $FILENAME" 19 | mv $FILENAME ${FILENAME%.*}-${VERSION}.zip 20 | elif [[ $FILENAME =~ ".gz" ]];then 21 | echo "rename $FILENAME" 22 | mv $FILENAME ${FILENAME%.*}-${VERSION}.gz 23 | else 24 | echo "skip $FILENAME" 25 | fi 26 | done -------------------------------------------------------------------------------- /.github/release/.fpm_systemd: -------------------------------------------------------------------------------- 1 | -s dir 2 | --name mihomo 3 | --category net 4 | --license GPL-3.0-or-later 5 | --description "The universal proxy platform." 6 | --url "https://wiki.metacubex.one/" 7 | --maintainer "MetaCubeX " 8 | --deb-field "Bug: https://github.com/MetaCubeX/mihomo/issues" 9 | --no-deb-generate-changes 10 | --config-files /etc/mihomo/config.yaml 11 | 12 | .github/release/config.yaml=/etc/mihomo/config.yaml 13 | 14 | .github/release/mihomo.service=/usr/lib/systemd/system/mihomo.service 15 | .github/release/mihomo@.service=/usr/lib/systemd/system/mihomo@.service 16 | 17 | 18 | LICENSE=/usr/share/licenses/mihomo/LICENSE -------------------------------------------------------------------------------- /.github/release/config.yaml: -------------------------------------------------------------------------------- 1 | mixed-port: 7890 2 | 3 | dns: 4 | enable: true 5 | ipv6: true 6 | enhanced-mode: fake-ip 7 | fake-ip-filter: 8 | - "*" 9 | - "+.lan" 10 | - "+.local" 11 | - "+.market.xiaomi.com" 12 | default-nameserver: 13 | - tls://223.5.5.5 14 | nameserver: 15 | - https://doh.pub/dns-query 16 | - https://dns.alidns.com/dns-query 17 | 18 | rules: 19 | - MATCH,DIRECT -------------------------------------------------------------------------------- /.github/release/mihomo.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=mihomo Daemon, Another Clash Kernel. 3 | Documentation=https://wiki.metacubex.one 4 | After=network.target nss-lookup.target network-online.target 5 | 6 | [Service] 7 | Type=simple 8 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE 9 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE 10 | ExecStart=/usr/bin/mihomo -d /etc/mihomo 11 | ExecReload=/bin/kill -HUP $MAINPID 12 | Restart=on-failure 13 | RestartSec=10 14 | LimitNOFILE=infinity 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /.github/release/mihomo@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=mihomo Daemon, Another Clash Kernel. 3 | Documentation=https://wiki.metacubex.one 4 | After=network.target nss-lookup.target network-online.target 5 | 6 | [Service] 7 | Type=simple 8 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE 9 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE 10 | ExecStart=/usr/bin/mihomo -d /etc/mihomo 11 | ExecReload=/bin/kill -HUP $MAINPID 12 | Restart=on-failure 13 | RestartSec=10 14 | LimitNOFILE=infinity 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /.github/rename-go120.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILENAMES=$(ls) 4 | for FILENAME in $FILENAMES 5 | do 6 | if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then 7 | mv $FILENAME ${FILENAME}-go120 8 | elif [[ $FILENAME =~ ".exe" ]];then 9 | mv $FILENAME ${FILENAME%.*}-go120.exe 10 | else echo "skip $FILENAME" 11 | fi 12 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # go mod vendor 16 | vendor 17 | 18 | # GoLand 19 | .idea/* 20 | 21 | # macOS file 22 | .DS_Store 23 | 24 | # test suite 25 | test/config/cache* 26 | /output 27 | .vscode/ 28 | .fleet/ -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - staticcheck 6 | - govet 7 | - gci 8 | 9 | linters-settings: 10 | gci: 11 | custom-order: true 12 | sections: 13 | - standard 14 | - prefix(github.com/metacubex/mihomo) 15 | - default 16 | staticcheck: 17 | go: '1.19' 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest as builder 2 | ARG TARGETPLATFORM 3 | RUN echo "I'm building for $TARGETPLATFORM" 4 | 5 | RUN apk add --no-cache gzip && \ 6 | mkdir /mihomo-config && \ 7 | wget -O /mihomo-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \ 8 | wget -O /mihomo-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \ 9 | wget -O /mihomo-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat 10 | 11 | COPY docker/file-name.sh /mihomo/file-name.sh 12 | WORKDIR /mihomo 13 | COPY bin/ bin/ 14 | RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ 15 | FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \ 16 | mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && chmod +x mihomo && echo "$FILE_NAME" > /mihomo-config/test 17 | FROM alpine:latest 18 | LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo" 19 | 20 | RUN apk add --no-cache ca-certificates tzdata iptables 21 | 22 | VOLUME ["/root/.config/mihomo/"] 23 | 24 | COPY --from=builder /mihomo-config/ /root/.config/mihomo/ 25 | COPY --from=builder /mihomo/mihomo /mihomo 26 | ENTRYPOINT [ "/mihomo" ] 27 | -------------------------------------------------------------------------------- /Meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xishang0128/mihomo/0edaf093778d126f1ac7b65e4bcb0160f610cf79/Meta.png -------------------------------------------------------------------------------- /adapter/inbound/auth.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | var skipAuthPrefixes []netip.Prefix 11 | 12 | func SetSkipAuthPrefixes(prefixes []netip.Prefix) { 13 | skipAuthPrefixes = prefixes 14 | } 15 | 16 | func SkipAuthPrefixes() []netip.Prefix { 17 | return skipAuthPrefixes 18 | } 19 | 20 | func SkipAuthRemoteAddr(addr net.Addr) bool { 21 | m := C.Metadata{} 22 | if err := m.SetRemoteAddr(addr); err != nil { 23 | return false 24 | } 25 | return skipAuth(m.AddrPort().Addr()) 26 | } 27 | 28 | func SkipAuthRemoteAddress(addr string) bool { 29 | m := C.Metadata{} 30 | if err := m.SetRemoteAddress(addr); err != nil { 31 | return false 32 | } 33 | return skipAuth(m.AddrPort().Addr()) 34 | } 35 | 36 | func skipAuth(addr netip.Addr) bool { 37 | return prefixesContains(skipAuthPrefixes, addr) 38 | } 39 | -------------------------------------------------------------------------------- /adapter/inbound/http.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | "github.com/metacubex/mihomo/transport/socks5" 8 | ) 9 | 10 | // NewHTTP receive normal http request and return HTTPContext 11 | func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { 12 | metadata := parseSocksAddr(target) 13 | metadata.NetWork = C.TCP 14 | metadata.Type = C.HTTP 15 | metadata.RawSrcAddr = srcConn.RemoteAddr() 16 | metadata.RawDstAddr = srcConn.LocalAddr() 17 | ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr())) 18 | ApplyAdditions(metadata, additions...) 19 | return conn, metadata 20 | } 21 | -------------------------------------------------------------------------------- /adapter/inbound/https.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | // NewHTTPS receive CONNECT request and return ConnContext 11 | func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { 12 | metadata := parseHTTPAddr(request) 13 | metadata.Type = C.HTTPS 14 | metadata.RawSrcAddr = conn.RemoteAddr() 15 | metadata.RawDstAddr = conn.LocalAddr() 16 | ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) 17 | ApplyAdditions(metadata, additions...) 18 | return conn, metadata 19 | } 20 | -------------------------------------------------------------------------------- /adapter/inbound/ipfilter.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | var lanAllowedIPs []netip.Prefix 11 | var lanDisAllowedIPs []netip.Prefix 12 | 13 | func SetAllowedIPs(prefixes []netip.Prefix) { 14 | lanAllowedIPs = prefixes 15 | } 16 | 17 | func SetDisAllowedIPs(prefixes []netip.Prefix) { 18 | lanDisAllowedIPs = prefixes 19 | } 20 | 21 | func AllowedIPs() []netip.Prefix { 22 | return lanAllowedIPs 23 | } 24 | 25 | func DisAllowedIPs() []netip.Prefix { 26 | return lanDisAllowedIPs 27 | } 28 | 29 | func IsRemoteAddrDisAllowed(addr net.Addr) bool { 30 | m := C.Metadata{} 31 | if err := m.SetRemoteAddr(addr); err != nil { 32 | return false 33 | } 34 | ipAddr := m.AddrPort().Addr() 35 | if ipAddr.IsValid() { 36 | return isAllowed(ipAddr) && !isDisAllowed(ipAddr) 37 | } 38 | return false 39 | } 40 | 41 | func isAllowed(addr netip.Addr) bool { 42 | return prefixesContains(lanAllowedIPs, addr) 43 | } 44 | 45 | func isDisAllowed(addr netip.Addr) bool { 46 | return prefixesContains(lanDisAllowedIPs, addr) 47 | } 48 | -------------------------------------------------------------------------------- /adapter/inbound/listen_notwindows.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package inbound 4 | 5 | import ( 6 | "net" 7 | "os" 8 | ) 9 | 10 | const SupportNamedPipe = false 11 | 12 | func ListenNamedPipe(path string) (net.Listener, error) { 13 | return nil, os.ErrInvalid 14 | } 15 | -------------------------------------------------------------------------------- /adapter/inbound/listen_windows.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "os" 6 | 7 | "github.com/metacubex/wireguard-go/ipc/namedpipe" 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | const SupportNamedPipe = true 12 | 13 | // windowsSDDL is the Security Descriptor set on the namedpipe. 14 | // It provides read/write access to all users and the local system. 15 | const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)" 16 | 17 | func ListenNamedPipe(path string) (net.Listener, error) { 18 | sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL") 19 | if sddl == "" { 20 | sddl = windowsSDDL 21 | } 22 | securityDescriptor, err := windows.SecurityDescriptorFromString(sddl) 23 | if err != nil { 24 | return nil, err 25 | } 26 | namedpipeLC := namedpipe.ListenConfig{ 27 | SecurityDescriptor: securityDescriptor, 28 | InputBufferSize: 256 * 1024, 29 | OutputBufferSize: 256 * 1024, 30 | } 31 | return namedpipeLC.Listen(path) 32 | } 33 | -------------------------------------------------------------------------------- /adapter/inbound/mptcp_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package inbound 4 | 5 | import "net" 6 | 7 | const multipathTCPAvailable = false 8 | 9 | func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) { 10 | } 11 | 12 | func getMultiPathTCP(listenConfig *net.ListenConfig) bool { 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /adapter/inbound/mptcp_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package inbound 4 | 5 | import "net" 6 | 7 | const multipathTCPAvailable = true 8 | 9 | func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) { 10 | listenConfig.SetMultipathTCP(open) 11 | } 12 | 13 | func getMultiPathTCP(listenConfig *net.ListenConfig) bool { 14 | return listenConfig.MultipathTCP() 15 | } 16 | -------------------------------------------------------------------------------- /adapter/inbound/packet.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | C "github.com/metacubex/mihomo/constant" 5 | "github.com/metacubex/mihomo/transport/socks5" 6 | ) 7 | 8 | // NewPacket is PacketAdapter generator 9 | func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) { 10 | metadata := parseSocksAddr(target) 11 | metadata.NetWork = C.UDP 12 | metadata.Type = source 13 | metadata.RawSrcAddr = packet.LocalAddr() 14 | metadata.RawDstAddr = metadata.UDPAddr() 15 | ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr())) 16 | if p, ok := packet.(C.UDPPacketInAddr); ok { 17 | ApplyAdditions(metadata, WithInAddr(p.InAddr())) 18 | } 19 | ApplyAdditions(metadata, additions...) 20 | 21 | return packet, metadata 22 | } 23 | -------------------------------------------------------------------------------- /adapter/inbound/socket.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | "github.com/metacubex/mihomo/transport/socks5" 8 | ) 9 | 10 | // NewSocket receive TCP inbound and return ConnContext 11 | func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) { 12 | metadata := parseSocksAddr(target) 13 | metadata.NetWork = C.TCP 14 | metadata.Type = source 15 | ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) 16 | ApplyAdditions(metadata, additions...) 17 | return conn, metadata 18 | } 19 | -------------------------------------------------------------------------------- /adapter/outbound/reality.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "crypto/ecdh" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | 10 | tlsC "github.com/metacubex/mihomo/component/tls" 11 | ) 12 | 13 | type RealityOptions struct { 14 | PublicKey string `proxy:"public-key"` 15 | ShortID string `proxy:"short-id"` 16 | } 17 | 18 | func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) { 19 | if o.PublicKey != "" { 20 | config := new(tlsC.RealityConfig) 21 | 22 | const x25519ScalarSize = 32 23 | publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey) 24 | if err != nil || len(publicKey) != x25519ScalarSize { 25 | return nil, errors.New("invalid REALITY public key") 26 | } 27 | config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey) 28 | if err != nil { 29 | return nil, fmt.Errorf("fail to create REALITY public key: %w", err) 30 | } 31 | 32 | n := hex.DecodedLen(len(o.ShortID)) 33 | if n > tlsC.RealityMaxShortIDLen { 34 | return nil, errors.New("invalid REALITY short id") 35 | } 36 | n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID)) 37 | if err != nil || n > tlsC.RealityMaxShortIDLen { 38 | return nil, errors.New("invalid REALITY short ID") 39 | } 40 | 41 | return config, nil 42 | } 43 | return nil, nil 44 | } 45 | -------------------------------------------------------------------------------- /adapter/outboundgroup/patch_android.go: -------------------------------------------------------------------------------- 1 | //go:build android && cmfa 2 | 3 | package outboundgroup 4 | 5 | import ( 6 | C "github.com/metacubex/mihomo/constant" 7 | "github.com/metacubex/mihomo/constant/provider" 8 | ) 9 | 10 | type ProxyGroup interface { 11 | C.ProxyAdapter 12 | 13 | Providers() []provider.ProxyProvider 14 | Proxies() []C.Proxy 15 | Now() string 16 | } 17 | 18 | func (f *Fallback) Providers() []provider.ProxyProvider { 19 | return f.providers 20 | } 21 | 22 | func (lb *LoadBalance) Providers() []provider.ProxyProvider { 23 | return lb.providers 24 | } 25 | 26 | func (f *Fallback) Proxies() []C.Proxy { 27 | return f.GetProxies(false) 28 | } 29 | 30 | func (lb *LoadBalance) Proxies() []C.Proxy { 31 | return lb.GetProxies(false) 32 | } 33 | 34 | func (lb *LoadBalance) Now() string { 35 | return "" 36 | } 37 | 38 | func (r *Relay) Providers() []provider.ProxyProvider { 39 | return r.providers 40 | } 41 | 42 | func (r *Relay) Proxies() []C.Proxy { 43 | return r.GetProxies(false) 44 | } 45 | 46 | func (r *Relay) Now() string { 47 | return "" 48 | } 49 | 50 | func (s *Selector) Providers() []provider.ProxyProvider { 51 | return s.providers 52 | } 53 | 54 | func (s *Selector) Proxies() []C.Proxy { 55 | return s.GetProxies(false) 56 | } 57 | 58 | func (u *URLTest) Providers() []provider.ProxyProvider { 59 | return u.providers 60 | } 61 | 62 | func (u *URLTest) Proxies() []C.Proxy { 63 | return u.GetProxies(false) 64 | } 65 | -------------------------------------------------------------------------------- /adapter/outboundgroup/util.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | type SelectAble interface { 4 | Set(string) error 5 | ForceSet(name string) 6 | } 7 | 8 | var _ SelectAble = (*Fallback)(nil) 9 | var _ SelectAble = (*URLTest)(nil) 10 | var _ SelectAble = (*Selector)(nil) 11 | -------------------------------------------------------------------------------- /adapter/provider/subscription_info.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/metacubex/mihomo/log" 9 | ) 10 | 11 | type SubscriptionInfo struct { 12 | Upload int64 13 | Download int64 14 | Total int64 15 | Expire int64 16 | } 17 | 18 | func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) { 19 | userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "") 20 | si = new(SubscriptionInfo) 21 | 22 | for _, field := range strings.Split(userinfo, ";") { 23 | name, value, ok := strings.Cut(field, "=") 24 | if !ok { 25 | continue 26 | } 27 | 28 | intValue, err := parseValue(value) 29 | if err != nil { 30 | log.Warnln("[Provider] get subscription-userinfo: %e", err) 31 | continue 32 | } 33 | 34 | switch name { 35 | case "upload": 36 | si.Upload = intValue 37 | case "download": 38 | si.Download = intValue 39 | case "total": 40 | si.Total = intValue 41 | case "expire": 42 | si.Expire = intValue 43 | } 44 | } 45 | return si 46 | } 47 | 48 | func parseValue(value string) (int64, error) { 49 | if intValue, err := strconv.ParseInt(value, 10, 64); err == nil { 50 | return intValue, nil 51 | } 52 | 53 | if floatValue, err := strconv.ParseFloat(value, 64); err == nil { 54 | return int64(floatValue), nil 55 | } 56 | 57 | return 0, fmt.Errorf("failed to parse value '%s'", value) 58 | } 59 | -------------------------------------------------------------------------------- /android_tz.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89 6 | 7 | package main 8 | 9 | // #include 10 | import "C" 11 | import "time" 12 | 13 | func init() { 14 | var currentT C.time_t 15 | var currentTM C.struct_tm 16 | C.time(¤tT) 17 | C.localtime_r(¤tT, ¤tTM) 18 | tzOffset := int(currentTM.tm_gmtoff) 19 | tz := C.GoString(currentTM.tm_zone) 20 | time.Local = time.FixedZone(tz, tzOffset) 21 | } 22 | -------------------------------------------------------------------------------- /check_amd64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | flags=$(grep '^flags\b' 2 && strings.ContainsAny(cmd, "|") { 32 | suffix := strings.Join(args[2:], " ") 33 | args = append(args[:2], suffix) 34 | } 35 | return args 36 | } 37 | -------------------------------------------------------------------------------- /common/cmd/cmd_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package cmd 4 | 5 | import ( 6 | "os/exec" 7 | ) 8 | 9 | func prepareBackgroundCommand(cmd *exec.Cmd) { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /common/cmd/cmd_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSplitArgs(t *testing.T) { 11 | args := splitArgs("ls") 12 | args1 := splitArgs("ls -la") 13 | args2 := splitArgs("bash -c ls") 14 | args3 := splitArgs("bash -c ls -lahF | grep 'cmd'") 15 | 16 | assert.Equal(t, 1, len(args)) 17 | assert.Equal(t, 2, len(args1)) 18 | assert.Equal(t, 3, len(args2)) 19 | assert.Equal(t, 3, len(args3)) 20 | } 21 | 22 | func TestExecCmd(t *testing.T) { 23 | if runtime.GOOS == "windows" { 24 | _, err := ExecCmd("cmd -c 'dir'") 25 | assert.Nil(t, err) 26 | return 27 | } 28 | 29 | _, err := ExecCmd("ls") 30 | _, err1 := ExecCmd("ls -la") 31 | _, err2 := ExecCmd("bash -c ls") 32 | _, err3 := ExecCmd("bash -c ls -la") 33 | _, err4 := ExecCmd("bash -c ls -la | grep 'cmd'") 34 | 35 | assert.Nil(t, err) 36 | assert.Nil(t, err1) 37 | assert.Nil(t, err2) 38 | assert.Nil(t, err3) 39 | assert.Nil(t, err4) 40 | } 41 | -------------------------------------------------------------------------------- /common/cmd/cmd_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package cmd 4 | 5 | import ( 6 | "os/exec" 7 | "syscall" 8 | ) 9 | 10 | func prepareBackgroundCommand(cmd *exec.Cmd) { 11 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 12 | } 13 | -------------------------------------------------------------------------------- /common/contextutils/afterfunc_compact.go: -------------------------------------------------------------------------------- 1 | package contextutils 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | func afterFunc(ctx context.Context, f func()) (stop func() bool) { 9 | stopc := make(chan struct{}) 10 | once := sync.Once{} // either starts running f or stops f from running 11 | if ctx.Done() != nil { 12 | go func() { 13 | select { 14 | case <-ctx.Done(): 15 | once.Do(func() { 16 | go f() 17 | }) 18 | case <-stopc: 19 | } 20 | }() 21 | } 22 | 23 | return func() bool { 24 | stopped := false 25 | once.Do(func() { 26 | stopped = true 27 | close(stopc) 28 | }) 29 | return stopped 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/contextutils/afterfunc_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package contextutils 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | func AfterFunc(ctx context.Context, f func()) (stop func() bool) { 10 | return afterFunc(ctx, f) 11 | } 12 | -------------------------------------------------------------------------------- /common/contextutils/afterfunc_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package contextutils 4 | 5 | import "context" 6 | 7 | func AfterFunc(ctx context.Context, f func()) (stop func() bool) { 8 | return context.AfterFunc(ctx, f) 9 | } 10 | -------------------------------------------------------------------------------- /common/convert/base64.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "encoding/base64" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | encRaw = base64.RawStdEncoding 10 | enc = base64.StdEncoding 11 | ) 12 | 13 | // DecodeBase64 try to decode content from the given bytes, 14 | // which can be in base64.RawStdEncoding, base64.StdEncoding or just plaintext. 15 | func DecodeBase64(buf []byte) []byte { 16 | result, err := tryDecodeBase64(buf) 17 | if err != nil { 18 | return buf 19 | } 20 | return result 21 | } 22 | 23 | func tryDecodeBase64(buf []byte) ([]byte, error) { 24 | dBuf := make([]byte, encRaw.DecodedLen(len(buf))) 25 | n, err := encRaw.Decode(dBuf, buf) 26 | if err != nil { 27 | n, err = enc.Decode(dBuf, buf) 28 | if err != nil { 29 | return nil, err 30 | } 31 | } 32 | return dBuf[:n], nil 33 | } 34 | 35 | func urlSafe(data string) string { 36 | return strings.NewReplacer("+", "-", "/", "_").Replace(data) 37 | } 38 | 39 | func decodeUrlSafe(data string) string { 40 | dcBuf, err := base64.RawURLEncoding.DecodeString(data) 41 | if err != nil { 42 | return "" 43 | } 44 | return string(dcBuf) 45 | } 46 | -------------------------------------------------------------------------------- /common/convert/converter_test.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // https://v2.hysteria.network/zh/docs/developers/URI-Scheme/ 10 | func TestConvertsV2Ray_normal(t *testing.T) { 11 | hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test" 12 | 13 | expected := []map[string]interface{}{ 14 | { 15 | "name": "hy2test", 16 | "type": "hysteria2", 17 | "server": "example.com", 18 | "port": "8443", 19 | "sni": "real.example.com", 20 | "obfs": "salamander", 21 | "obfs-password": "gawrgura", 22 | "alpn": []string{"h3", "h4"}, 23 | "password": "letmein", 24 | "up": "114", 25 | "down": "514", 26 | "skip-cert-verify": true, 27 | "fingerprint": "deadbeef", 28 | }, 29 | } 30 | 31 | proxies, err := ConvertsV2Ray([]byte(hy2test)) 32 | 33 | assert.Nil(t, err) 34 | assert.Equal(t, expected, proxies) 35 | } 36 | -------------------------------------------------------------------------------- /common/murmur3/murmur.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | type bmixer interface { 4 | bmix(p []byte) (tail []byte) 5 | Size() (n int) 6 | reset() 7 | } 8 | 9 | type digest struct { 10 | clen int // Digested input cumulative length. 11 | tail []byte // 0 to Size()-1 bytes view of `buf'. 12 | buf [16]byte // Expected (but not required) to be Size() large. 13 | seed uint32 // Seed for initializing the hash. 14 | bmixer 15 | } 16 | 17 | func (d *digest) BlockSize() int { return 1 } 18 | 19 | func (d *digest) Write(p []byte) (n int, err error) { 20 | n = len(p) 21 | d.clen += n 22 | 23 | if len(d.tail) > 0 { 24 | // Stick back pending bytes. 25 | nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. 26 | if nfree < len(p) { 27 | // One full block can be formed. 28 | block := append(d.tail, p[:nfree]...) 29 | p = p[nfree:] 30 | _ = d.bmix(block) // No tail. 31 | } else { 32 | // Tail's buf is large enough to prevent reallocs. 33 | p = append(d.tail, p...) 34 | } 35 | } 36 | 37 | d.tail = d.bmix(p) 38 | 39 | // Keep own copy of the 0 to Size()-1 pending bytes. 40 | nn := copy(d.buf[:], d.tail) 41 | d.tail = d.buf[:nn] 42 | 43 | return n, nil 44 | } 45 | 46 | func (d *digest) Reset() { 47 | d.clen = 0 48 | d.tail = nil 49 | d.bmixer.reset() 50 | } 51 | -------------------------------------------------------------------------------- /common/net/addr.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type CustomAddr interface { 8 | net.Addr 9 | RawAddr() net.Addr 10 | } 11 | 12 | type customAddr struct { 13 | networkStr string 14 | addrStr string 15 | rawAddr net.Addr 16 | } 17 | 18 | func (a customAddr) Network() string { 19 | return a.networkStr 20 | } 21 | 22 | func (a customAddr) String() string { 23 | return a.addrStr 24 | } 25 | 26 | func (a customAddr) RawAddr() net.Addr { 27 | return a.rawAddr 28 | } 29 | 30 | func NewCustomAddr(networkStr string, addrStr string, rawAddr net.Addr) CustomAddr { 31 | return customAddr{ 32 | networkStr: networkStr, 33 | addrStr: addrStr, 34 | rawAddr: rawAddr, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/net/bind.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "net" 4 | 5 | type bindPacketConn struct { 6 | EnhancePacketConn 7 | rAddr net.Addr 8 | } 9 | 10 | func (c *bindPacketConn) Read(b []byte) (n int, err error) { 11 | n, _, err = c.EnhancePacketConn.ReadFrom(b) 12 | return n, err 13 | } 14 | 15 | func (c *bindPacketConn) WaitRead() (data []byte, put func(), err error) { 16 | data, put, _, err = c.EnhancePacketConn.WaitReadFrom() 17 | return 18 | } 19 | 20 | func (c *bindPacketConn) Write(b []byte) (n int, err error) { 21 | return c.EnhancePacketConn.WriteTo(b, c.rAddr) 22 | } 23 | 24 | func (c *bindPacketConn) RemoteAddr() net.Addr { 25 | return c.rAddr 26 | } 27 | 28 | func (c *bindPacketConn) LocalAddr() net.Addr { 29 | if c.EnhancePacketConn.LocalAddr() == nil { 30 | return &net.UDPAddr{IP: net.IPv4zero, Port: 0} 31 | } else { 32 | return c.EnhancePacketConn.LocalAddr() 33 | } 34 | } 35 | 36 | func (c *bindPacketConn) Upstream() any { 37 | return c.EnhancePacketConn 38 | } 39 | 40 | func NewBindPacketConn(pc net.PacketConn, rAddr net.Addr) net.Conn { 41 | return &bindPacketConn{ 42 | EnhancePacketConn: NewEnhancePacketConn(pc), 43 | rAddr: rAddr, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/net/bufconn_unsafe.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "io" 5 | "unsafe" 6 | ) 7 | 8 | // bufioReader copy from stdlib bufio/bufio.go 9 | // This structure has remained unchanged from go1.5 to go1.21. 10 | type bufioReader struct { 11 | buf []byte 12 | rd io.Reader // reader provided by the client 13 | r, w int // buf read and write positions 14 | err error 15 | lastByte int // last byte read for UnreadByte; -1 means invalid 16 | lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid 17 | } 18 | 19 | func (c *BufferedConn) AppendData(buf []byte) (ok bool) { 20 | b := (*bufioReader)(unsafe.Pointer(c.r)) 21 | pos := len(b.buf) - b.w - len(buf) 22 | if pos >= -b.r { // len(b.buf)-(b.w - b.r) >= len(buf) 23 | if pos < 0 { // len(b.buf)-b.w < len(buf) 24 | // Slide existing data to beginning. 25 | copy(b.buf, b.buf[b.r:b.w]) 26 | b.w -= b.r 27 | b.r = 0 28 | } 29 | 30 | b.w += copy(b.buf[b.w:], buf) 31 | return true 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /common/net/cached.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metacubex/mihomo/common/buf" 7 | ) 8 | 9 | var _ ExtendedConn = (*CachedConn)(nil) 10 | 11 | type CachedConn struct { 12 | ExtendedConn 13 | data []byte 14 | } 15 | 16 | func NewCachedConn(c net.Conn, data []byte) *CachedConn { 17 | return &CachedConn{NewExtendedConn(c), data} 18 | } 19 | 20 | func (c *CachedConn) Read(b []byte) (n int, err error) { 21 | if len(c.data) > 0 { 22 | n = copy(b, c.data) 23 | c.data = c.data[n:] 24 | return 25 | } 26 | return c.ExtendedConn.Read(b) 27 | } 28 | 29 | func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy 30 | if len(c.data) > 0 { 31 | return buf.As(c.data) 32 | } 33 | return nil 34 | } 35 | 36 | func (c *CachedConn) Upstream() any { 37 | return c.ExtendedConn 38 | } 39 | 40 | func (c *CachedConn) ReaderReplaceable() bool { 41 | if len(c.data) > 0 { 42 | return false 43 | } 44 | return true 45 | } 46 | 47 | func (c *CachedConn) WriterReplaceable() bool { 48 | return true 49 | } 50 | -------------------------------------------------------------------------------- /common/net/context.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/metacubex/mihomo/common/contextutils" 8 | ) 9 | 10 | // SetupContextForConn is a helper function that starts connection I/O interrupter. 11 | // if ctx be canceled before done called, it will close the connection. 12 | // should use like this: 13 | // 14 | // func streamConn(ctx context.Context, conn net.Conn) (_ net.Conn, err error) { 15 | // if ctx.Done() != nil { 16 | // done := N.SetupContextForConn(ctx, conn) 17 | // defer done(&err) 18 | // } 19 | // conn, err := xxx 20 | // return conn, err 21 | // } 22 | func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) { 23 | stopc := make(chan struct{}) 24 | stop := contextutils.AfterFunc(ctx, func() { 25 | // Close the connection, discarding the error 26 | _ = conn.Close() 27 | close(stopc) 28 | }) 29 | return func(inputErr *error) { 30 | if !stop() { 31 | // The AfterFunc was started, wait for it to complete. 32 | <-stopc 33 | if ctxErr := ctx.Err(); ctxErr != nil && inputErr != nil { 34 | // Return context error to user. 35 | *inputErr = ctxErr 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/net/io.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "io" 4 | 5 | type ReadOnlyReader struct { 6 | io.Reader 7 | } 8 | 9 | type WriteOnlyWriter struct { 10 | io.Writer 11 | } 12 | -------------------------------------------------------------------------------- /common/net/packet.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/common/net/deadline" 5 | "github.com/metacubex/mihomo/common/net/packet" 6 | ) 7 | 8 | type EnhancePacketConn = packet.EnhancePacketConn 9 | type WaitReadFrom = packet.WaitReadFrom 10 | 11 | var NewEnhancePacketConn = packet.NewEnhancePacketConn 12 | var NewThreadSafePacketConn = packet.NewThreadSafePacketConn 13 | var NewRefPacketConn = packet.NewRefPacketConn 14 | 15 | var NewDeadlineNetPacketConn = deadline.NewNetPacketConn 16 | var NewDeadlineEnhancePacketConn = deadline.NewEnhancePacketConn 17 | var NewDeadlineSingPacketConn = deadline.NewSingPacketConn 18 | var NewDeadlineEnhanceSingPacketConn = deadline.NewEnhanceSingPacketConn 19 | -------------------------------------------------------------------------------- /common/net/packet/ref_sing.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/sagernet/sing/common/buf" 7 | M "github.com/sagernet/sing/common/metadata" 8 | N "github.com/sagernet/sing/common/network" 9 | ) 10 | 11 | type refSingPacketConn struct { 12 | *refPacketConn 13 | singPacketConn SingPacketConn 14 | } 15 | 16 | var _ N.NetPacketConn = (*refSingPacketConn)(nil) 17 | 18 | func (c *refSingPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 19 | defer runtime.KeepAlive(c.ref) 20 | return c.singPacketConn.WritePacket(buffer, destination) 21 | } 22 | 23 | func (c *refSingPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 24 | defer runtime.KeepAlive(c.ref) 25 | return c.singPacketConn.ReadPacket(buffer) 26 | } 27 | -------------------------------------------------------------------------------- /common/net/packet/thread.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | ) 7 | 8 | type threadSafePacketConn struct { 9 | EnhancePacketConn 10 | access sync.Mutex 11 | } 12 | 13 | func (c *threadSafePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 14 | c.access.Lock() 15 | defer c.access.Unlock() 16 | return c.EnhancePacketConn.WriteTo(b, addr) 17 | } 18 | 19 | func (c *threadSafePacketConn) Upstream() any { 20 | return c.EnhancePacketConn 21 | } 22 | 23 | func (c *threadSafePacketConn) ReaderReplaceable() bool { 24 | return true 25 | } 26 | 27 | func NewThreadSafePacketConn(pc net.PacketConn) EnhancePacketConn { 28 | tsPC := &threadSafePacketConn{EnhancePacketConn: NewEnhancePacketConn(pc)} 29 | if singPC, isSingPC := pc.(SingPacketConn); isSingPC { 30 | return &threadSafeSingPacketConn{ 31 | threadSafePacketConn: tsPC, 32 | singPacketConn: singPC, 33 | } 34 | } 35 | return tsPC 36 | } 37 | -------------------------------------------------------------------------------- /common/net/packet/thread_sing.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/sagernet/sing/common/buf" 5 | M "github.com/sagernet/sing/common/metadata" 6 | N "github.com/sagernet/sing/common/network" 7 | ) 8 | 9 | type threadSafeSingPacketConn struct { 10 | *threadSafePacketConn 11 | singPacketConn SingPacketConn 12 | } 13 | 14 | var _ N.NetPacketConn = (*threadSafeSingPacketConn)(nil) 15 | 16 | func (c *threadSafeSingPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { 17 | c.access.Lock() 18 | defer c.access.Unlock() 19 | return c.singPacketConn.WritePacket(buffer, destination) 20 | } 21 | 22 | func (c *threadSafeSingPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { 23 | return c.singPacketConn.ReadPacket(buffer) 24 | } 25 | -------------------------------------------------------------------------------- /common/net/relay.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | //import ( 4 | // "io" 5 | // "net" 6 | // "time" 7 | //) 8 | // 9 | //// Relay copies between left and right bidirectionally. 10 | //func Relay(leftConn, rightConn net.Conn) { 11 | // ch := make(chan error) 12 | // 13 | // go func() { 14 | // // Wrapping to avoid using *net.TCPConn.(ReadFrom) 15 | // // See also https://github.com/metacubex/mihomo/pull/1209 16 | // _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) 17 | // leftConn.SetReadDeadline(time.Now()) 18 | // ch <- err 19 | // }() 20 | // 21 | // _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) 22 | // rightConn.SetReadDeadline(time.Now()) 23 | // <-ch 24 | //} 25 | -------------------------------------------------------------------------------- /common/net/tcpip.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | func SplitNetworkType(s string) (string, string, error) { 10 | var ( 11 | shecme string 12 | hostPort string 13 | ) 14 | result := strings.Split(s, "://") 15 | if len(result) == 2 { 16 | shecme = result[0] 17 | hostPort = result[1] 18 | } else if len(result) == 1 { 19 | hostPort = result[0] 20 | } else { 21 | return "", "", fmt.Errorf("tcp/udp style error") 22 | } 23 | 24 | if len(shecme) == 0 { 25 | shecme = "udp" 26 | } 27 | 28 | if shecme != "tcp" && shecme != "udp" { 29 | return "", "", fmt.Errorf("scheme should be tcp:// or udp://") 30 | } else { 31 | return shecme, hostPort, nil 32 | } 33 | } 34 | 35 | func SplitHostPort(s string) (host, port string, hasPort bool, err error) { 36 | temp := s 37 | hasPort = true 38 | 39 | if !strings.Contains(s, ":") && !strings.Contains(s, "]:") { 40 | temp += ":0" 41 | hasPort = false 42 | } 43 | 44 | host, port, err = net.SplitHostPort(temp) 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /common/observable/iterable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | type Iterable[T any] <-chan T 4 | -------------------------------------------------------------------------------- /common/observable/subscriber.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Subscription[T any] <-chan T 8 | 9 | type Subscriber[T any] struct { 10 | buffer chan T 11 | once sync.Once 12 | } 13 | 14 | func (s *Subscriber[T]) Emit(item T) { 15 | s.buffer <- item 16 | } 17 | 18 | func (s *Subscriber[T]) Out() Subscription[T] { 19 | return s.buffer 20 | } 21 | 22 | func (s *Subscriber[T]) Close() { 23 | s.once.Do(func() { 24 | close(s.buffer) 25 | }) 26 | } 27 | 28 | func newSubscriber[T any]() *Subscriber[T] { 29 | sub := &Subscriber[T]{ 30 | buffer: make(chan T, 200), 31 | } 32 | return sub 33 | } 34 | -------------------------------------------------------------------------------- /common/once/once_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.22 2 | 3 | package once 4 | 5 | import ( 6 | "sync" 7 | "sync/atomic" 8 | "unsafe" 9 | ) 10 | 11 | type Once struct { 12 | done uint32 13 | m sync.Mutex 14 | } 15 | 16 | func Done(once *sync.Once) bool { 17 | // atomic visit sync.Once.done 18 | return atomic.LoadUint32((*uint32)(unsafe.Pointer(once))) == 1 19 | } 20 | 21 | func Reset(once *sync.Once) { 22 | o := (*Once)(unsafe.Pointer(once)) 23 | o.m.Lock() 24 | defer o.m.Unlock() 25 | atomic.StoreUint32(&o.done, 0) 26 | } 27 | -------------------------------------------------------------------------------- /common/once/once_go122.go: -------------------------------------------------------------------------------- 1 | //go:build go1.22 2 | 3 | package once 4 | 5 | import ( 6 | "sync" 7 | "sync/atomic" 8 | "unsafe" 9 | ) 10 | 11 | type Once struct { 12 | done atomic.Uint32 13 | m sync.Mutex 14 | } 15 | 16 | func Done(once *sync.Once) bool { 17 | // atomic visit sync.Once.done 18 | return (*atomic.Uint32)(unsafe.Pointer(once)).Load() == 1 19 | } 20 | 21 | func Reset(once *sync.Once) { 22 | o := (*Once)(unsafe.Pointer(once)) 23 | o.m.Lock() 24 | defer o.m.Unlock() 25 | o.done.Store(0) 26 | } 27 | -------------------------------------------------------------------------------- /common/picker/picker_test.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/samber/lo" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, error) { 13 | return func() (T, error) { 14 | timer := time.NewTimer(time.Millisecond * time.Duration(delay)) 15 | select { 16 | case <-timer.C: 17 | return input, nil 18 | case <-ctx.Done(): 19 | return lo.Empty[T](), ctx.Err() 20 | } 21 | } 22 | } 23 | 24 | func TestPicker_Basic(t *testing.T) { 25 | t.Parallel() 26 | picker, ctx := WithContext[int](context.Background()) 27 | picker.Go(sleepAndSend(ctx, 200, 2)) 28 | picker.Go(sleepAndSend(ctx, 100, 1)) 29 | 30 | number := picker.Wait() 31 | assert.NotNil(t, number) 32 | assert.Equal(t, number, 1) 33 | } 34 | 35 | func TestPicker_Timeout(t *testing.T) { 36 | t.Parallel() 37 | picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5) 38 | picker.Go(sleepAndSend(ctx, 100, 1)) 39 | 40 | number := picker.Wait() 41 | assert.Equal(t, number, lo.Empty[int]()) 42 | assert.NotNil(t, picker.Error()) 43 | } 44 | -------------------------------------------------------------------------------- /common/pool/buffer.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }} 9 | 10 | func GetBuffer() *bytes.Buffer { 11 | return bufferPool.Get().(*bytes.Buffer) 12 | } 13 | 14 | func PutBuffer(buf *bytes.Buffer) { 15 | buf.Reset() 16 | bufferPool.Put(buf) 17 | } 18 | -------------------------------------------------------------------------------- /common/pool/buffer_low_memory.go: -------------------------------------------------------------------------------- 1 | //go:build with_low_memory 2 | 3 | package pool 4 | 5 | const ( 6 | // io.Copy default buffer size is 32 KiB 7 | // but the maximum packet size of vmess/shadowsocks is about 16 KiB 8 | // so define a buffer of 20 KiB to reduce the memory of each TCP relay 9 | RelayBufferSize = 16 * 1024 10 | 11 | // RelayBufferSize uses 20KiB, but due to the allocator it will actually 12 | // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU 13 | // set to 9000, so the UDP Buffer size set to 16Kib 14 | UDPBufferSize = 8 * 1024 15 | ) 16 | -------------------------------------------------------------------------------- /common/pool/buffer_standard.go: -------------------------------------------------------------------------------- 1 | //go:build !with_low_memory 2 | 3 | package pool 4 | 5 | const ( 6 | // io.Copy default buffer size is 32 KiB 7 | // but the maximum packet size of vmess/shadowsocks is about 16 KiB 8 | // so define a buffer of 20 KiB to reduce the memory of each TCP relay 9 | RelayBufferSize = 20 * 1024 10 | 11 | // RelayBufferSize uses 20KiB, but due to the allocator it will actually 12 | // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU 13 | // set to 9000, so the UDP Buffer size set to 16Kib 14 | UDPBufferSize = 16 * 1024 15 | ) 16 | -------------------------------------------------------------------------------- /common/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | func Get(size int) []byte { 4 | return defaultAllocator.Get(size) 5 | } 6 | 7 | func Put(buf []byte) error { 8 | return defaultAllocator.Put(buf) 9 | } 10 | -------------------------------------------------------------------------------- /common/pool/sing.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import "github.com/sagernet/sing/common/buf" 4 | 5 | func init() { 6 | buf.DefaultAllocator = defaultAllocator 7 | } 8 | -------------------------------------------------------------------------------- /common/sockopt/reuse_common.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | func RawConnReuseaddr(rc syscall.RawConn) (err error) { 9 | var innerErr error 10 | err = rc.Control(func(fd uintptr) { 11 | innerErr = reuseControl(fd) 12 | }) 13 | 14 | if innerErr != nil { 15 | err = innerErr 16 | } 17 | return 18 | } 19 | 20 | func UDPReuseaddr(c net.PacketConn) error { 21 | if c, ok := c.(syscall.Conn); ok { 22 | rc, err := c.SyscallConn() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | return RawConnReuseaddr(rc) 28 | } 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /common/sockopt/reuse_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows 2 | 3 | package sockopt 4 | 5 | func reuseControl(fd uintptr) error { return nil } 6 | -------------------------------------------------------------------------------- /common/sockopt/reuse_unix.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 2 | 3 | package sockopt 4 | 5 | import ( 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | func reuseControl(fd uintptr) error { 10 | e1 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) 11 | e2 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 12 | 13 | if e1 != nil { 14 | return e1 15 | } 16 | 17 | if e2 != nil { 18 | return e2 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /common/sockopt/reuse_windows.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "golang.org/x/sys/windows" 5 | ) 6 | 7 | func reuseControl(fd uintptr) error { 8 | return windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) 9 | } 10 | -------------------------------------------------------------------------------- /common/utils/callback.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | 7 | list "github.com/bahlo/generic-list-go" 8 | ) 9 | 10 | type Callback[T any] struct { 11 | list list.List[func(T)] 12 | mutex sync.RWMutex 13 | } 14 | 15 | func NewCallback[T any]() *Callback[T] { 16 | return &Callback[T]{} 17 | } 18 | 19 | func (c *Callback[T]) Register(item func(T)) io.Closer { 20 | c.mutex.Lock() 21 | defer c.mutex.Unlock() 22 | element := c.list.PushBack(item) 23 | return &callbackCloser[T]{ 24 | element: element, 25 | callback: c, 26 | } 27 | } 28 | 29 | func (c *Callback[T]) Emit(item T) { 30 | c.mutex.RLock() 31 | defer c.mutex.RUnlock() 32 | for element := c.list.Front(); element != nil; element = element.Next() { 33 | go element.Value(item) 34 | } 35 | } 36 | 37 | type callbackCloser[T any] struct { 38 | element *list.Element[func(T)] 39 | callback *Callback[T] 40 | once sync.Once 41 | } 42 | 43 | func (c *callbackCloser[T]) Close() error { 44 | c.once.Do(func() { 45 | c.callback.mutex.Lock() 46 | defer c.callback.mutex.Unlock() 47 | c.callback.list.Remove(c.element) 48 | }) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /common/utils/global_id.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "hash/maphash" 5 | "unsafe" 6 | ) 7 | 8 | var globalSeed = maphash.MakeSeed() 9 | 10 | func GlobalID(material string) (id [8]byte) { 11 | *(*uint64)(unsafe.Pointer(&id[0])) = maphash.String(globalSeed, material) 12 | return 13 | } 14 | 15 | func MapHash(material string) uint64 { 16 | return maphash.String(globalSeed, material) 17 | } 18 | -------------------------------------------------------------------------------- /common/utils/hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "errors" 7 | ) 8 | 9 | // HashType warps hash array inside struct 10 | // someday can change to other hash algorithm simply 11 | type HashType struct { 12 | md5 [md5.Size]byte // MD5 13 | } 14 | 15 | func MakeHash(data []byte) HashType { 16 | return HashType{md5.Sum(data)} 17 | } 18 | 19 | func (h HashType) Equal(hash HashType) bool { 20 | return h.md5 == hash.md5 21 | } 22 | 23 | func (h HashType) Bytes() []byte { 24 | return h.md5[:] 25 | } 26 | 27 | func (h HashType) String() string { 28 | return hex.EncodeToString(h.Bytes()) 29 | } 30 | 31 | func (h HashType) MarshalText() ([]byte, error) { 32 | return []byte(h.String()), nil 33 | } 34 | 35 | func (h *HashType) UnmarshalText(data []byte) error { 36 | if hex.DecodedLen(len(data)) != md5.Size { 37 | return errors.New("invalid hash length") 38 | } 39 | _, err := hex.Decode(h.md5[:], data) 40 | return err 41 | } 42 | 43 | func (h HashType) MarshalBinary() ([]byte, error) { 44 | return h.md5[:], nil 45 | } 46 | 47 | func (h *HashType) UnmarshalBinary(data []byte) error { 48 | if len(data) != md5.Size { 49 | return errors.New("invalid hash length") 50 | } 51 | copy(h.md5[:], data) 52 | return nil 53 | } 54 | 55 | func (h HashType) Len() int { 56 | return len(h.md5) 57 | } 58 | 59 | func (h HashType) IsValid() bool { 60 | var zero HashType 61 | return h != zero 62 | } 63 | -------------------------------------------------------------------------------- /common/utils/manipulation.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/samber/lo" 4 | 5 | func EmptyOr[T comparable](v T, def T) T { 6 | ret, _ := lo.Coalesce(v, def) 7 | return ret 8 | } 9 | -------------------------------------------------------------------------------- /common/utils/must.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func MustOK[T any](result T, ok bool) T { 4 | if ok { 5 | return result 6 | } 7 | panic("operation failed") 8 | } 9 | -------------------------------------------------------------------------------- /common/utils/range.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | type Range[T constraints.Ordered] struct { 8 | start T 9 | end T 10 | } 11 | 12 | func NewRange[T constraints.Ordered](start, end T) Range[T] { 13 | if start > end { 14 | return Range[T]{ 15 | start: end, 16 | end: start, 17 | } 18 | } 19 | 20 | return Range[T]{ 21 | start: start, 22 | end: end, 23 | } 24 | } 25 | 26 | func (r Range[T]) Contains(t T) bool { 27 | return t >= r.start && t <= r.end 28 | } 29 | 30 | func (r Range[T]) LeftContains(t T) bool { 31 | return t >= r.start && t < r.end 32 | } 33 | 34 | func (r Range[T]) RightContains(t T) bool { 35 | return t > r.start && t <= r.end 36 | } 37 | 38 | func (r Range[T]) Start() T { 39 | return r.start 40 | } 41 | 42 | func (r Range[T]) End() T { 43 | return r.end 44 | } 45 | -------------------------------------------------------------------------------- /common/utils/slice.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | func Filter[T comparable](tSlice []T, filter func(t T) bool) []T { 10 | result := make([]T, 0) 11 | for _, t := range tSlice { 12 | if filter(t) { 13 | result = append(result, t) 14 | } 15 | } 16 | return result 17 | } 18 | 19 | func Map[T any, N any](arr []T, block func(it T) N) []N { 20 | if arr == nil { // keep nil 21 | return nil 22 | } 23 | retArr := make([]N, 0, len(arr)) 24 | for index := range arr { 25 | retArr = append(retArr, block(arr[index])) 26 | } 27 | return retArr 28 | } 29 | 30 | func ToStringSlice(value any) ([]string, error) { 31 | strArr := make([]string, 0) 32 | switch reflect.TypeOf(value).Kind() { 33 | case reflect.Slice, reflect.Array: 34 | origin := reflect.ValueOf(value) 35 | for i := 0; i < origin.Len(); i++ { 36 | item := fmt.Sprintf("%v", origin.Index(i)) 37 | strArr = append(strArr, item) 38 | } 39 | case reflect.String: 40 | strArr = append(strArr, fmt.Sprintf("%v", value)) 41 | default: 42 | return nil, errors.New("value format error, must be string or array") 43 | } 44 | return strArr, nil 45 | } 46 | -------------------------------------------------------------------------------- /common/utils/string_unsafe.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "unsafe" 4 | 5 | // ImmutableBytesFromString is equivalent to []byte(s), except that it uses the 6 | // same memory backing s instead of making a heap-allocated copy. This is only 7 | // valid if the returned slice is never mutated. 8 | func ImmutableBytesFromString(s string) []byte { 9 | b := unsafe.StringData(s) 10 | return unsafe.Slice(b, len(s)) 11 | } 12 | 13 | // StringFromImmutableBytes is equivalent to string(bs), except that it uses 14 | // the same memory backing bs instead of making a heap-allocated copy. This is 15 | // only valid if bs is never mutated after StringFromImmutableBytes returns. 16 | func StringFromImmutableBytes(bs []byte) string { 17 | if len(bs) == 0 { 18 | return "" 19 | } 20 | return unsafe.String(&bs[0], len(bs)) 21 | } 22 | -------------------------------------------------------------------------------- /common/utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func Reverse(s string) string { 4 | a := []rune(s) 5 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 6 | a[i], a[j] = a[j], a[i] 7 | } 8 | return string(a) 9 | } 10 | -------------------------------------------------------------------------------- /component/arp/arp_linux.go: -------------------------------------------------------------------------------- 1 | package arp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/sagernet/netlink" 8 | ) 9 | 10 | func neighMAC(n netlink.Neigh) string { 11 | return n.HardwareAddr.String() 12 | } 13 | 14 | func neighIP(n netlink.Neigh) net.IP { 15 | return n.IP 16 | } 17 | 18 | func GetARPTable() (map[string]string, error) { 19 | entries := make(map[string]string) 20 | 21 | links, err := netlink.LinkList() 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | for _, link := range links { 27 | attr := link.Attrs() 28 | neighs, err := netlink.NeighList(attr.Index, 0) 29 | if err != nil { 30 | fmt.Println(err) 31 | continue 32 | } 33 | for _, neigh := range neighs { 34 | ip := neighIP(neigh) 35 | mac := neighMAC(neigh) 36 | 37 | if IsReserved(ip) { 38 | continue 39 | } 40 | 41 | if ip.IsGlobalUnicast() { 42 | entries[ip.String()] = mac 43 | } 44 | } 45 | } 46 | return entries, nil 47 | } 48 | -------------------------------------------------------------------------------- /component/arp/arp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows 2 | 3 | package arp 4 | 5 | func GetARPTable() (map[string]string, error) { 6 | return nil, nil 7 | } 8 | -------------------------------------------------------------------------------- /component/arp/arp_windows.go: -------------------------------------------------------------------------------- 1 | package arp 2 | 3 | func GetARPTable() (map[string]string, error) { 4 | table, err := GetIpNetTable2() 5 | if err != nil { 6 | return nil, err 7 | } 8 | 9 | entries := make(map[string]string) 10 | for _, row := range table { 11 | entry := row.ToARPEntry() 12 | 13 | if IsReserved(entry.IP) { 14 | continue 15 | } 16 | 17 | if entry.IP.IsGlobalUnicast() { 18 | entries[entry.IP.String()] = entry.MAC.String() 19 | } 20 | } 21 | return entries, nil 22 | } 23 | -------------------------------------------------------------------------------- /component/arp/mib_ipnet_table2.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package arp 5 | 6 | const anySize = 1 << 16 7 | 8 | type MIBIpNetTable2 []MIBIpNetRow2 9 | 10 | type rawMIBIpNetTable2 struct { 11 | numEntries uint32 12 | padding uint32 13 | table [anySize]rawMIBIpNetRow2 14 | } 15 | 16 | func (r *rawMIBIpNetTable2) parse() MIBIpNetTable2 { 17 | t := make([]MIBIpNetRow2, r.numEntries) 18 | for i := 0; i < int(r.numEntries); i++ { 19 | t[i] = r.table[i].Parse() 20 | } 21 | return t 22 | } 23 | -------------------------------------------------------------------------------- /component/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | type Authenticator interface { 4 | Verify(user string, pass string) bool 5 | Users() []string 6 | } 7 | 8 | type AuthStore interface { 9 | Authenticator() Authenticator 10 | SetAuthenticator(Authenticator) 11 | } 12 | 13 | type AuthUser struct { 14 | User string 15 | Pass string 16 | } 17 | 18 | type inMemoryAuthenticator struct { 19 | storage map[string]string 20 | usernames []string 21 | } 22 | 23 | func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { 24 | realPass, ok := au.storage[user] 25 | return ok && realPass == pass 26 | } 27 | 28 | func (au *inMemoryAuthenticator) Users() []string { return au.usernames } 29 | 30 | func NewAuthenticator(users []AuthUser) Authenticator { 31 | if len(users) == 0 { 32 | return nil 33 | } 34 | au := &inMemoryAuthenticator{ 35 | storage: make(map[string]string), 36 | usernames: make([]string, 0, len(users)), 37 | } 38 | for _, user := range users { 39 | au.storage[user.User] = user.Pass 40 | au.usernames = append(au.usernames, user.User) 41 | } 42 | return au 43 | } 44 | -------------------------------------------------------------------------------- /component/ca/ca-certificates.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xishang0128/mihomo/0edaf093778d126f1ac7b65e4bcb0160f610cf79/component/ca/ca-certificates.crt -------------------------------------------------------------------------------- /component/ca/fix_windows.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/constant/features" 5 | ) 6 | 7 | func init() { 8 | // crypto/x509: certificate validation in Windows fails to validate IP in SAN 9 | // https://github.com/golang/go/issues/37176 10 | // As far as I can tell this is still the case on most older versions of Windows (but seems to be fixed in 10) 11 | if features.WindowsMajorVersion < 10 && len(_CaCertificates) > 0 { 12 | DisableSystemCa = true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /component/dhcp/conn.go: -------------------------------------------------------------------------------- 1 | package dhcp 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/netip" 7 | "runtime" 8 | 9 | "github.com/metacubex/mihomo/component/dialer" 10 | ) 11 | 12 | func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) { 13 | listenAddr := "0.0.0.0:68" 14 | if runtime.GOOS == "linux" || runtime.GOOS == "android" { 15 | listenAddr = "255.255.255.255:68" 16 | } 17 | 18 | options := []dialer.Option{ 19 | dialer.WithInterface(ifaceName), 20 | dialer.WithAddrReuse(true), 21 | } 22 | 23 | // fallback bind on windows, because syscall bind can not receive broadcast 24 | if runtime.GOOS == "windows" { 25 | options = append(options, dialer.WithFallbackBind(true)) 26 | } 27 | 28 | return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...) 29 | } 30 | -------------------------------------------------------------------------------- /component/dialer/bind_linux.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/netip" 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func bindControl(ifaceName string) controlFn { 13 | return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { 14 | addrPort, err := netip.ParseAddrPort(address) 15 | if err == nil && !addrPort.Addr().IsGlobalUnicast() { 16 | return 17 | } 18 | 19 | var innerErr error 20 | err = c.Control(func(fd uintptr) { 21 | innerErr = unix.BindToDevice(int(fd), ifaceName) 22 | }) 23 | 24 | if innerErr != nil { 25 | err = innerErr 26 | } 27 | 28 | return 29 | } 30 | } 31 | 32 | func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { 33 | addControlToDialer(dialer, bindControl(ifaceName)) 34 | 35 | return nil 36 | } 37 | 38 | func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) { 39 | addControlToListenConfig(lc, bindControl(ifaceName)) 40 | 41 | return address, nil 42 | } 43 | 44 | func ParseNetwork(network string, addr netip.Addr) string { 45 | return network 46 | } 47 | -------------------------------------------------------------------------------- /component/dialer/bind_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !windows 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | "net/netip" 8 | ) 9 | 10 | func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { 11 | return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination) 12 | } 13 | 14 | func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) { 15 | return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort) 16 | } 17 | 18 | func ParseNetwork(network string, addr netip.Addr) string { 19 | return fallbackParseNetwork(network, addr) 20 | } 21 | -------------------------------------------------------------------------------- /component/dialer/control.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "syscall" 7 | ) 8 | 9 | type controlFn = func(ctx context.Context, network, address string, c syscall.RawConn) error 10 | 11 | func addControlToListenConfig(lc *net.ListenConfig, fn controlFn) { 12 | llc := *lc 13 | lc.Control = func(network, address string, c syscall.RawConn) (err error) { 14 | switch { 15 | case llc.Control != nil: 16 | if err = llc.Control(network, address, c); err != nil { 17 | return 18 | } 19 | } 20 | return fn(context.Background(), network, address, c) 21 | } 22 | } 23 | 24 | func addControlToDialer(d *net.Dialer, fn controlFn) { 25 | ld := *d 26 | d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { 27 | switch { 28 | case ld.ControlContext != nil: 29 | if err = ld.ControlContext(ctx, network, address, c); err != nil { 30 | return 31 | } 32 | case ld.Control != nil: 33 | if err = ld.Control(network, address, c); err != nil { 34 | return 35 | } 36 | } 37 | return fn(ctx, network, address, c) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /component/dialer/error.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrorNoIpAddress = errors.New("no ip address") 9 | ErrorInvalidedNetworkStack = errors.New("invalided network stack") 10 | ) 11 | -------------------------------------------------------------------------------- /component/dialer/mark_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package dialer 4 | 5 | import ( 6 | "context" 7 | "net" 8 | "net/netip" 9 | "syscall" 10 | ) 11 | 12 | func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) { 13 | addControlToDialer(dialer, bindMarkToControl(mark)) 14 | } 15 | 16 | func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { 17 | addControlToListenConfig(lc, bindMarkToControl(mark)) 18 | } 19 | 20 | func bindMarkToControl(mark int) controlFn { 21 | return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { 22 | 23 | addrPort, err := netip.ParseAddrPort(address) 24 | if err == nil && !addrPort.Addr().IsGlobalUnicast() { 25 | return 26 | } 27 | 28 | var innerErr error 29 | err = c.Control(func(fd uintptr) { 30 | innerErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) 31 | }) 32 | if innerErr != nil { 33 | err = innerErr 34 | } 35 | return 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /component/dialer/mark_nonlinux.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | "net/netip" 8 | "sync" 9 | 10 | "github.com/metacubex/mihomo/log" 11 | ) 12 | 13 | var printMarkWarnOnce sync.Once 14 | 15 | func printMarkWarn() { 16 | printMarkWarnOnce.Do(func() { 17 | log.Warnln("Routing mark on socket is not supported on current platform") 18 | }) 19 | } 20 | 21 | func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) { 22 | printMarkWarn() 23 | } 24 | 25 | func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { 26 | printMarkWarn() 27 | } 28 | -------------------------------------------------------------------------------- /component/dialer/mptcp_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | const multipathTCPAvailable = false 10 | 11 | func setMultiPathTCP(dialer *net.Dialer) { 12 | } 13 | -------------------------------------------------------------------------------- /component/dialer/mptcp_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package dialer 4 | 5 | import "net" 6 | 7 | const multipathTCPAvailable = true 8 | 9 | func setMultiPathTCP(dialer *net.Dialer) { 10 | dialer.SetMultipathTCP(true) 11 | } 12 | -------------------------------------------------------------------------------- /component/dialer/reuse.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "syscall" 7 | 8 | "github.com/metacubex/mihomo/common/sockopt" 9 | ) 10 | 11 | func addrReuseToListenConfig(lc *net.ListenConfig) { 12 | addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { 13 | return sockopt.RawConnReuseaddr(c) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /component/dialer/socket_hook.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "syscall" 7 | ) 8 | 9 | // SocketControl 10 | // never change type traits because it's used in CMFA 11 | type SocketControl func(network, address string, conn syscall.RawConn) error 12 | 13 | // DefaultSocketHook 14 | // never change type traits because it's used in CMFA 15 | var DefaultSocketHook SocketControl 16 | 17 | func socketHookToToDialer(dialer *net.Dialer) { 18 | addControlToDialer(dialer, func(ctx context.Context, network, address string, c syscall.RawConn) error { 19 | return DefaultSocketHook(network, address, c) 20 | }) 21 | } 22 | 23 | func socketHookToListenConfig(lc *net.ListenConfig) { 24 | addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { 25 | return DefaultSocketHook(network, address, c) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /component/dialer/tfo_windows.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import "github.com/metacubex/mihomo/constant/features" 4 | 5 | func init() { 6 | // According to MSDN, this option is available since Windows 10, 1607 7 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx 8 | if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) { 9 | DisableTFO = true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /component/generater/cmd.go: -------------------------------------------------------------------------------- 1 | package generater 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/gofrs/uuid/v5" 8 | ) 9 | 10 | func Main(args []string) { 11 | if len(args) < 1 { 12 | panic("Using: generate uuid/reality-keypair/wg-keypair") 13 | } 14 | switch args[0] { 15 | case "uuid": 16 | newUUID, err := uuid.NewV4() 17 | if err != nil { 18 | panic(err) 19 | } 20 | fmt.Println(newUUID.String()) 21 | case "reality-keypair": 22 | privateKey, err := GeneratePrivateKey() 23 | if err != nil { 24 | panic(err) 25 | } 26 | publicKey := privateKey.PublicKey() 27 | fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:])) 28 | fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])) 29 | case "wg-keypair": 30 | privateKey, err := GeneratePrivateKey() 31 | if err != nil { 32 | panic(err) 33 | } 34 | fmt.Println("PrivateKey: " + privateKey.String()) 35 | fmt.Println("PublicKey: " + privateKey.PublicKey().String()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /component/geodata/attr.go: -------------------------------------------------------------------------------- 1 | package geodata 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/metacubex/mihomo/component/geodata/router" 7 | ) 8 | 9 | type AttributeList struct { 10 | matcher []BooleanMatcher 11 | } 12 | 13 | func (al *AttributeList) Match(domain *router.Domain) bool { 14 | for _, matcher := range al.matcher { 15 | if !matcher.Match(domain) { 16 | return false 17 | } 18 | } 19 | return true 20 | } 21 | 22 | func (al *AttributeList) IsEmpty() bool { 23 | return len(al.matcher) == 0 24 | } 25 | 26 | func (al *AttributeList) String() string { 27 | matcher := make([]string, len(al.matcher)) 28 | for i, match := range al.matcher { 29 | matcher[i] = string(match) 30 | } 31 | return strings.Join(matcher, ",") 32 | } 33 | 34 | func parseAttrs(attrs []string) *AttributeList { 35 | al := new(AttributeList) 36 | for _, attr := range attrs { 37 | trimmedAttr := strings.ToLower(strings.TrimSpace(attr)) 38 | if len(trimmedAttr) == 0 { 39 | continue 40 | } 41 | al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr)) 42 | } 43 | return al 44 | } 45 | 46 | type AttributeMatcher interface { 47 | Match(*router.Domain) bool 48 | } 49 | 50 | type BooleanMatcher string 51 | 52 | func (m BooleanMatcher) Match(domain *router.Domain) bool { 53 | for _, attr := range domain.Attribute { 54 | if strings.EqualFold(attr.GetKey(), string(m)) { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /component/geodata/geodata.go: -------------------------------------------------------------------------------- 1 | package geodata 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/metacubex/mihomo/component/geodata/router" 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | type loader struct { 11 | LoaderImplementation 12 | } 13 | 14 | func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) { 15 | return l.LoadSiteByPath(C.GeositeName, list) 16 | } 17 | 18 | func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) { 19 | return l.LoadIPByPath(C.GeoipName, country) 20 | } 21 | 22 | var loaders map[string]func() LoaderImplementation 23 | 24 | func RegisterGeoDataLoaderImplementationCreator(name string, loader func() LoaderImplementation) { 25 | if loaders == nil { 26 | loaders = map[string]func() LoaderImplementation{} 27 | } 28 | loaders[name] = loader 29 | } 30 | 31 | func getGeoDataLoaderImplementation(name string) (LoaderImplementation, error) { 32 | if geoLoader, ok := loaders[name]; ok { 33 | return geoLoader(), nil 34 | } 35 | return nil, fmt.Errorf("unable to locate GeoData loader %s", name) 36 | } 37 | 38 | func GetGeoDataLoader(name string) (Loader, error) { 39 | loadImpl, err := getGeoDataLoaderImplementation(name) 40 | if err == nil { 41 | return &loader{loadImpl}, nil 42 | } 43 | return nil, err 44 | } 45 | -------------------------------------------------------------------------------- /component/geodata/geodataproto.go: -------------------------------------------------------------------------------- 1 | package geodata 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/component/geodata/router" 5 | ) 6 | 7 | type LoaderImplementation interface { 8 | LoadSiteByPath(filename, list string) ([]*router.Domain, error) 9 | LoadSiteByBytes(geositeBytes []byte, list string) ([]*router.Domain, error) 10 | LoadIPByPath(filename, country string) ([]*router.CIDR, error) 11 | LoadIPByBytes(geoipBytes []byte, country string) ([]*router.CIDR, error) 12 | } 13 | 14 | type Loader interface { 15 | LoaderImplementation 16 | LoadGeoSite(list string) ([]*router.Domain, error) 17 | LoadGeoIP(country string) ([]*router.CIDR, error) 18 | } 19 | -------------------------------------------------------------------------------- /component/geodata/package_info.go: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/v2fly/v2ray-core/tree/master/infra/conf/geodata 2 | // License: MIT 3 | 4 | package geodata 5 | -------------------------------------------------------------------------------- /component/geodata/strmatcher/matchers.go: -------------------------------------------------------------------------------- 1 | package strmatcher 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | type fullMatcher string 9 | 10 | func (m fullMatcher) Match(s string) bool { 11 | return string(m) == s 12 | } 13 | 14 | func (m fullMatcher) String() string { 15 | return "full:" + string(m) 16 | } 17 | 18 | type substrMatcher string 19 | 20 | func (m substrMatcher) Match(s string) bool { 21 | return strings.Contains(s, string(m)) 22 | } 23 | 24 | func (m substrMatcher) String() string { 25 | return "keyword:" + string(m) 26 | } 27 | 28 | type domainMatcher string 29 | 30 | func (m domainMatcher) Match(s string) bool { 31 | pattern := string(m) 32 | if !strings.HasSuffix(s, pattern) { 33 | return false 34 | } 35 | return len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.' 36 | } 37 | 38 | func (m domainMatcher) String() string { 39 | return "domain:" + string(m) 40 | } 41 | 42 | type regexMatcher struct { 43 | pattern *regexp.Regexp 44 | } 45 | 46 | func (m *regexMatcher) Match(s string) bool { 47 | return m.pattern.MatchString(s) 48 | } 49 | 50 | func (m *regexMatcher) String() string { 51 | return "regexp:" + m.pattern.String() 52 | } 53 | -------------------------------------------------------------------------------- /component/geodata/strmatcher/package_info.go: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/v2fly/v2ray-core/tree/master/common/strmatcher 2 | // License: MIT 3 | 4 | package strmatcher 5 | -------------------------------------------------------------------------------- /component/keepalive/tcp_keepalive_go122.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.23 2 | 3 | package keepalive 4 | 5 | import ( 6 | "net" 7 | "time" 8 | ) 9 | 10 | type TCPConn interface { 11 | net.Conn 12 | SetKeepAlive(keepalive bool) error 13 | SetKeepAlivePeriod(d time.Duration) error 14 | } 15 | 16 | func tcpKeepAlive(tcp TCPConn) { 17 | if DisableKeepAlive() { 18 | _ = tcp.SetKeepAlive(false) 19 | } else { 20 | _ = tcp.SetKeepAlive(true) 21 | _ = tcp.SetKeepAlivePeriod(KeepAliveInterval()) 22 | } 23 | } 24 | 25 | func setNetDialer(dialer *net.Dialer) { 26 | if DisableKeepAlive() { 27 | dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled. 28 | } else { 29 | dialer.KeepAlive = KeepAliveInterval() 30 | } 31 | } 32 | 33 | func setNetListenConfig(lc *net.ListenConfig) { 34 | if DisableKeepAlive() { 35 | lc.KeepAlive = -1 // If negative, keep-alive probes are disabled. 36 | } else { 37 | lc.KeepAlive = KeepAliveInterval() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /component/keepalive/tcp_keepalive_go123_unix.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 && unix 2 | 3 | package keepalive 4 | 5 | func SupportTCPKeepAliveIdle() bool { 6 | return true 7 | } 8 | 9 | func SupportTCPKeepAliveInterval() bool { 10 | return true 11 | } 12 | 13 | func SupportTCPKeepAliveCount() bool { 14 | return true 15 | } 16 | -------------------------------------------------------------------------------- /component/nat/proxy.go: -------------------------------------------------------------------------------- 1 | package nat 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metacubex/mihomo/common/atomic" 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | type writeBackProxy struct { 11 | wb atomic.TypedValue[C.WriteBack] 12 | } 13 | 14 | func (w *writeBackProxy) WriteBack(b []byte, addr net.Addr) (n int, err error) { 15 | return w.wb.Load().WriteBack(b, addr) 16 | } 17 | 18 | func (w *writeBackProxy) UpdateWriteBack(wb C.WriteBack) { 19 | w.wb.Store(wb) 20 | } 21 | 22 | func NewWriteBackProxy(wb C.WriteBack) C.WriteBackProxy { 23 | w := &writeBackProxy{} 24 | w.UpdateWriteBack(wb) 25 | return w 26 | } 27 | -------------------------------------------------------------------------------- /component/power/event.go: -------------------------------------------------------------------------------- 1 | package power 2 | 3 | type Type uint8 4 | 5 | const ( 6 | SUSPEND Type = iota 7 | RESUME 8 | RESUMEAUTOMATIC // Because the user is not present, most applications should do nothing. 9 | ) 10 | 11 | func (t Type) String() string { 12 | switch t { 13 | case SUSPEND: 14 | return "SUSPEND" 15 | case RESUME: 16 | return "RESUME" 17 | case RESUMEAUTOMATIC: 18 | return "RESUMEAUTOMATIC" 19 | default: 20 | return "" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /component/power/event_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package power 4 | 5 | import "errors" 6 | 7 | func NewEventListener(cb func(Type)) (func(), error) { 8 | return nil, errors.New("not support on this platform") 9 | } 10 | -------------------------------------------------------------------------------- /component/process/find_process_mode.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | FindProcessAlways = "always" 11 | FindProcessStrict = "strict" 12 | FindProcessOff = "off" 13 | ) 14 | 15 | var ( 16 | validModes = map[string]struct{}{ 17 | FindProcessAlways: {}, 18 | FindProcessOff: {}, 19 | FindProcessStrict: {}, 20 | } 21 | ) 22 | 23 | type FindProcessMode string 24 | 25 | func (m FindProcessMode) Always() bool { 26 | return m == FindProcessAlways 27 | } 28 | 29 | func (m FindProcessMode) Off() bool { 30 | return m == FindProcessOff 31 | } 32 | 33 | func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error { 34 | var tp string 35 | if err := unmarshal(&tp); err != nil { 36 | return err 37 | } 38 | return m.Set(tp) 39 | } 40 | 41 | func (m *FindProcessMode) UnmarshalJSON(data []byte) error { 42 | var tp string 43 | if err := json.Unmarshal(data, &tp); err != nil { 44 | return err 45 | } 46 | return m.Set(tp) 47 | } 48 | 49 | func (m *FindProcessMode) Set(value string) error { 50 | mode := strings.ToLower(value) 51 | _, exist := validModes[mode] 52 | if !exist { 53 | return errors.New("invalid find process mode") 54 | } 55 | *m = FindProcessMode(mode) 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /component/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "errors" 5 | "net/netip" 6 | 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | var ( 11 | ErrInvalidNetwork = errors.New("invalid network") 12 | ErrPlatformNotSupport = errors.New("not support on this platform") 13 | ErrNotFound = errors.New("process not found") 14 | ) 15 | 16 | const ( 17 | TCP = "tcp" 18 | UDP = "udp" 19 | ) 20 | 21 | func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) { 22 | return findProcessName(network, srcIP, srcPort) 23 | } 24 | 25 | // PackageNameResolver 26 | // never change type traits because it's used in CMFA 27 | type PackageNameResolver func(metadata *C.Metadata) (string, error) 28 | 29 | // DefaultPackageNameResolver 30 | // never change type traits because it's used in CMFA 31 | var DefaultPackageNameResolver PackageNameResolver 32 | 33 | func FindPackageName(metadata *C.Metadata) (string, error) { 34 | if resolver := DefaultPackageNameResolver; resolver != nil { 35 | return resolver(metadata) 36 | } 37 | return "", ErrPlatformNotSupport 38 | } 39 | -------------------------------------------------------------------------------- /component/process/process_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !windows && (!freebsd || !amd64) 2 | 3 | package process 4 | 5 | import "net/netip" 6 | 7 | func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { 8 | return 0, "", ErrPlatformNotSupport 9 | } 10 | 11 | func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { 12 | return 0, 0, ErrPlatformNotSupport 13 | } 14 | -------------------------------------------------------------------------------- /component/profile/cachefile/etag.go: -------------------------------------------------------------------------------- 1 | package cachefile 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/metacubex/mihomo/common/utils" 7 | "github.com/metacubex/mihomo/log" 8 | 9 | "github.com/metacubex/bbolt" 10 | "github.com/vmihailenco/msgpack/v5" 11 | ) 12 | 13 | type EtagWithHash struct { 14 | Hash utils.HashType 15 | ETag string 16 | Time time.Time 17 | } 18 | 19 | func (c *CacheFile) SetETagWithHash(url string, etagWithHash EtagWithHash) { 20 | if c.DB == nil { 21 | return 22 | } 23 | 24 | data, err := msgpack.Marshal(etagWithHash) 25 | if err != nil { 26 | return // maybe panic is better 27 | } 28 | 29 | err = c.DB.Batch(func(t *bbolt.Tx) error { 30 | bucket, err := t.CreateBucketIfNotExists(bucketETag) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return bucket.Put([]byte(url), data) 36 | }) 37 | if err != nil { 38 | log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) 39 | return 40 | } 41 | } 42 | func (c *CacheFile) GetETagWithHash(key string) (etagWithHash EtagWithHash) { 43 | if c.DB == nil { 44 | return 45 | } 46 | c.DB.View(func(t *bbolt.Tx) error { 47 | if bucket := t.Bucket(bucketETag); bucket != nil { 48 | if v := bucket.Get([]byte(key)); v != nil { 49 | if err := msgpack.Unmarshal(v, &etagWithHash); err != nil { 50 | return err 51 | } 52 | } 53 | } 54 | return nil 55 | }) 56 | 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /component/profile/cachefile/subscriptioninfo.go: -------------------------------------------------------------------------------- 1 | package cachefile 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/log" 5 | 6 | "github.com/metacubex/bbolt" 7 | ) 8 | 9 | func (c *CacheFile) SetSubscriptionInfo(name string, userInfo string) { 10 | if c.DB == nil { 11 | return 12 | } 13 | 14 | err := c.DB.Batch(func(t *bbolt.Tx) error { 15 | bucket, err := t.CreateBucketIfNotExists(bucketSubscriptionInfo) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | return bucket.Put([]byte(name), []byte(userInfo)) 21 | }) 22 | if err != nil { 23 | log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error()) 24 | return 25 | } 26 | } 27 | func (c *CacheFile) GetSubscriptionInfo(name string) (userInfo string) { 28 | if c.DB == nil { 29 | return 30 | } 31 | c.DB.View(func(t *bbolt.Tx) error { 32 | if bucket := t.Bucket(bucketSubscriptionInfo); bucket != nil { 33 | if v := bucket.Get([]byte(name)); v != nil { 34 | userInfo = string(v) 35 | } 36 | } 37 | return nil 38 | }) 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /component/profile/profile.go: -------------------------------------------------------------------------------- 1 | package profile 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/common/atomic" 5 | ) 6 | 7 | // StoreSelected is a global switch for storing selected proxy to cache 8 | var StoreSelected = atomic.NewBool(true) 9 | -------------------------------------------------------------------------------- /component/proxydialer/slowdown.go: -------------------------------------------------------------------------------- 1 | package proxydialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/netip" 7 | 8 | "github.com/metacubex/mihomo/component/slowdown" 9 | C "github.com/metacubex/mihomo/constant" 10 | ) 11 | 12 | type SlowDownDialer struct { 13 | C.Dialer 14 | Slowdown *slowdown.SlowDown 15 | } 16 | 17 | func (d SlowDownDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 18 | return slowdown.Do(d.Slowdown, ctx, func() (net.Conn, error) { 19 | return d.Dialer.DialContext(ctx, network, address) 20 | }) 21 | } 22 | 23 | func (d SlowDownDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { 24 | return slowdown.Do(d.Slowdown, ctx, func() (net.PacketConn, error) { 25 | return d.Dialer.ListenPacket(ctx, network, address, rAddrPort) 26 | }) 27 | } 28 | 29 | func NewSlowDownDialer(d C.Dialer, sd *slowdown.SlowDown) SlowDownDialer { 30 | return SlowDownDialer{ 31 | Dialer: d, 32 | Slowdown: sd, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /component/proxydialer/slowdown_sing.go: -------------------------------------------------------------------------------- 1 | package proxydialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/metacubex/mihomo/component/slowdown" 8 | M "github.com/sagernet/sing/common/metadata" 9 | ) 10 | 11 | type SlowDownSingDialer struct { 12 | SingDialer 13 | Slowdown *slowdown.SlowDown 14 | } 15 | 16 | func (d SlowDownSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 17 | return slowdown.Do(d.Slowdown, ctx, func() (net.Conn, error) { 18 | return d.SingDialer.DialContext(ctx, network, destination) 19 | }) 20 | } 21 | 22 | func (d SlowDownSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 23 | return slowdown.Do(d.Slowdown, ctx, func() (net.PacketConn, error) { 24 | return d.SingDialer.ListenPacket(ctx, destination) 25 | }) 26 | } 27 | 28 | func NewSlowDownSingDialer(d SingDialer, sd *slowdown.SlowDown) SlowDownSingDialer { 29 | return SlowDownSingDialer{ 30 | SingDialer: d, 31 | Slowdown: sd, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /component/resolver/hosts/hosts_windows.go: -------------------------------------------------------------------------------- 1 | package hosts 2 | 3 | // this file copy and modify from golang's std net/hook_windows.go 4 | 5 | import ( 6 | "golang.org/x/sys/windows" 7 | ) 8 | 9 | func init() { 10 | if dir, err := windows.GetSystemDirectory(); err == nil { 11 | hostsFilePath = dir + "/Drivers/etc/hosts" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /component/resolver/ip4p.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | "strconv" 7 | 8 | "github.com/metacubex/mihomo/log" 9 | ) 10 | 11 | var ( 12 | ip4PEnable bool 13 | ) 14 | 15 | func GetIP4PEnable() bool { 16 | return ip4PEnable 17 | } 18 | 19 | func SetIP4PEnable(enableIP4PConvert bool) { 20 | ip4PEnable = enableIP4PConvert 21 | } 22 | 23 | // kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go 24 | 25 | func LookupIP4P(addr netip.Addr, port string) (netip.Addr, string) { 26 | if ip4PEnable { 27 | ip := addr.AsSlice() 28 | if ip[0] == 0x20 && ip[1] == 0x01 && 29 | ip[2] == 0x00 && ip[3] == 0x00 { 30 | addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]}) 31 | port = strconv.Itoa(int(ip[10])<<8 + int(ip[11])) 32 | log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port)) 33 | return addr, port 34 | } 35 | } 36 | return addr, port 37 | } 38 | -------------------------------------------------------------------------------- /component/resolver/local.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "context" 5 | 6 | D "github.com/miekg/dns" 7 | ) 8 | 9 | var DefaultLocalServer LocalServer 10 | 11 | type LocalServer interface { 12 | ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) 13 | } 14 | 15 | // ServeMsg with a dns.Msg, return resolve dns.Msg 16 | func ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) { 17 | if server := DefaultLocalServer; server != nil { 18 | return server.ServeMsg(ctx, msg) 19 | } 20 | 21 | return nil, ErrIPNotFound 22 | } 23 | -------------------------------------------------------------------------------- /component/resolver/system.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import "sync" 4 | 5 | var blacklist struct { 6 | Map map[string]struct{} 7 | Mutex sync.Mutex 8 | } 9 | 10 | func init() { 11 | blacklist.Map = make(map[string]struct{}) 12 | } 13 | 14 | func AddSystemDnsBlacklist(names ...string) { 15 | blacklist.Mutex.Lock() 16 | defer blacklist.Mutex.Unlock() 17 | for _, name := range names { 18 | blacklist.Map[name] = struct{}{} 19 | } 20 | } 21 | 22 | func RemoveSystemDnsBlacklist(names ...string) { 23 | blacklist.Mutex.Lock() 24 | defer blacklist.Mutex.Unlock() 25 | for _, name := range names { 26 | delete(blacklist.Map, name) 27 | } 28 | } 29 | 30 | func IsSystemDnsBlacklisted(names ...string) bool { 31 | blacklist.Mutex.Lock() 32 | defer blacklist.Mutex.Unlock() 33 | for _, name := range names { 34 | if _, ok := blacklist.Map[name]; ok { 35 | return true 36 | } 37 | } 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /component/slowdown/slowdown.go: -------------------------------------------------------------------------------- 1 | package slowdown 2 | 3 | import ( 4 | "context" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type SlowDown struct { 10 | errTimes atomic.Int64 11 | backoff Backoff 12 | } 13 | 14 | func (s *SlowDown) Wait(ctx context.Context) (err error) { 15 | timer := time.NewTimer(s.backoff.Duration()) 16 | defer timer.Stop() 17 | select { 18 | case <-timer.C: 19 | case <-ctx.Done(): 20 | err = ctx.Err() 21 | } 22 | return 23 | } 24 | 25 | func New() *SlowDown { 26 | return &SlowDown{ 27 | backoff: Backoff{ 28 | Min: 10 * time.Millisecond, 29 | Max: 1 * time.Second, 30 | Factor: 2, 31 | Jitter: true, 32 | }, 33 | } 34 | } 35 | 36 | func Do[T any](s *SlowDown, ctx context.Context, fn func() (T, error)) (t T, err error) { 37 | if s.errTimes.Load() > 10 { 38 | err = s.Wait(ctx) 39 | if err != nil { 40 | return 41 | } 42 | } 43 | t, err = fn() 44 | if err != nil { 45 | s.errTimes.Add(1) 46 | return 47 | } 48 | s.errTimes.Store(0) 49 | s.backoff.Reset() 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /component/sniffer/base_sniffer.go: -------------------------------------------------------------------------------- 1 | package sniffer 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/metacubex/mihomo/common/utils" 7 | "github.com/metacubex/mihomo/constant" 8 | "github.com/metacubex/mihomo/constant/sniffer" 9 | ) 10 | 11 | type SnifferConfig struct { 12 | OverrideDest bool 13 | Ports utils.IntRanges[uint16] 14 | } 15 | 16 | type BaseSniffer struct { 17 | ports utils.IntRanges[uint16] 18 | supportNetworkType constant.NetWork 19 | } 20 | 21 | // Protocol implements sniffer.Sniffer 22 | func (*BaseSniffer) Protocol() string { 23 | return "unknown" 24 | } 25 | 26 | // SniffData implements sniffer.Sniffer 27 | func (*BaseSniffer) SniffData(bytes []byte) (string, error) { 28 | return "", errors.New("TODO") 29 | } 30 | 31 | // SupportNetwork implements sniffer.Sniffer 32 | func (bs *BaseSniffer) SupportNetwork() constant.NetWork { 33 | return bs.supportNetworkType 34 | } 35 | 36 | // SupportPort implements sniffer.Sniffer 37 | func (bs *BaseSniffer) SupportPort(port uint16) bool { 38 | return bs.ports.Check(port) 39 | } 40 | 41 | func NewBaseSniffer(ports utils.IntRanges[uint16], networkType constant.NetWork) *BaseSniffer { 42 | return &BaseSniffer{ 43 | ports: ports, 44 | supportNetworkType: networkType, 45 | } 46 | } 47 | 48 | var _ sniffer.Sniffer = (*BaseSniffer)(nil) 49 | -------------------------------------------------------------------------------- /component/trie/ipcidr_node.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrorOverMaxValue = errors.New("the value don't over max value") 7 | ) 8 | 9 | type IpCidrNode struct { 10 | Mark bool 11 | child map[uint32]*IpCidrNode 12 | maxValue uint32 13 | } 14 | 15 | func NewIpCidrNode(mark bool, maxValue uint32) *IpCidrNode { 16 | ipCidrNode := &IpCidrNode{ 17 | Mark: mark, 18 | child: map[uint32]*IpCidrNode{}, 19 | maxValue: maxValue, 20 | } 21 | 22 | return ipCidrNode 23 | } 24 | 25 | func (n *IpCidrNode) addChild(value uint32) error { 26 | if value > n.maxValue { 27 | return ErrorOverMaxValue 28 | } 29 | 30 | n.child[value] = NewIpCidrNode(false, n.maxValue) 31 | return nil 32 | } 33 | 34 | func (n *IpCidrNode) hasChild(value uint32) bool { 35 | return n.getChild(value) != nil 36 | } 37 | 38 | func (n *IpCidrNode) getChild(value uint32) *IpCidrNode { 39 | if value <= n.maxValue { 40 | return n.child[value] 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | C "github.com/metacubex/mihomo/constant" 8 | "github.com/metacubex/mihomo/log" 9 | ) 10 | 11 | // Init prepare necessary files 12 | func Init(dir string) error { 13 | // initial homedir 14 | if _, err := os.Stat(dir); os.IsNotExist(err) { 15 | if err := os.MkdirAll(dir, 0o777); err != nil { 16 | return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) 17 | } 18 | } 19 | 20 | // initial config.yaml 21 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 22 | log.Infoln("Can't find config, create a initial config file") 23 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644) 24 | if err != nil { 25 | return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) 26 | } 27 | f.Write([]byte(`mixed-port: 7890`)) 28 | f.Close() 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /constant/context.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "net" 5 | 6 | N "github.com/metacubex/mihomo/common/net" 7 | 8 | "github.com/gofrs/uuid/v5" 9 | ) 10 | 11 | type PlainContext interface { 12 | ID() uuid.UUID 13 | } 14 | 15 | type ConnContext interface { 16 | PlainContext 17 | Metadata() *Metadata 18 | Conn() *N.BufferedConn 19 | } 20 | 21 | type PacketConnContext interface { 22 | PlainContext 23 | Metadata() *Metadata 24 | PacketConn() net.PacketConn 25 | } 26 | -------------------------------------------------------------------------------- /constant/features/cmfa.go: -------------------------------------------------------------------------------- 1 | //go:build cmfa 2 | 3 | package features 4 | 5 | const CMFA = true 6 | -------------------------------------------------------------------------------- /constant/features/cmfa_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !cmfa 2 | 3 | package features 4 | 5 | const CMFA = false 6 | -------------------------------------------------------------------------------- /constant/features/low_memory.go: -------------------------------------------------------------------------------- 1 | //go:build with_low_memory 2 | 3 | package features 4 | 5 | const WithLowMemory = true 6 | -------------------------------------------------------------------------------- /constant/features/low_memory_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_low_memory 2 | 3 | package features 4 | 5 | const WithLowMemory = false 6 | -------------------------------------------------------------------------------- /constant/features/no_fake_tcp.go: -------------------------------------------------------------------------------- 1 | //go:build no_fake_tcp 2 | 3 | package features 4 | 5 | const NoFakeTCP = true 6 | -------------------------------------------------------------------------------- /constant/features/no_fake_tcp_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !no_fake_tcp 2 | 3 | package features 4 | 5 | const NoFakeTCP = false 6 | -------------------------------------------------------------------------------- /constant/features/tags.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | func Tags() (tags []string) { 4 | if CMFA { 5 | tags = append(tags, "cmfa") 6 | } 7 | if WithLowMemory { 8 | tags = append(tags, "with_low_memory") 9 | } 10 | if NoFakeTCP { 11 | tags = append(tags, "no_fake_tcp") 12 | } 13 | if WithGVisor { 14 | tags = append(tags, "with_gvisor") 15 | } 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /constant/features/version.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | var WindowsMajorVersion uint32 4 | var WindowsMinorVersion uint32 5 | var WindowsBuildNumber uint32 6 | -------------------------------------------------------------------------------- /constant/features/version_windows.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import "golang.org/x/sys/windows" 4 | 5 | func init() { 6 | version := windows.RtlGetVersion() 7 | WindowsMajorVersion = version.MajorVersion 8 | WindowsMinorVersion = version.MinorVersion 9 | WindowsBuildNumber = version.BuildNumber 10 | } 11 | -------------------------------------------------------------------------------- /constant/features/with_gvisor.go: -------------------------------------------------------------------------------- 1 | //go:build with_gvisor 2 | 3 | package features 4 | 5 | const WithGVisor = true 6 | -------------------------------------------------------------------------------- /constant/features/with_gvisor_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_gvisor 2 | 3 | package features 4 | 5 | const WithGVisor = false 6 | -------------------------------------------------------------------------------- /constant/listener.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "net" 4 | 5 | type Listener interface { 6 | RawAddress() string 7 | Address() string 8 | Close() error 9 | } 10 | 11 | type MultiAddrListener interface { 12 | Close() error 13 | Config() string 14 | AddrList() (addrList []net.Addr) 15 | } 16 | 17 | type InboundListener interface { 18 | Name() string 19 | Listen(tunnel Tunnel) error 20 | Close() error 21 | Address() string 22 | RawAddress() string 23 | Config() InboundConfig 24 | } 25 | 26 | type InboundConfig interface { 27 | Name() string 28 | Equal(config InboundConfig) bool 29 | } 30 | -------------------------------------------------------------------------------- /constant/matcher.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "net/netip" 4 | 5 | type DomainMatcher interface { 6 | MatchDomain(domain string) bool 7 | } 8 | 9 | type IpMatcher interface { 10 | MatchIp(ip netip.Addr) bool 11 | } 12 | -------------------------------------------------------------------------------- /constant/sniffer/sniffer.go: -------------------------------------------------------------------------------- 1 | package sniffer 2 | 3 | import "github.com/metacubex/mihomo/constant" 4 | 5 | type Sniffer interface { 6 | SupportNetwork() constant.NetWork 7 | // SniffData must not change input bytes 8 | SniffData(bytes []byte) (string, error) 9 | Protocol() string 10 | SupportPort(port uint16) bool 11 | } 12 | 13 | type MultiPacketSniffer interface { 14 | WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender 15 | } 16 | 17 | const ( 18 | TLS Type = iota 19 | HTTP 20 | QUIC 21 | ) 22 | 23 | var ( 24 | List = []Type{TLS, HTTP, QUIC} 25 | ) 26 | 27 | type Type int 28 | 29 | func (rt Type) String() string { 30 | switch rt { 31 | case TLS: 32 | return "TLS" 33 | case HTTP: 34 | return "HTTP" 35 | case QUIC: 36 | return "QUIC" 37 | default: 38 | return "Unknown" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /constant/tunnel.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "net" 4 | 5 | type Tunnel interface { 6 | // HandleTCPConn will handle a tcp connection blocking 7 | HandleTCPConn(conn net.Conn, metadata *Metadata) 8 | // HandleUDPPacket will handle a udp packet nonblocking 9 | HandleUDPPacket(packet UDPPacket, metadata *Metadata) 10 | // NatTable return nat table 11 | NatTable() NatTable 12 | } 13 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Meta = true 5 | Version = "1.10.0" 6 | BuildTime = "unknown time" 7 | MihomoName = "mihomo" 8 | ) 9 | -------------------------------------------------------------------------------- /context/conn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/common/utils" 5 | "net" 6 | 7 | N "github.com/metacubex/mihomo/common/net" 8 | C "github.com/metacubex/mihomo/constant" 9 | 10 | "github.com/gofrs/uuid/v5" 11 | ) 12 | 13 | type ConnContext struct { 14 | id uuid.UUID 15 | metadata *C.Metadata 16 | conn *N.BufferedConn 17 | } 18 | 19 | func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { 20 | return &ConnContext{ 21 | id: utils.NewUUIDV4(), 22 | metadata: metadata, 23 | conn: N.NewBufferedConn(conn), 24 | } 25 | } 26 | 27 | // ID implement C.ConnContext ID 28 | func (c *ConnContext) ID() uuid.UUID { 29 | return c.id 30 | } 31 | 32 | // Metadata implement C.ConnContext Metadata 33 | func (c *ConnContext) Metadata() *C.Metadata { 34 | return c.metadata 35 | } 36 | 37 | // Conn implement C.ConnContext Conn 38 | func (c *ConnContext) Conn() *N.BufferedConn { 39 | return c.conn 40 | } 41 | -------------------------------------------------------------------------------- /context/dns.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "context" 5 | "github.com/metacubex/mihomo/common/utils" 6 | 7 | "github.com/gofrs/uuid/v5" 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | const ( 12 | DNSTypeHost = "host" 13 | DNSTypeFakeIP = "fakeip" 14 | DNSTypeRaw = "raw" 15 | ) 16 | 17 | type DNSContext struct { 18 | context.Context 19 | 20 | id uuid.UUID 21 | msg *dns.Msg 22 | tp string 23 | } 24 | 25 | func NewDNSContext(ctx context.Context, msg *dns.Msg) *DNSContext { 26 | return &DNSContext{ 27 | Context: ctx, 28 | 29 | id: utils.NewUUIDV4(), 30 | msg: msg, 31 | } 32 | } 33 | 34 | // ID implement C.PlainContext ID 35 | func (c *DNSContext) ID() uuid.UUID { 36 | return c.id 37 | } 38 | 39 | // SetType set type of response 40 | func (c *DNSContext) SetType(tp string) { 41 | c.tp = tp 42 | } 43 | 44 | // Type return type of response 45 | func (c *DNSContext) Type() string { 46 | return c.tp 47 | } 48 | -------------------------------------------------------------------------------- /context/packetconn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metacubex/mihomo/common/utils" 7 | C "github.com/metacubex/mihomo/constant" 8 | 9 | "github.com/gofrs/uuid/v5" 10 | ) 11 | 12 | type PacketConnContext struct { 13 | id uuid.UUID 14 | metadata *C.Metadata 15 | packetConn net.PacketConn 16 | } 17 | 18 | func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext { 19 | return &PacketConnContext{ 20 | id: utils.NewUUIDV4(), 21 | metadata: metadata, 22 | } 23 | } 24 | 25 | // ID implement C.PacketConnContext ID 26 | func (pc *PacketConnContext) ID() uuid.UUID { 27 | return pc.id 28 | } 29 | 30 | // Metadata implement C.PacketConnContext Metadata 31 | func (pc *PacketConnContext) Metadata() *C.Metadata { 32 | return pc.metadata 33 | } 34 | 35 | // PacketConn implement C.PacketConnContext PacketConn 36 | func (pc *PacketConnContext) PacketConn() net.PacketConn { 37 | return pc.packetConn 38 | } 39 | 40 | // InjectPacketConn injectPacketConn manually 41 | func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) { 42 | pc.packetConn = pconn 43 | } 44 | -------------------------------------------------------------------------------- /dns/dialer.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // export functions from tunnel module 4 | 5 | import "github.com/metacubex/mihomo/tunnel" 6 | 7 | const RespectRules = tunnel.DnsRespectRules 8 | 9 | type dnsDialer = tunnel.DNSDialer 10 | 11 | var newDNSDialer = tunnel.NewDNSDialer 12 | -------------------------------------------------------------------------------- /dns/edns0_subnet.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net/netip" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | func setEdns0Subnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) bool { 10 | var ( 11 | optRecord *dns.OPT 12 | subnetOption *dns.EDNS0_SUBNET 13 | ) 14 | findExists: 15 | for _, record := range message.Extra { 16 | var isOPTRecord bool 17 | if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord { 18 | for _, option := range optRecord.Option { 19 | var isEDNS0Subnet bool 20 | if subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET); isEDNS0Subnet { 21 | if !override { 22 | return false 23 | } 24 | break findExists 25 | } 26 | } 27 | } 28 | } 29 | if optRecord == nil { 30 | optRecord = &dns.OPT{ 31 | Hdr: dns.RR_Header{ 32 | Name: ".", 33 | Rrtype: dns.TypeOPT, 34 | }, 35 | } 36 | message.Extra = append(message.Extra, optRecord) 37 | } 38 | if subnetOption == nil { 39 | subnetOption = new(dns.EDNS0_SUBNET) 40 | optRecord.Option = append(optRecord.Option, subnetOption) 41 | } 42 | subnetOption.Code = dns.EDNS0SUBNET 43 | if clientSubnet.Addr().Is4() { 44 | subnetOption.Family = 1 45 | } else { 46 | subnetOption.Family = 2 47 | } 48 | subnetOption.SourceNetmask = uint8(clientSubnet.Bits()) 49 | subnetOption.Address = clientSubnet.Addr().AsSlice() 50 | return true 51 | } 52 | -------------------------------------------------------------------------------- /dns/local.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | 6 | D "github.com/miekg/dns" 7 | ) 8 | 9 | type LocalServer struct { 10 | handler handler 11 | } 12 | 13 | // ServeMsg implement resolver.LocalServer ResolveMsg 14 | func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) { 15 | return handlerWithContext(ctx, s.handler, msg) 16 | } 17 | 18 | func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer { 19 | return &LocalServer{handler: NewHandler(resolver, mapper)} 20 | } 21 | -------------------------------------------------------------------------------- /dns/patch_android.go: -------------------------------------------------------------------------------- 1 | //go:build android && cmfa 2 | 3 | package dns 4 | 5 | import ( 6 | "github.com/metacubex/mihomo/component/resolver" 7 | ) 8 | 9 | var systemResolver []dnsClient 10 | 11 | func FlushCacheWithDefaultResolver() { 12 | if r := resolver.DefaultResolver; r != nil { 13 | r.ClearCache() 14 | } 15 | if r := resolver.SystemResolver; r != nil { 16 | r.ClearCache() 17 | } 18 | resolver.ResetConnection() 19 | } 20 | 21 | func UpdateSystemDNS(addr []string) { 22 | if len(addr) == 0 { 23 | systemResolver = nil 24 | } 25 | 26 | ns := make([]NameServer, 0, len(addr)) 27 | for _, d := range addr { 28 | ns = append(ns, NameServer{Addr: d}) 29 | } 30 | 31 | systemResolver = transform(ns, nil) 32 | } 33 | 34 | func (c *systemClient) getDnsClients() ([]dnsClient, error) { 35 | return systemResolver, nil 36 | } 37 | 38 | func (c *systemClient) ResetConnection() { 39 | for _, r := range systemResolver { 40 | r.ResetConnection() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dns/policy.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/component/trie" 5 | C "github.com/metacubex/mihomo/constant" 6 | ) 7 | 8 | type dnsPolicy interface { 9 | Match(domain string) []dnsClient 10 | } 11 | 12 | type domainTriePolicy struct { 13 | *trie.DomainTrie[[]dnsClient] 14 | } 15 | 16 | func (p domainTriePolicy) Match(domain string) []dnsClient { 17 | record := p.DomainTrie.Search(domain) 18 | if record != nil { 19 | return record.Data() 20 | } 21 | return nil 22 | } 23 | 24 | type domainMatcherPolicy struct { 25 | matcher C.DomainMatcher 26 | dnsClients []dnsClient 27 | } 28 | 29 | func (p domainMatcherPolicy) Match(domain string) []dnsClient { 30 | if p.matcher.MatchDomain(domain) { 31 | return p.dnsClients 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /dns/rcode.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | D "github.com/miekg/dns" 8 | ) 9 | 10 | func newRCodeClient(addr string) rcodeClient { 11 | var rcode int 12 | switch addr { 13 | case "success": 14 | rcode = D.RcodeSuccess 15 | case "format_error": 16 | rcode = D.RcodeFormatError 17 | case "server_failure": 18 | rcode = D.RcodeServerFailure 19 | case "name_error": 20 | rcode = D.RcodeNameError 21 | case "not_implemented": 22 | rcode = D.RcodeNotImplemented 23 | case "refused": 24 | rcode = D.RcodeRefused 25 | default: 26 | panic(fmt.Errorf("unsupported RCode type: %s", addr)) 27 | } 28 | 29 | return rcodeClient{ 30 | rcode: rcode, 31 | addr: "rcode://" + addr, 32 | } 33 | } 34 | 35 | type rcodeClient struct { 36 | rcode int 37 | addr string 38 | } 39 | 40 | var _ dnsClient = rcodeClient{} 41 | 42 | func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { 43 | m.Response = true 44 | m.Rcode = r.rcode 45 | return m, nil 46 | } 47 | 48 | func (r rcodeClient) Address() string { 49 | return r.addr 50 | } 51 | 52 | func (r rcodeClient) ResetConnection() {} 53 | -------------------------------------------------------------------------------- /dns/system_posix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package dns 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "net/netip" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | const resolvConf = "/etc/resolv.conf" 14 | 15 | func dnsReadConfig() (servers []string, err error) { 16 | file, err := os.Open(resolvConf) 17 | if err != nil { 18 | err = fmt.Errorf("failed to read %s: %w", resolvConf, err) 19 | return 20 | } 21 | defer func() { _ = file.Close() }() 22 | scanner := bufio.NewScanner(file) 23 | for scanner.Scan() { 24 | line := scanner.Text() 25 | if len(line) > 0 && (line[0] == ';' || line[0] == '#') { 26 | // comment. 27 | continue 28 | } 29 | f := strings.Fields(line) 30 | if len(f) < 1 { 31 | continue 32 | } 33 | switch f[0] { 34 | case "nameserver": // add one name server 35 | if len(f) > 1 { 36 | if addr, err := netip.ParseAddr(f[1]); err == nil { 37 | servers = append(servers, addr.String()) 38 | } 39 | } 40 | } 41 | } 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /docker/file-name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | os="mihomo-linux-" 3 | case $TARGETPLATFORM in 4 | "linux/amd64") 5 | arch="amd64-compatible" 6 | ;; 7 | "linux/386") 8 | arch="386" 9 | ;; 10 | "linux/arm64") 11 | arch="arm64" 12 | ;; 13 | "linux/arm/v7") 14 | arch="armv7" 15 | ;; 16 | "riscv64") 17 | arch="riscv64" 18 | ;; 19 | *) 20 | echo "Unknown architecture" 21 | exit 1 22 | ;; 23 | esac 24 | file_name="$os$arch-$(cat bin/version.txt)" 25 | echo $file_name -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xishang0128/mihomo/0edaf093778d126f1ac7b65e4bcb0160f610cf79/docs/logo.png -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1671072901, 6 | "narHash": "sha256-eyFdLtfxYyZnbJorRiZ2kP2kW4gEU76hLzpZGW9mcZg=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "69ce4fbad877f91d4b9bc4cfedfb0ff1fe5043d5", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "master", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "utils": "utils" 23 | } 24 | }, 25 | "utils": { 26 | "locked": { 27 | "lastModified": 1667395993, 28 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "flake-utils", 37 | "type": "github" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /hub/executor/concurrent_load_limit.go: -------------------------------------------------------------------------------- 1 | //go:build !386 && !amd64 && !arm64 && !arm64be && !mipsle && !mips 2 | 3 | package executor 4 | 5 | const concurrentCount = 5 6 | -------------------------------------------------------------------------------- /hub/executor/concurrent_load_single.go: -------------------------------------------------------------------------------- 1 | //go:build mips || mipsle 2 | 3 | package executor 4 | 5 | const concurrentCount = 1 6 | -------------------------------------------------------------------------------- /hub/executor/concurrent_load_unlimit.go: -------------------------------------------------------------------------------- 1 | //go:build 386 || amd64 || arm64 || arm64be 2 | 3 | package executor 4 | 5 | import "math" 6 | 7 | const concurrentCount = math.MaxInt 8 | -------------------------------------------------------------------------------- /hub/route/cache.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/metacubex/mihomo/component/resolver" 7 | 8 | "github.com/go-chi/chi/v5" 9 | "github.com/go-chi/render" 10 | ) 11 | 12 | func cacheRouter() http.Handler { 13 | r := chi.NewRouter() 14 | r.Post("/fakeip/flush", flushFakeIPPool) 15 | return r 16 | } 17 | 18 | func flushFakeIPPool(w http.ResponseWriter, r *http.Request) { 19 | err := resolver.FlushFakeIP() 20 | if err != nil { 21 | render.Status(r, http.StatusBadRequest) 22 | render.JSON(w, r, newError(err.Error())) 23 | return 24 | } 25 | render.NoContent(w, r) 26 | } 27 | -------------------------------------------------------------------------------- /hub/route/common.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | // When name is composed of a partial escape string, Golang does not unescape it 11 | func getEscapeParam(r *http.Request, paramName string) string { 12 | param := chi.URLParam(r, paramName) 13 | if newParam, err := url.PathUnescape(param); err == nil { 14 | param = newParam 15 | } 16 | return param 17 | } 18 | -------------------------------------------------------------------------------- /hub/route/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | CtxKeyProxyName = contextKey("proxy name") 5 | CtxKeyProviderName = contextKey("provider name") 6 | CtxKeyProxy = contextKey("proxy") 7 | CtxKeyProvider = contextKey("provider") 8 | ) 9 | 10 | type contextKey string 11 | 12 | func (c contextKey) String() string { 13 | return "mihomo context key " + string(c) 14 | } 15 | -------------------------------------------------------------------------------- /hub/route/errors.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | ErrUnauthorized = newError("Unauthorized") 5 | ErrBadRequest = newError("Body invalid") 6 | ErrForbidden = newError("Forbidden") 7 | ErrNotFound = newError("Resource not found") 8 | ErrRequestTimeout = newError("Timeout") 9 | ) 10 | 11 | // HTTPError is custom HTTP error for API 12 | type HTTPError struct { 13 | Message string `json:"message"` 14 | } 15 | 16 | func (e *HTTPError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func newError(msg string) *HTTPError { 21 | return &HTTPError{Message: msg} 22 | } 23 | -------------------------------------------------------------------------------- /hub/route/external.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import "github.com/go-chi/chi/v5" 4 | 5 | type externalRouter func(r chi.Router) 6 | 7 | var externalRouters = make([]externalRouter, 0) 8 | 9 | func Register(route ...externalRouter) { 10 | externalRouters = append(externalRouters, route...) 11 | } 12 | 13 | func addExternalRouters(r chi.Router) { 14 | if len(externalRouters) == 0 { 15 | return 16 | } 17 | 18 | for _, caller := range externalRouters { 19 | caller(r) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hub/route/patch_android.go: -------------------------------------------------------------------------------- 1 | //go:build android && cmfa 2 | 3 | package route 4 | 5 | func init() { 6 | SetEmbedMode(true) // set embed mode default 7 | } 8 | -------------------------------------------------------------------------------- /hub/route/rules.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/constant" 5 | "net/http" 6 | 7 | "github.com/metacubex/mihomo/tunnel" 8 | 9 | "github.com/go-chi/chi/v5" 10 | "github.com/go-chi/render" 11 | ) 12 | 13 | func ruleRouter() http.Handler { 14 | r := chi.NewRouter() 15 | r.Get("/", getRules) 16 | return r 17 | } 18 | 19 | type Rule struct { 20 | Type string `json:"type"` 21 | Payload string `json:"payload"` 22 | Proxy string `json:"proxy"` 23 | Size int `json:"size"` 24 | } 25 | 26 | func getRules(w http.ResponseWriter, r *http.Request) { 27 | rawRules := tunnel.Rules() 28 | rules := []Rule{} 29 | for _, rule := range rawRules { 30 | r := Rule{ 31 | Type: rule.RuleType().String(), 32 | Payload: rule.Payload(), 33 | Proxy: rule.Adapter(), 34 | Size: -1, 35 | } 36 | if rule.RuleType() == constant.GEOIP || rule.RuleType() == constant.GEOSITE { 37 | r.Size = rule.(constant.RuleGroup).GetRecodeSize() 38 | } 39 | rules = append(rules, r) 40 | 41 | } 42 | 43 | render.JSON(w, r, render.M{ 44 | "rules": rules, 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /listener/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/component/auth" 5 | ) 6 | 7 | type authStore struct { 8 | authenticator auth.Authenticator 9 | } 10 | 11 | func (a *authStore) Authenticator() auth.Authenticator { 12 | return a.authenticator 13 | } 14 | 15 | func (a *authStore) SetAuthenticator(authenticator auth.Authenticator) { 16 | a.authenticator = authenticator 17 | } 18 | 19 | func NewAuthStore(authenticator auth.Authenticator) auth.AuthStore { 20 | return &authStore{authenticator} 21 | } 22 | 23 | var Default auth.AuthStore = NewAuthStore(nil) 24 | 25 | type nilAuthStore struct{} 26 | 27 | func (a *nilAuthStore) Authenticator() auth.Authenticator { 28 | return nil 29 | } 30 | 31 | func (a *nilAuthStore) SetAuthenticator(authenticator auth.Authenticator) {} 32 | 33 | var Nil auth.AuthStore = (*nilAuthStore)(nil) // always return nil, even call SetAuthenticator() with a non-nil authenticator 34 | -------------------------------------------------------------------------------- /listener/config/anytls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type AnyTLSServer struct { 8 | Enable bool `yaml:"enable" json:"enable"` 9 | Listen string `yaml:"listen" json:"listen"` 10 | Users map[string]string `yaml:"users" json:"users,omitempty"` 11 | Certificate string `yaml:"certificate" json:"certificate"` 12 | PrivateKey string `yaml:"private-key" json:"private-key"` 13 | PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"` 14 | } 15 | 16 | func (t AnyTLSServer) String() string { 17 | b, _ := json.Marshal(t) 18 | return string(b) 19 | } 20 | -------------------------------------------------------------------------------- /listener/config/auth.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/component/auth" 5 | "github.com/metacubex/mihomo/listener/reality" 6 | ) 7 | 8 | // AuthServer for http/socks/mixed server 9 | type AuthServer struct { 10 | Enable bool 11 | Listen string 12 | AuthStore auth.AuthStore 13 | Certificate string 14 | PrivateKey string 15 | RealityConfig reality.Config 16 | } 17 | -------------------------------------------------------------------------------- /listener/config/shadowsocks.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/listener/sing" 5 | 6 | "encoding/json" 7 | ) 8 | 9 | type ShadowsocksServer struct { 10 | Enable bool 11 | Listen string 12 | Password string 13 | Cipher string 14 | Udp bool 15 | MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` 16 | ShadowTLS ShadowTLS `yaml:"shadow-tls" json:"shadow-tls,omitempty"` 17 | } 18 | 19 | func (t ShadowsocksServer) String() string { 20 | b, _ := json.Marshal(t) 21 | return string(b) 22 | } 23 | -------------------------------------------------------------------------------- /listener/config/shadowtls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type ShadowTLS struct { 4 | Enable bool 5 | Version int 6 | Password string 7 | Users []ShadowTLSUser 8 | Handshake ShadowTLSHandshakeOptions 9 | HandshakeForServerName map[string]ShadowTLSHandshakeOptions 10 | StrictMode bool 11 | WildcardSNI string 12 | } 13 | 14 | type ShadowTLSUser struct { 15 | Name string 16 | Password string 17 | } 18 | 19 | type ShadowTLSHandshakeOptions struct { 20 | Dest string 21 | Proxy string 22 | } 23 | -------------------------------------------------------------------------------- /listener/config/trojan.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/metacubex/mihomo/listener/reality" 7 | "github.com/metacubex/mihomo/listener/sing" 8 | ) 9 | 10 | type TrojanUser struct { 11 | Username string 12 | Password string 13 | } 14 | 15 | type TrojanServer struct { 16 | Enable bool 17 | Listen string 18 | Users []TrojanUser 19 | WsPath string 20 | GrpcServiceName string 21 | Certificate string 22 | PrivateKey string 23 | RealityConfig reality.Config 24 | MuxOption sing.MuxOption 25 | TrojanSSOption TrojanSSOption 26 | } 27 | 28 | // TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5 29 | type TrojanSSOption struct { 30 | Enabled bool 31 | Method string 32 | Password string 33 | } 34 | 35 | func (t TrojanServer) String() string { 36 | b, _ := json.Marshal(t) 37 | return string(b) 38 | } 39 | -------------------------------------------------------------------------------- /listener/config/vless.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/metacubex/mihomo/listener/reality" 7 | "github.com/metacubex/mihomo/listener/sing" 8 | ) 9 | 10 | type VlessUser struct { 11 | Username string 12 | UUID string 13 | Flow string 14 | } 15 | 16 | type VlessServer struct { 17 | Enable bool 18 | Listen string 19 | Users []VlessUser 20 | WsPath string 21 | GrpcServiceName string 22 | Certificate string 23 | PrivateKey string 24 | RealityConfig reality.Config 25 | MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` 26 | } 27 | 28 | func (t VlessServer) String() string { 29 | b, _ := json.Marshal(t) 30 | return string(b) 31 | } 32 | -------------------------------------------------------------------------------- /listener/config/vmess.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/metacubex/mihomo/listener/reality" 7 | "github.com/metacubex/mihomo/listener/sing" 8 | ) 9 | 10 | type VmessUser struct { 11 | Username string 12 | UUID string 13 | AlterID int 14 | } 15 | 16 | type VmessServer struct { 17 | Enable bool 18 | Listen string 19 | Users []VmessUser 20 | WsPath string 21 | GrpcServiceName string 22 | Certificate string 23 | PrivateKey string 24 | RealityConfig reality.Config 25 | MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` 26 | } 27 | 28 | func (t VmessServer) String() string { 29 | b, _ := json.Marshal(t) 30 | return string(b) 31 | } 32 | -------------------------------------------------------------------------------- /listener/http/hack.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "net/http" 6 | _ "unsafe" 7 | ) 8 | 9 | //go:linkname ReadRequest net/http.readRequest 10 | func ReadRequest(b *bufio.Reader) (req *http.Request, err error) 11 | -------------------------------------------------------------------------------- /listener/inbound/auth.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/component/auth" 5 | authStore "github.com/metacubex/mihomo/listener/auth" 6 | ) 7 | 8 | type AuthUser struct { 9 | Username string `inbound:"username"` 10 | Password string `inbound:"password"` 11 | } 12 | 13 | type AuthUsers []AuthUser 14 | 15 | func (a AuthUsers) GetAuthStore() auth.AuthStore { 16 | if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array 17 | if len(a) == 0 { 18 | return authStore.Nil 19 | } 20 | users := make([]auth.AuthUser, len(a)) 21 | for i, user := range a { 22 | users[i] = auth.AuthUser{ 23 | User: user.Username, 24 | Pass: user.Password, 25 | } 26 | } 27 | authenticator := auth.NewAuthenticator(users) 28 | return authStore.NewAuthStore(authenticator) 29 | } 30 | return authStore.Default 31 | } 32 | -------------------------------------------------------------------------------- /listener/inbound/mux.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import "github.com/metacubex/mihomo/listener/sing" 4 | 5 | type MuxOption struct { 6 | Padding bool `inbound:"padding,omitempty"` 7 | Brutal BrutalOptions `inbound:"brutal,omitempty"` 8 | } 9 | 10 | type BrutalOptions struct { 11 | Enabled bool `inbound:"enabled,omitempty"` 12 | Up string `inbound:"up,omitempty"` 13 | Down string `inbound:"down,omitempty"` 14 | } 15 | 16 | func (m MuxOption) Build() sing.MuxOption { 17 | return sing.MuxOption{ 18 | Padding: m.Padding, 19 | Brutal: sing.BrutalOptions{ 20 | Enabled: m.Brutal.Enabled, 21 | Up: m.Brutal.Up, 22 | Down: m.Brutal.Down, 23 | }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /listener/inbound/mux_test.go: -------------------------------------------------------------------------------- 1 | package inbound_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/metacubex/mihomo/adapter/outbound" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs 12 | 13 | // notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter. 14 | // The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it. 15 | // The underlying outbound.ProxyAdapter should only be closed by the caller of testSingMux. 16 | type notCloseProxyAdapter struct { 17 | outbound.ProxyAdapter 18 | } 19 | 20 | func (n *notCloseProxyAdapter) Close() error { 21 | return nil 22 | } 23 | 24 | func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) { 25 | t.Run("singmux", func(t *testing.T) { 26 | for _, protocol := range singMuxProtocolList { 27 | protocol := protocol 28 | t.Run(protocol, func(t *testing.T) { 29 | t.Parallel() 30 | singMuxOption := outbound.SingMuxOption{ 31 | Enabled: true, 32 | Protocol: protocol, 33 | } 34 | out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out}) 35 | if !assert.NoError(t, err) { 36 | return 37 | } 38 | defer out.Close() 39 | 40 | tunnel.DoTest(t, out) 41 | }) 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /listener/inbound/reality.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import "github.com/metacubex/mihomo/listener/reality" 4 | 5 | type RealityConfig struct { 6 | Dest string `inbound:"dest"` 7 | PrivateKey string `inbound:"private-key"` 8 | ShortID []string `inbound:"short-id"` 9 | ServerNames []string `inbound:"server-names"` 10 | MaxTimeDifference int `inbound:"max-time-difference,omitempty"` 11 | Proxy string `inbound:"proxy,omitempty"` 12 | } 13 | 14 | func (c RealityConfig) Build() reality.Config { 15 | return reality.Config{ 16 | Dest: c.Dest, 17 | PrivateKey: c.PrivateKey, 18 | ShortID: c.ShortID, 19 | ServerNames: c.ServerNames, 20 | MaxTimeDifference: c.MaxTimeDifference, 21 | Proxy: c.Proxy, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /listener/inner/tcp.go: -------------------------------------------------------------------------------- 1 | package inner 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | N "github.com/metacubex/mihomo/common/net" 8 | C "github.com/metacubex/mihomo/constant" 9 | ) 10 | 11 | var tunnel C.Tunnel 12 | 13 | func New(t C.Tunnel) { 14 | tunnel = t 15 | } 16 | 17 | func GetTunnel() C.Tunnel { 18 | return tunnel 19 | } 20 | 21 | func HandleTcp(tunnel C.Tunnel, address string, proxy string) (conn net.Conn, err error) { 22 | if tunnel == nil { 23 | return nil, errors.New("tunnel uninitialized") 24 | } 25 | // executor Parsed 26 | conn1, conn2 := N.Pipe() 27 | 28 | metadata := &C.Metadata{} 29 | metadata.NetWork = C.TCP 30 | metadata.Type = C.INNER 31 | metadata.DNSMode = C.DNSNormal 32 | metadata.Process = C.MihomoName 33 | if proxy != "" { 34 | metadata.SpecialProxy = proxy 35 | } 36 | if err = metadata.SetRemoteAddress(address); err != nil { 37 | return nil, err 38 | } 39 | 40 | go tunnel.HandleTCPConn(conn2, metadata) 41 | return conn1, nil 42 | } 43 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_386.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 9 | 10 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 11 | var a [6]uintptr 12 | a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 13 | if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { 14 | return errno 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_other.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !386 2 | 3 | package redir 4 | 5 | import "syscall" 6 | 7 | const GETSOCKOPT = syscall.SYS_GETSOCKOPT 8 | 9 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 10 | if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { 11 | return errno 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /listener/redir/tcp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !freebsd 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | 9 | "github.com/metacubex/mihomo/transport/socks5" 10 | ) 11 | 12 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 13 | return nil, errors.New("system not support yet") 14 | } 15 | -------------------------------------------------------------------------------- /listener/shadowsocks/utils.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "net" 7 | "net/url" 8 | 9 | "github.com/metacubex/mihomo/transport/socks5" 10 | ) 11 | 12 | type packet struct { 13 | pc net.PacketConn 14 | rAddr net.Addr 15 | payload []byte 16 | put func() 17 | } 18 | 19 | func (c *packet) Data() []byte { 20 | return c.payload 21 | } 22 | 23 | // WriteBack wirtes UDP packet with source(ip, port) = `addr` 24 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 25 | if addr == nil { 26 | err = errors.New("address is invalid") 27 | return 28 | } 29 | packet := bytes.Join([][]byte{socks5.ParseAddrToSocksAddr(addr), b}, []byte{}) 30 | return c.pc.WriteTo(packet, c.rAddr) 31 | } 32 | 33 | // LocalAddr returns the source IP/Port of UDP Packet 34 | func (c *packet) LocalAddr() net.Addr { 35 | return c.rAddr 36 | } 37 | 38 | func (c *packet) Drop() { 39 | if c.put != nil { 40 | c.put() 41 | c.put = nil 42 | } 43 | c.payload = nil 44 | } 45 | 46 | func (c *packet) InAddr() net.Addr { 47 | return c.pc.LocalAddr() 48 | } 49 | 50 | func ParseSSURL(s string) (addr, cipher, password string, err error) { 51 | u, err := url.Parse(s) 52 | if err != nil { 53 | return 54 | } 55 | 56 | addr = u.Host 57 | if u.User != nil { 58 | cipher = u.User.Username() 59 | password, _ = u.User.Password() 60 | } 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /listener/sing/context.go: -------------------------------------------------------------------------------- 1 | package sing 2 | 3 | import ( 4 | "context" 5 | "golang.org/x/exp/slices" 6 | 7 | "github.com/metacubex/mihomo/adapter/inbound" 8 | 9 | "github.com/sagernet/sing/common/auth" 10 | ) 11 | 12 | type contextKey string 13 | 14 | var ctxKeyAdditions = contextKey("Additions") 15 | 16 | func WithAdditions(ctx context.Context, additions ...inbound.Addition) context.Context { 17 | return context.WithValue(ctx, ctxKeyAdditions, additions) 18 | } 19 | 20 | func getAdditions(ctx context.Context) (additions []inbound.Addition) { 21 | if v := ctx.Value(ctxKeyAdditions); v != nil { 22 | if a, ok := v.([]inbound.Addition); ok { 23 | additions = a 24 | } 25 | } 26 | if user, ok := auth.UserFromContext[string](ctx); ok { 27 | additions = slices.Clone(additions) 28 | additions = append(additions, inbound.WithInUser(user)) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /listener/sing/dialer.go: -------------------------------------------------------------------------------- 1 | package sing 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | C "github.com/metacubex/mihomo/constant" 9 | "github.com/metacubex/mihomo/listener/inner" 10 | 11 | M "github.com/sagernet/sing/common/metadata" 12 | N "github.com/sagernet/sing/common/network" 13 | ) 14 | 15 | type Dialer struct { 16 | t C.Tunnel 17 | proxy string 18 | } 19 | 20 | func (d Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 21 | if network != "tcp" && network != "tcp4" && network != "tcp6" { 22 | return nil, fmt.Errorf("unsupported network %s", network) 23 | } 24 | return inner.HandleTcp(d.t, destination.String(), d.proxy) 25 | } 26 | 27 | func (d Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 28 | return nil, fmt.Errorf("unsupported ListenPacket") 29 | } 30 | 31 | var _ N.Dialer = (*Dialer)(nil) 32 | 33 | func NewDialer(t C.Tunnel, proxy string) (d *Dialer) { 34 | return &Dialer{t, proxy} 35 | } 36 | -------------------------------------------------------------------------------- /listener/sing/util.go: -------------------------------------------------------------------------------- 1 | package sing 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/metacubex/mihomo/adapter/inbound" 8 | "github.com/metacubex/mihomo/transport/socks5" 9 | ) 10 | 11 | // HandleSocket like inbound.NewSocket combine with Tunnel.HandleTCPConn but also handel specialFqdn 12 | func (h *ListenerHandler) HandleSocket(target socks5.Addr, conn net.Conn, _additions ...inbound.Addition) { 13 | conn, metadata := inbound.NewSocket(target, conn, h.Type, h.Additions...) 14 | if h.IsSpecialFqdn(metadata.Host) { 15 | _ = h.ParseSpecialFqdn( 16 | WithAdditions(context.Background(), _additions...), 17 | conn, 18 | ConvertMetadata(metadata), 19 | ) 20 | } else { 21 | inbound.ApplyAdditions(metadata, _additions...) 22 | h.Tunnel.HandleTCPConn(conn, metadata) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /listener/sing_tun/redirect_linux.go: -------------------------------------------------------------------------------- 1 | package sing_tun 2 | 3 | const supportRedirect = true 4 | -------------------------------------------------------------------------------- /listener/sing_tun/redirect_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package sing_tun 4 | 5 | const supportRedirect = false 6 | -------------------------------------------------------------------------------- /listener/sing_tun/server_notandroid.go: -------------------------------------------------------------------------------- 1 | //go:build !android || cmfa 2 | 3 | package sing_tun 4 | 5 | import ( 6 | tun "github.com/metacubex/sing-tun" 7 | ) 8 | 9 | func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { 10 | return nil 11 | } 12 | func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {} 13 | -------------------------------------------------------------------------------- /listener/sing_tun/server_notwindows.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package sing_tun 4 | 5 | import ( 6 | tun "github.com/metacubex/sing-tun" 7 | ) 8 | 9 | func tunNew(options tun.Options) (tun.Tun, error) { 10 | return tun.New(options) 11 | } 12 | -------------------------------------------------------------------------------- /listener/sing_tun/server_windows.go: -------------------------------------------------------------------------------- 1 | package sing_tun 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/metacubex/mihomo/constant/features" 7 | "github.com/metacubex/mihomo/log" 8 | 9 | tun "github.com/metacubex/sing-tun" 10 | ) 11 | 12 | func tunNew(options tun.Options) (tunIf tun.Tun, err error) { 13 | maxRetry := 3 14 | for i := 0; i < maxRetry; i++ { 15 | timeBegin := time.Now() 16 | tunIf, err = tun.New(options) 17 | if err == nil { 18 | return 19 | } 20 | timeEnd := time.Now() 21 | if timeEnd.Sub(timeBegin) < 1*time.Second { // retrying for "Cannot create a file when that file already exists." 22 | return 23 | } 24 | log.Warnln("Start Tun interface timeout: %s [retrying %d/%d]", err, i+1, maxRetry) 25 | } 26 | return 27 | } 28 | 29 | func init() { 30 | tun.TunnelType = InterfaceName 31 | 32 | if features.WindowsMajorVersion < 10 { 33 | // to resolve "bind: The requested address is not valid in its context" 34 | EnforceBindInterface = true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /listener/sing_tun/tun_name_darwin.go: -------------------------------------------------------------------------------- 1 | package sing_tun 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | func getTunnelName(fd int32) (string, error) { 6 | return unix.GetsockoptString( 7 | int(fd), 8 | 2, /* #define SYSPROTO_CONTROL 2 */ 9 | 2, /* #define UTUN_OPT_IFNAME 2 */ 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /listener/sing_tun/tun_name_linux.go: -------------------------------------------------------------------------------- 1 | package sing_tun 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/sys/unix" 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | const ifReqSize = unix.IFNAMSIZ + 64 11 | 12 | func getTunnelName(fd int32) (string, error) { 13 | var ifr [ifReqSize]byte 14 | var errno syscall.Errno 15 | _, _, errno = unix.Syscall( 16 | unix.SYS_IOCTL, 17 | uintptr(fd), 18 | uintptr(unix.TUNGETIFF), 19 | uintptr(unsafe.Pointer(&ifr[0])), 20 | ) 21 | if errno != 0 { 22 | return "", fmt.Errorf("failed to get name of TUN device: %w", errno) 23 | } 24 | return unix.ByteSliceToString(ifr[:]), nil 25 | } 26 | -------------------------------------------------------------------------------- /listener/sing_tun/tun_name_other.go: -------------------------------------------------------------------------------- 1 | //go:build !(darwin || linux) 2 | 3 | package sing_tun 4 | 5 | import "os" 6 | 7 | func getTunnelName(fd int32) (string, error) { 8 | return "", os.ErrInvalid 9 | } 10 | -------------------------------------------------------------------------------- /listener/socks/utils.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metacubex/mihomo/transport/socks5" 7 | ) 8 | 9 | type packet struct { 10 | pc net.PacketConn 11 | rAddr net.Addr 12 | payload []byte 13 | put func() 14 | } 15 | 16 | func (c *packet) Data() []byte { 17 | return c.payload 18 | } 19 | 20 | // WriteBack write UDP packet with source(ip, port) = `addr` 21 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 22 | packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) 23 | if err != nil { 24 | return 25 | } 26 | return c.pc.WriteTo(packet, c.rAddr) 27 | } 28 | 29 | // LocalAddr returns the source IP/Port of UDP Packet 30 | func (c *packet) LocalAddr() net.Addr { 31 | return c.rAddr 32 | } 33 | 34 | func (c *packet) Drop() { 35 | if c.put != nil { 36 | c.put() 37 | c.put = nil 38 | } 39 | c.payload = nil 40 | } 41 | 42 | func (c *packet) InAddr() net.Addr { 43 | return c.pc.LocalAddr() 44 | } 45 | -------------------------------------------------------------------------------- /listener/tproxy/setsockopt_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "syscall" 8 | ) 9 | 10 | func setsockopt(rc syscall.RawConn, addr string) error { 11 | return errors.New("not supported on current platform") 12 | } 13 | -------------------------------------------------------------------------------- /listener/tproxy/udp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | "net/netip" 9 | ) 10 | 11 | func getOrigDst(oob []byte) (netip.AddrPort, error) { 12 | return netip.AddrPort{}, errors.New("UDP redir not supported on current platform") 13 | } 14 | 15 | func getDSCP(oob []byte) (uint8, error) { 16 | return 0, errors.New("UDP redir not supported on current platform") 17 | } 18 | 19 | func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) { 20 | return nil, errors.New("UDP redir not supported on current platform") 21 | } 22 | -------------------------------------------------------------------------------- /listener/trojan/packet.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | ) 7 | 8 | type packet struct { 9 | pc net.PacketConn 10 | rAddr net.Addr 11 | payload []byte 12 | put func() 13 | } 14 | 15 | func (c *packet) Data() []byte { 16 | return c.payload 17 | } 18 | 19 | // WriteBack wirtes UDP packet with source(ip, port) = `addr` 20 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 21 | if addr == nil { 22 | err = errors.New("address is invalid") 23 | return 24 | } 25 | return c.pc.WriteTo(b, addr) 26 | } 27 | 28 | // LocalAddr returns the source IP/Port of UDP Packet 29 | func (c *packet) LocalAddr() net.Addr { 30 | return c.rAddr 31 | } 32 | 33 | func (c *packet) Drop() { 34 | if c.put != nil { 35 | c.put() 36 | c.put = nil 37 | } 38 | c.payload = nil 39 | } 40 | 41 | func (c *packet) InAddr() net.Addr { 42 | return c.pc.LocalAddr() 43 | } 44 | -------------------------------------------------------------------------------- /listener/tunnel/packet.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metacubex/mihomo/common/pool" 7 | ) 8 | 9 | type packet struct { 10 | pc net.PacketConn 11 | rAddr net.Addr 12 | payload []byte 13 | } 14 | 15 | func (c *packet) Data() []byte { 16 | return c.payload 17 | } 18 | 19 | // WriteBack write UDP packet with source(ip, port) = `addr` 20 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 21 | return c.pc.WriteTo(b, c.rAddr) 22 | } 23 | 24 | // LocalAddr returns the source IP/Port of UDP Packet 25 | func (c *packet) LocalAddr() net.Addr { 26 | return c.rAddr 27 | } 28 | 29 | func (c *packet) Drop() { 30 | _ = pool.Put(c.payload) 31 | c.payload = nil 32 | } 33 | 34 | func (c *packet) InAddr() net.Addr { 35 | return c.pc.LocalAddr() 36 | } 37 | -------------------------------------------------------------------------------- /ntp/time_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !(windows || linux || darwin) 2 | 3 | package ntp 4 | 5 | import ( 6 | "os" 7 | "time" 8 | ) 9 | 10 | func setSystemTime(nowTime time.Time) error { 11 | return os.ErrInvalid 12 | } 13 | -------------------------------------------------------------------------------- /ntp/time_unix.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package ntp 4 | 5 | import ( 6 | "time" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func setSystemTime(nowTime time.Time) error { 12 | timeVal := unix.NsecToTimeval(nowTime.UnixNano()) 13 | return unix.Settimeofday(&timeVal) 14 | } 15 | -------------------------------------------------------------------------------- /ntp/time_windows.go: -------------------------------------------------------------------------------- 1 | package ntp 2 | 3 | import ( 4 | "time" 5 | "unsafe" 6 | 7 | "golang.org/x/sys/windows" 8 | ) 9 | 10 | func setSystemTime(nowTime time.Time) error { 11 | var systemTime windows.Systemtime 12 | systemTime.Year = uint16(nowTime.Year()) 13 | systemTime.Month = uint16(nowTime.Month()) 14 | systemTime.Day = uint16(nowTime.Day()) 15 | systemTime.Hour = uint16(nowTime.Hour()) 16 | systemTime.Minute = uint16(nowTime.Minute()) 17 | systemTime.Second = uint16(nowTime.Second()) 18 | systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) 19 | 20 | dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") 21 | proc := dllKernel32.NewProc("SetSystemTime") 22 | 23 | _, _, err := proc.Call( 24 | uintptr(unsafe.Pointer(&systemTime)), 25 | ) 26 | 27 | if err != nil && err.Error() != "The operation completed successfully." { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /rules/common/base.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | 8 | "golang.org/x/exp/slices" 9 | ) 10 | 11 | var ( 12 | errPayload = errors.New("payloadRule error") 13 | ) 14 | 15 | // params 16 | var ( 17 | NoResolve = "no-resolve" 18 | Src = "src" 19 | ) 20 | 21 | type Base struct { 22 | } 23 | 24 | func (b *Base) ShouldFindProcess() bool { 25 | return false 26 | } 27 | 28 | func (b *Base) ShouldResolveIP() bool { 29 | return false 30 | } 31 | 32 | func (b *Base) ProviderNames() []string { return nil } 33 | 34 | func ParseParams(params []string) (isSrc bool, noResolve bool) { 35 | isSrc = slices.Contains(params, Src) 36 | if isSrc { 37 | noResolve = true 38 | } else { 39 | noResolve = slices.Contains(params, NoResolve) 40 | } 41 | return 42 | } 43 | 44 | type ParseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (C.Rule, error) 45 | -------------------------------------------------------------------------------- /rules/common/domain.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | "golang.org/x/net/idna" 8 | ) 9 | 10 | type Domain struct { 11 | *Base 12 | domain string 13 | adapter string 14 | } 15 | 16 | func (d *Domain) RuleType() C.RuleType { 17 | return C.Domain 18 | } 19 | 20 | func (d *Domain) Match(metadata *C.Metadata) (bool, string) { 21 | return metadata.RuleHost() == d.domain, d.adapter 22 | } 23 | 24 | func (d *Domain) Adapter() string { 25 | return d.adapter 26 | } 27 | 28 | func (d *Domain) Payload() string { 29 | return d.domain 30 | } 31 | 32 | func NewDomain(domain string, adapter string) *Domain { 33 | punycode, _ := idna.ToASCII(strings.ToLower(domain)) 34 | return &Domain{ 35 | Base: &Base{}, 36 | domain: punycode, 37 | adapter: adapter, 38 | } 39 | } 40 | 41 | //var _ C.Rule = (*Domain)(nil) 42 | -------------------------------------------------------------------------------- /rules/common/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | "golang.org/x/net/idna" 8 | ) 9 | 10 | type DomainKeyword struct { 11 | *Base 12 | keyword string 13 | adapter string 14 | } 15 | 16 | func (dk *DomainKeyword) RuleType() C.RuleType { 17 | return C.DomainKeyword 18 | } 19 | 20 | func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) { 21 | domain := metadata.RuleHost() 22 | return strings.Contains(domain, dk.keyword), dk.adapter 23 | } 24 | 25 | func (dk *DomainKeyword) Adapter() string { 26 | return dk.adapter 27 | } 28 | 29 | func (dk *DomainKeyword) Payload() string { 30 | return dk.keyword 31 | } 32 | 33 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 34 | punycode, _ := idna.ToASCII(strings.ToLower(keyword)) 35 | return &DomainKeyword{ 36 | Base: &Base{}, 37 | keyword: punycode, 38 | adapter: adapter, 39 | } 40 | } 41 | 42 | //var _ C.Rule = (*DomainKeyword)(nil) 43 | -------------------------------------------------------------------------------- /rules/common/domain_regex.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | C "github.com/metacubex/mihomo/constant" 5 | 6 | "github.com/dlclark/regexp2" 7 | ) 8 | 9 | type DomainRegex struct { 10 | *Base 11 | regex *regexp2.Regexp 12 | adapter string 13 | } 14 | 15 | func (dr *DomainRegex) RuleType() C.RuleType { 16 | return C.DomainRegex 17 | } 18 | 19 | func (dr *DomainRegex) Match(metadata *C.Metadata) (bool, string) { 20 | domain := metadata.RuleHost() 21 | match, _ := dr.regex.MatchString(domain) 22 | return match, dr.adapter 23 | } 24 | 25 | func (dr *DomainRegex) Adapter() string { 26 | return dr.adapter 27 | } 28 | 29 | func (dr *DomainRegex) Payload() string { 30 | return dr.regex.String() 31 | } 32 | 33 | func NewDomainRegex(regex string, adapter string) (*DomainRegex, error) { 34 | r, err := regexp2.Compile(regex, regexp2.IgnoreCase) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &DomainRegex{ 39 | Base: &Base{}, 40 | regex: r, 41 | adapter: adapter, 42 | }, nil 43 | } 44 | 45 | //var _ C.Rule = (*DomainRegex)(nil) 46 | -------------------------------------------------------------------------------- /rules/common/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/metacubex/mihomo/constant" 7 | "golang.org/x/net/idna" 8 | ) 9 | 10 | type DomainSuffix struct { 11 | *Base 12 | suffix string 13 | adapter string 14 | } 15 | 16 | func (ds *DomainSuffix) RuleType() C.RuleType { 17 | return C.DomainSuffix 18 | } 19 | 20 | func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) { 21 | domain := metadata.RuleHost() 22 | return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter 23 | } 24 | 25 | func (ds *DomainSuffix) Adapter() string { 26 | return ds.adapter 27 | } 28 | 29 | func (ds *DomainSuffix) Payload() string { 30 | return ds.suffix 31 | } 32 | 33 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 34 | punycode, _ := idna.ToASCII(strings.ToLower(suffix)) 35 | return &DomainSuffix{ 36 | Base: &Base{}, 37 | suffix: punycode, 38 | adapter: adapter, 39 | } 40 | } 41 | 42 | //var _ C.Rule = (*DomainSuffix)(nil) 43 | -------------------------------------------------------------------------------- /rules/common/dscp.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/metacubex/mihomo/common/utils" 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | type DSCP struct { 11 | *Base 12 | ranges utils.IntRanges[uint8] 13 | payload string 14 | adapter string 15 | } 16 | 17 | func (d *DSCP) RuleType() C.RuleType { 18 | return C.DSCP 19 | } 20 | 21 | func (d *DSCP) Match(metadata *C.Metadata) (bool, string) { 22 | return d.ranges.Check(metadata.DSCP), d.adapter 23 | } 24 | 25 | func (d *DSCP) Adapter() string { 26 | return d.adapter 27 | } 28 | 29 | func (d *DSCP) Payload() string { 30 | return d.payload 31 | } 32 | 33 | func NewDSCP(dscp string, adapter string) (*DSCP, error) { 34 | ranges, err := utils.NewUnsignedRanges[uint8](dscp) 35 | if err != nil { 36 | return nil, fmt.Errorf("parse DSCP rule fail: %w", err) 37 | } 38 | for _, r := range ranges { 39 | if r.End() > 63 { 40 | return nil, fmt.Errorf("DSCP couldn't be negative or exceed 63") 41 | } 42 | } 43 | return &DSCP{ 44 | Base: &Base{}, 45 | payload: dscp, 46 | ranges: ranges, 47 | adapter: adapter, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /rules/common/final.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | C "github.com/metacubex/mihomo/constant" 5 | ) 6 | 7 | type Match struct { 8 | *Base 9 | adapter string 10 | } 11 | 12 | func (f *Match) RuleType() C.RuleType { 13 | return C.MATCH 14 | } 15 | 16 | func (f *Match) Match(metadata *C.Metadata) (bool, string) { 17 | return true, f.adapter 18 | } 19 | 20 | func (f *Match) Adapter() string { 21 | return f.adapter 22 | } 23 | 24 | func (f *Match) Payload() string { 25 | return "" 26 | } 27 | 28 | func NewMatch(adapter string) *Match { 29 | return &Match{ 30 | Base: &Base{}, 31 | adapter: adapter, 32 | } 33 | } 34 | 35 | //var _ C.Rule = (*Match)(nil) 36 | -------------------------------------------------------------------------------- /rules/common/in_name.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | C "github.com/metacubex/mihomo/constant" 6 | "strings" 7 | ) 8 | 9 | type InName struct { 10 | *Base 11 | names []string 12 | adapter string 13 | payload string 14 | } 15 | 16 | func (u *InName) Match(metadata *C.Metadata) (bool, string) { 17 | for _, name := range u.names { 18 | if metadata.InName == name { 19 | return true, u.adapter 20 | } 21 | } 22 | return false, "" 23 | } 24 | 25 | func (u *InName) RuleType() C.RuleType { 26 | return C.InName 27 | } 28 | 29 | func (u *InName) Adapter() string { 30 | return u.adapter 31 | } 32 | 33 | func (u *InName) Payload() string { 34 | return u.payload 35 | } 36 | 37 | func NewInName(iNames, adapter string) (*InName, error) { 38 | names := strings.Split(iNames, "/") 39 | if len(names) == 0 { 40 | return nil, fmt.Errorf("in name couldn't be empty") 41 | } 42 | 43 | return &InName{ 44 | Base: &Base{}, 45 | names: names, 46 | adapter: adapter, 47 | payload: iNames, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /rules/common/in_user.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | C "github.com/metacubex/mihomo/constant" 6 | "strings" 7 | ) 8 | 9 | type InUser struct { 10 | *Base 11 | users []string 12 | adapter string 13 | payload string 14 | } 15 | 16 | func (u *InUser) Match(metadata *C.Metadata) (bool, string) { 17 | for _, user := range u.users { 18 | if metadata.InUser == user { 19 | return true, u.adapter 20 | } 21 | } 22 | return false, "" 23 | } 24 | 25 | func (u *InUser) RuleType() C.RuleType { 26 | return C.InUser 27 | } 28 | 29 | func (u *InUser) Adapter() string { 30 | return u.adapter 31 | } 32 | 33 | func (u *InUser) Payload() string { 34 | return u.payload 35 | } 36 | 37 | func NewInUser(iUsers, adapter string) (*InUser, error) { 38 | users := strings.Split(iUsers, "/") 39 | if len(users) == 0 { 40 | return nil, fmt.Errorf("in user couldn't be empty") 41 | } 42 | 43 | return &InUser{ 44 | Base: &Base{}, 45 | users: users, 46 | adapter: adapter, 47 | payload: iUsers, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /rules/common/mac.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/metacubex/mihomo/component/arp" 10 | C "github.com/metacubex/mihomo/constant" 11 | ) 12 | 13 | type Mac struct { 14 | *Base 15 | mac string 16 | adapter string 17 | } 18 | 19 | func (m *Mac) RuleType() C.RuleType { 20 | return C.Mac 21 | } 22 | 23 | func (m *Mac) Match(metadata *C.Metadata) (bool, string) { 24 | if runtime.GOOS == "windows" || runtime.GOOS == "linux" { 25 | if arp.IPToMac(metadata.SrcIP) == m.mac { 26 | return true, m.adapter 27 | } 28 | } 29 | return false, m.adapter 30 | } 31 | 32 | func (m *Mac) Adapter() string { 33 | return m.adapter 34 | } 35 | 36 | func (m *Mac) Payload() string { 37 | return m.mac 38 | } 39 | 40 | func NewMAC(mac string, adapter string) (*Mac, error) { 41 | mac = regexp.MustCompile(`[^a-fA-F0-9]`).ReplaceAllString(mac, "") 42 | if len(mac) != 12 { 43 | return nil, fmt.Errorf("invalid MAC address length") 44 | } 45 | formattedMAC := fmt.Sprintf("%s:%s:%s:%s:%s:%s", 46 | mac[0:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12]) 47 | return &Mac{ 48 | Base: &Base{}, 49 | mac: strings.ToLower(formattedMAC), 50 | adapter: adapter, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /rules/common/network_type.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | C "github.com/metacubex/mihomo/constant" 6 | "strings" 7 | ) 8 | 9 | type NetworkType struct { 10 | *Base 11 | network C.NetWork 12 | adapter string 13 | } 14 | 15 | func NewNetworkType(network, adapter string) (*NetworkType, error) { 16 | ntType := NetworkType{ 17 | Base: &Base{}, 18 | } 19 | 20 | ntType.adapter = adapter 21 | switch strings.ToUpper(network) { 22 | case "TCP": 23 | ntType.network = C.TCP 24 | case "UDP": 25 | ntType.network = C.UDP 26 | default: 27 | return nil, fmt.Errorf("unsupported network type, only TCP/UDP") 28 | } 29 | 30 | return &ntType, nil 31 | } 32 | 33 | func (n *NetworkType) RuleType() C.RuleType { 34 | return C.Network 35 | } 36 | 37 | func (n *NetworkType) Match(metadata *C.Metadata) (bool, string) { 38 | return n.network == metadata.NetWork, n.adapter 39 | } 40 | 41 | func (n *NetworkType) Adapter() string { 42 | return n.adapter 43 | } 44 | 45 | func (n *NetworkType) Payload() string { 46 | return n.network.String() 47 | } 48 | -------------------------------------------------------------------------------- /rules/common/port.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/metacubex/mihomo/common/utils" 7 | C "github.com/metacubex/mihomo/constant" 8 | ) 9 | 10 | type Port struct { 11 | *Base 12 | adapter string 13 | port string 14 | ruleType C.RuleType 15 | portRanges utils.IntRanges[uint16] 16 | } 17 | 18 | func (p *Port) RuleType() C.RuleType { 19 | return p.ruleType 20 | } 21 | 22 | func (p *Port) Match(metadata *C.Metadata) (bool, string) { 23 | targetPort := metadata.DstPort 24 | switch p.ruleType { 25 | case C.InPort: 26 | targetPort = metadata.InPort 27 | case C.SrcPort: 28 | targetPort = metadata.SrcPort 29 | } 30 | return p.portRanges.Check(targetPort), p.adapter 31 | } 32 | 33 | func (p *Port) Adapter() string { 34 | return p.adapter 35 | } 36 | 37 | func (p *Port) Payload() string { 38 | return p.port 39 | } 40 | 41 | func NewPort(port string, adapter string, ruleType C.RuleType) (*Port, error) { 42 | portRanges, err := utils.NewUnsignedRanges[uint16](port) 43 | if err != nil { 44 | return nil, fmt.Errorf("%w, %w", errPayload, err) 45 | } 46 | 47 | if len(portRanges) == 0 { 48 | return nil, errPayload 49 | } 50 | 51 | return &Port{ 52 | Base: &Base{}, 53 | adapter: adapter, 54 | port: port, 55 | ruleType: ruleType, 56 | portRanges: portRanges, 57 | }, nil 58 | } 59 | 60 | var _ C.Rule = (*Port)(nil) 61 | -------------------------------------------------------------------------------- /test/.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | 9 | linters-settings: 10 | gci: 11 | sections: 12 | - standard 13 | - prefix(github.com/metacubex/mihomo) 14 | - default 15 | staticcheck: 16 | go: '1.19' 17 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | GOOS=darwin golangci-lint run ./... 3 | GOOS=linux golangci-lint run ./... 4 | 5 | test: 6 | go test -p 1 -v ./... 7 | 8 | benchmark: 9 | go test -benchmem -run=^$$ -bench . 10 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Mihomo testing suit 2 | 3 | ### Protocol testing suit 4 | 5 | * TCP pingpong test 6 | * UDP pingpong test 7 | * TCP large data test 8 | * UDP large data test 9 | 10 | ### Protocols 11 | 12 | - [x] Shadowsocks 13 | - [x] Normal 14 | - [x] ObfsHTTP 15 | - [x] ObfsTLS 16 | - [x] ObfsV2rayPlugin 17 | - [x] Vmess 18 | - [x] Normal 19 | - [x] AEAD 20 | - [x] HTTP 21 | - [x] HTTP2 22 | - [x] TLS 23 | - [x] Websocket 24 | - [x] Websocket TLS 25 | - [x] gRPC 26 | - [x] Trojan 27 | - [x] Normal 28 | - [x] gRPC 29 | - [x] Snell 30 | - [x] Normal 31 | - [x] ObfsHTTP 32 | - [x] ObfsTLS 33 | 34 | ### Features 35 | 36 | - [ ] DNS 37 | - [x] DNS Server 38 | - [x] FakeIP 39 | - [x] Host 40 | 41 | ### Command 42 | 43 | Prerequisite 44 | 45 | * docker (support Linux and macOS) 46 | 47 | ``` 48 | $ make test 49 | ``` 50 | 51 | benchmark (Linux) 52 | 53 | > Cannot represent the throughput of the protocol on your machine 54 | > but you can compare the corresponding throughput of the protocol on mihomo 55 | > (change chunkSize to measure the maximum throughput of mihomo on your machine) 56 | 57 | ``` 58 | $ make benchmark 59 | ``` 60 | -------------------------------------------------------------------------------- /test/config/hysteria.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": ":10002", 3 | "cert": "/home/ubuntu/my.crt", 4 | "key": "/home/ubuntu/my.key", 5 | "obfs": "fuck me till the daylight", 6 | "up_mbps": 100, 7 | "down_mbps": 100 8 | } -------------------------------------------------------------------------------- /test/config/snell-http.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = http 5 | -------------------------------------------------------------------------------- /test/config/snell-tls.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = tls 5 | -------------------------------------------------------------------------------- /test/config/snell.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | -------------------------------------------------------------------------------- /test/config/trojan-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "trojan", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "password": "example", 11 | "email": "grpc@example.com" 12 | } 13 | ] 14 | }, 15 | "streamSettings": { 16 | "network": "grpc", 17 | "security": "tls", 18 | "tlsSettings": { 19 | "certificates": [ 20 | { 21 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 22 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 23 | } 24 | ] 25 | }, 26 | "grpcSettings": { 27 | "serviceName": "example" 28 | } 29 | } 30 | } 31 | ], 32 | "outbounds": [ 33 | { 34 | "protocol": "freedom" 35 | } 36 | ], 37 | "log": { 38 | "loglevel": "debug" 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/trojan-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "disable_http_check": true, 6 | "password": [ 7 | "example" 8 | ], 9 | "websocket": { 10 | "enabled": true, 11 | "path": "/", 12 | "host": "example.org" 13 | }, 14 | "ssl": { 15 | "verify": true, 16 | "cert": "/fullchain.pem", 17 | "key": "/privkey.pem", 18 | "sni": "example.org" 19 | } 20 | } -------------------------------------------------------------------------------- /test/config/trojan-xtls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "trojan", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "password": "example", 11 | "email": "xtls@example.com", 12 | "flow": "xtls-rprx-direct", 13 | "level": 0 14 | } 15 | ] 16 | }, 17 | "streamSettings": { 18 | "network": "tcp", 19 | "security": "xtls", 20 | "xtlsSettings": { 21 | "certificates": [ 22 | { 23 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 24 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /test/config/trojan.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "password": [ 6 | "password" 7 | ], 8 | "log_level": 1, 9 | "ssl": { 10 | "cert": "/path/to/certificate.crt", 11 | "key": "/path/to/private.key", 12 | "key_password": "", 13 | "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384", 14 | "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", 15 | "prefer_server_cipher": true, 16 | "alpn": [ 17 | "http/1.1" 18 | ], 19 | "alpn_port_override": { 20 | "h2": 81 21 | }, 22 | "reuse_session": true, 23 | "session_ticket": false, 24 | "session_timeout": 600, 25 | "plain_http_response": "", 26 | "curves": "", 27 | "dhparam": "" 28 | }, 29 | "tcp": { 30 | "prefer_ipv4": false, 31 | "no_delay": true, 32 | "keep_alive": true, 33 | "reuse_port": false, 34 | "fast_open": false, 35 | "fast_open_qlen": 20 36 | }, 37 | "mysql": { 38 | "enabled": false 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/vless-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 11 | "level": 0, 12 | "email": "love@example.com" 13 | } 14 | ], 15 | "decryption": "none" 16 | }, 17 | "streamSettings": { 18 | "network": "tcp", 19 | "security": "tls", 20 | "tlsSettings": { 21 | "certificates": [ 22 | { 23 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 24 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /test/config/vless-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 11 | "level": 0, 12 | "email": "ws@example.com" 13 | } 14 | ], 15 | "decryption": "none" 16 | }, 17 | "streamSettings": { 18 | "network": "ws", 19 | "security": "tls", 20 | "tlsSettings": { 21 | "certificates": [ 22 | { 23 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 24 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 25 | } 26 | ] 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/config/vless-xtls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 11 | "email": "xtls@example.com", 12 | "flow": "xtls-rprx-direct", 13 | "level": 0 14 | } 15 | ], 16 | "decryption": "none" 17 | }, 18 | "streamSettings": { 19 | "network": "tcp", 20 | "security": "xtls", 21 | "xtlsSettings": { 22 | "certificates": [ 23 | { 24 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 25 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 26 | } 27 | ] 28 | } 29 | } 30 | } 31 | ], 32 | "outbounds": [ 33 | { 34 | "protocol": "freedom" 35 | } 36 | ], 37 | "log": { 38 | "loglevel": "debug" 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/vmess-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "grpc", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "grpcSettings": { 26 | "serviceName": "example!" 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /test/config/vmess-http2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "http", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "httpSettings": { 26 | "host": [ 27 | "example.org" 28 | ], 29 | "path": "/test" 30 | } 31 | } 32 | } 33 | ], 34 | "outbounds": [ 35 | { 36 | "protocol": "freedom" 37 | } 38 | ], 39 | "log": { 40 | "loglevel": "debug" 41 | } 42 | } -------------------------------------------------------------------------------- /test/config/vmess-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ], 33 | "log": { 34 | "loglevel": "debug" 35 | } 36 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-0rtt.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none", 17 | "wsSettings": { 18 | "maxEarlyData": 128, 19 | "earlyDataHeaderName": "Sec-WebSocket-Protocol" 20 | } 21 | } 22 | } 23 | ], 24 | "outbounds": [ 25 | { 26 | "protocol": "freedom" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /test/config/vmess-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none" 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /test/config/vmess.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp" 16 | } 17 | } 18 | ], 19 | "outbounds": [ 20 | { 21 | "protocol": "freedom" 22 | } 23 | ], 24 | "log": { 25 | "loglevel": "debug" 26 | } 27 | } -------------------------------------------------------------------------------- /test/config/xray-shadowsocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "shadowsocks", 7 | "settings": { 8 | "network": "tcp,udp", 9 | "clients": [ 10 | { 11 | "method": "aes-128-gcm", 12 | "level": 0, 13 | "password": "FzcLbKs2dY9mhL" 14 | } 15 | ] 16 | } 17 | } 18 | ], 19 | "outbounds": [ 20 | { 21 | "protocol": "freedom" 22 | } 23 | ], 24 | "log": { 25 | "loglevel": "debug" 26 | } 27 | } -------------------------------------------------------------------------------- /test/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func Listen(network, address string) (net.Listener, error) { 10 | lc := net.ListenConfig{} 11 | 12 | var lastErr error 13 | for i := 0; i < 5; i++ { 14 | l, err := lc.Listen(context.Background(), network, address) 15 | if err == nil { 16 | return l, nil 17 | } 18 | 19 | lastErr = err 20 | time.Sleep(time.Millisecond * 200) 21 | } 22 | return nil, lastErr 23 | } 24 | 25 | func ListenPacket(network, address string) (net.PacketConn, error) { 26 | var lastErr error 27 | for i := 0; i < 5; i++ { 28 | l, err := net.ListenPacket(network, address) 29 | if err == nil { 30 | return l, nil 31 | } 32 | 33 | lastErr = err 34 | time.Sleep(time.Millisecond * 200) 35 | } 36 | return nil, lastErr 37 | } 38 | 39 | func TCPing(addr string) bool { 40 | for i := 0; i < 10; i++ { 41 | conn, err := net.Dial("tcp", addr) 42 | if err == nil { 43 | conn.Close() 44 | return true 45 | } 46 | time.Sleep(time.Millisecond * 500) 47 | } 48 | 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /test/util_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "net/netip" 8 | ) 9 | 10 | func defaultRouteIP() (netip.Addr, error) { 11 | return netip.Addr{}, errors.New("not supported") 12 | } 13 | -------------------------------------------------------------------------------- /transport/anytls/session/frame.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | const ( // cmds 8 | cmdWaste = 0 // Paddings 9 | cmdSYN = 1 // stream open 10 | cmdPSH = 2 // data push 11 | cmdFIN = 3 // stream close, a.k.a EOF mark 12 | cmdSettings = 4 // Settings (Client send to Server) 13 | cmdAlert = 5 // Alert 14 | cmdUpdatePaddingScheme = 6 // update padding scheme 15 | // Since version 2 16 | cmdSYNACK = 7 // Server reports to the client that the stream has been opened 17 | cmdHeartRequest = 8 // Keep alive command 18 | cmdHeartResponse = 9 // Keep alive command 19 | cmdServerSettings = 10 // Settings (Server send to client) 20 | ) 21 | 22 | const ( 23 | headerOverHeadSize = 1 + 4 + 2 24 | ) 25 | 26 | // frame defines a packet from or to be multiplexed into a single connection 27 | type frame struct { 28 | cmd byte // 1 29 | sid uint32 // 4 30 | data []byte // 2 + len(data) 31 | } 32 | 33 | func newFrame(cmd byte, sid uint32) frame { 34 | return frame{cmd: cmd, sid: sid} 35 | } 36 | 37 | type rawHeader [headerOverHeadSize]byte 38 | 39 | func (h rawHeader) Cmd() byte { 40 | return h[0] 41 | } 42 | 43 | func (h rawHeader) StreamID() uint32 { 44 | return binary.BigEndian.Uint32(h[1:]) 45 | } 46 | 47 | func (h rawHeader) Length() uint16 { 48 | return binary.BigEndian.Uint16(h[5:]) 49 | } 50 | -------------------------------------------------------------------------------- /transport/anytls/util/deadline.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func NewDeadlineWatcher(ddl time.Duration, timeOut func()) (done func()) { 9 | t := time.NewTimer(ddl) 10 | closeCh := make(chan struct{}) 11 | go func() { 12 | defer t.Stop() 13 | select { 14 | case <-closeCh: 15 | case <-t.C: 16 | timeOut() 17 | } 18 | }() 19 | var once sync.Once 20 | return func() { 21 | once.Do(func() { 22 | close(closeCh) 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /transport/anytls/util/routine.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "runtime/debug" 6 | "time" 7 | 8 | "github.com/metacubex/mihomo/log" 9 | ) 10 | 11 | func StartRoutine(ctx context.Context, d time.Duration, f func()) { 12 | go func() { 13 | defer func() { 14 | if r := recover(); r != nil { 15 | log.Errorln("[BUG] %v %s", r, string(debug.Stack())) 16 | } 17 | }() 18 | for { 19 | time.Sleep(d) 20 | f() 21 | select { 22 | case <-ctx.Done(): 23 | return 24 | default: 25 | } 26 | } 27 | }() 28 | } 29 | -------------------------------------------------------------------------------- /transport/anytls/util/string_map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type StringMap map[string]string 8 | 9 | func (s StringMap) ToBytes() []byte { 10 | var lines []string 11 | for k, v := range s { 12 | lines = append(lines, k+"="+v) 13 | } 14 | return []byte(strings.Join(lines, "\n")) 15 | } 16 | 17 | func StringMapFromBytes(b []byte) StringMap { 18 | var m = make(StringMap) 19 | var lines = strings.Split(string(b), "\n") 20 | for _, line := range lines { 21 | v := strings.SplitN(line, "=", 2) 22 | if len(v) == 2 { 23 | m[v[0]] = v[1] 24 | } 25 | } 26 | return m 27 | } 28 | -------------------------------------------------------------------------------- /transport/anytls/util/type.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type DialOutFunc func(ctx context.Context) (net.Conn, error) 9 | -------------------------------------------------------------------------------- /transport/gun/transport.go: -------------------------------------------------------------------------------- 1 | package gun 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | 8 | "golang.org/x/net/http2" 9 | ) 10 | 11 | type TransportWrap struct { 12 | *http2.Transport 13 | ctx context.Context 14 | cancel context.CancelFunc 15 | closeOnce sync.Once 16 | } 17 | 18 | func (tw *TransportWrap) Close() error { 19 | tw.closeOnce.Do(func() { 20 | tw.cancel() 21 | closeTransport(tw.Transport) 22 | }) 23 | return nil 24 | } 25 | 26 | type netAddr struct { 27 | remoteAddr net.Addr 28 | localAddr net.Addr 29 | } 30 | 31 | func (addr netAddr) RemoteAddr() net.Addr { 32 | return addr.remoteAddr 33 | } 34 | 35 | func (addr netAddr) LocalAddr() net.Addr { 36 | return addr.localAddr 37 | } 38 | -------------------------------------------------------------------------------- /transport/gun/utils.go: -------------------------------------------------------------------------------- 1 | package gun 2 | 3 | func UVarintLen(x uint64) int { 4 | switch { 5 | case x < 1<<(7*1): 6 | return 1 7 | case x < 1<<(7*2): 8 | return 2 9 | case x < 1<<(7*3): 10 | return 3 11 | case x < 1<<(7*4): 12 | return 4 13 | case x < 1<<(7*5): 14 | return 5 15 | case x < 1<<(7*6): 16 | return 6 17 | case x < 1<<(7*7): 18 | return 7 19 | case x < 1<<(7*8): 20 | return 8 21 | case x < 1<<(7*9): 22 | return 9 23 | default: 24 | return 10 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /transport/hysteria/conns/faketcp/LICENSE: -------------------------------------------------------------------------------- 1 | Grabbed from https://github.com/xtaci/tcpraw with modifications -------------------------------------------------------------------------------- /transport/hysteria/conns/faketcp/tcp_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !linux || no_fake_tcp 2 | // +build !linux no_fake_tcp 3 | 4 | package faketcp 5 | 6 | import ( 7 | "errors" 8 | "net" 9 | ) 10 | 11 | type TCPConn struct{ *net.UDPConn } 12 | 13 | // Dial connects to the remote TCP port, 14 | // and returns a single packet-oriented connection 15 | func Dial(network, address string) (*TCPConn, error) { 16 | return nil, errors.New("faketcp is not supported on this platform") 17 | } 18 | 19 | func Listen(network, address string) (*TCPConn, error) { 20 | return nil, errors.New("faketcp is not supported on this platform") 21 | } 22 | -------------------------------------------------------------------------------- /transport/hysteria/obfs/dummy.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type DummyObfuscator struct{} 4 | 5 | func NewDummyObfuscator() *DummyObfuscator { 6 | return &DummyObfuscator{} 7 | } 8 | 9 | func (x *DummyObfuscator) Deobfuscate(in []byte, out []byte) int { 10 | if len(out) < len(in) { 11 | return 0 12 | } 13 | return copy(out, in) 14 | } 15 | 16 | func (x *DummyObfuscator) Obfuscate(in []byte, out []byte) int { 17 | return copy(out, in) 18 | } 19 | -------------------------------------------------------------------------------- /transport/hysteria/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type Obfuscator interface { 4 | Deobfuscate(in []byte, out []byte) int 5 | Obfuscate(in []byte, out []byte) int 6 | } 7 | -------------------------------------------------------------------------------- /transport/hysteria/obfs/xplus.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha256" 6 | ) 7 | 8 | // [salt][obfuscated payload] 9 | 10 | const saltLen = 16 11 | 12 | type XPlusObfuscator struct { 13 | Key []byte 14 | } 15 | 16 | func NewXPlusObfuscator(key []byte) *XPlusObfuscator { 17 | return &XPlusObfuscator{ 18 | Key: key, 19 | } 20 | } 21 | 22 | func (x *XPlusObfuscator) Deobfuscate(in []byte, out []byte) int { 23 | pLen := len(in) - saltLen 24 | if pLen <= 0 || len(out) < pLen { 25 | // Invalid 26 | return 0 27 | } 28 | key := sha256.Sum256(append(x.Key, in[:saltLen]...)) 29 | // Deobfuscate the payload 30 | for i, c := range in[saltLen:] { 31 | out[i] = c ^ key[i%sha256.Size] 32 | } 33 | return pLen 34 | } 35 | 36 | func (x *XPlusObfuscator) Obfuscate(in []byte, out []byte) int { 37 | _, _ = rand.Read(out[:saltLen]) // salt 38 | // Obfuscate the payload 39 | key := sha256.Sum256(append(x.Key, out[:saltLen]...)) 40 | for i, c := range in { 41 | out[i+saltLen] = c ^ key[i%sha256.Size] 42 | } 43 | return len(in) + saltLen 44 | } 45 | -------------------------------------------------------------------------------- /transport/hysteria/obfs/xplus_test.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestXPlusObfuscator(t *testing.T) { 9 | x := NewXPlusObfuscator([]byte("Vaundy")) 10 | tests := []struct { 11 | name string 12 | p []byte 13 | }{ 14 | {name: "1", p: []byte("HelloWorld")}, 15 | {name: "2", p: []byte("Regret is just a horrible attempt at time travel that ends with you feeling like crap")}, 16 | {name: "3", p: []byte("To be, or not to be, that is the question:\nWhether 'tis nobler in the mind to suffer\n" + 17 | "The slings and arrows of outrageous fortune,\nOr to take arms against a sea of troubles\n" + 18 | "And by opposing end them. To die—to sleep,\nNo more; and by a sleep to say we end")}, 19 | {name: "empty", p: []byte("")}, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | buf := make([]byte, 10240) 24 | n := x.Obfuscate(tt.p, buf) 25 | n2 := x.Deobfuscate(buf[:n], buf[n:]) 26 | if !bytes.Equal(tt.p, buf[n:n+n2]) { 27 | t.Errorf("Inconsistent deobfuscate result: got %v, want %v", buf[n:n+n2], tt.p) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /transport/hysteria/pmtud_fix/avail.go: -------------------------------------------------------------------------------- 1 | //go:build linux || windows || darwin 2 | 3 | package pmtud_fix 4 | 5 | const ( 6 | DisablePathMTUDiscovery = false 7 | ) 8 | -------------------------------------------------------------------------------- /transport/hysteria/pmtud_fix/unavail.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows && !darwin 2 | 3 | package pmtud_fix 4 | 5 | const ( 6 | DisablePathMTUDiscovery = true 7 | ) 8 | -------------------------------------------------------------------------------- /transport/hysteria/utils/misc.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | func SplitHostPort(hostport string) (string, uint16, error) { 10 | host, port, err := net.SplitHostPort(hostport) 11 | if err != nil { 12 | return "", 0, err 13 | } 14 | portUint, err := strconv.ParseUint(port, 10, 16) 15 | if err != nil { 16 | return "", 0, err 17 | } 18 | return host, uint16(portUint), err 19 | } 20 | 21 | func ParseIPZone(s string) (net.IP, string) { 22 | s, zone := splitHostZone(s) 23 | return net.ParseIP(s), zone 24 | } 25 | 26 | func splitHostZone(s string) (host, zone string) { 27 | if i := last(s, '%'); i > 0 { 28 | host, zone = s[:i], s[i+1:] 29 | } else { 30 | host = s 31 | } 32 | return 33 | } 34 | 35 | func last(s string, b byte) int { 36 | i := len(s) 37 | for i--; i >= 0; i-- { 38 | if s[i] == b { 39 | break 40 | } 41 | } 42 | return i 43 | } 44 | 45 | type PacketDialer interface { 46 | ListenPacket(rAddr net.Addr) (net.PacketConn, error) 47 | Context() context.Context 48 | RemoteAddr(host string) (net.Addr, error) 49 | } 50 | -------------------------------------------------------------------------------- /transport/restls/restls.go: -------------------------------------------------------------------------------- 1 | package restls 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | tls "github.com/3andne/restls-client-go" 8 | ) 9 | 10 | const ( 11 | Mode string = "restls" 12 | ) 13 | 14 | type Restls struct { 15 | *tls.UConn 16 | } 17 | 18 | func (r *Restls) Upstream() any { 19 | return r.UConn.NetConn() 20 | } 21 | 22 | type Config = tls.Config 23 | 24 | var NewRestlsConfig = tls.NewRestlsConfig 25 | 26 | // NewRestls return a Restls Connection 27 | func NewRestls(ctx context.Context, conn net.Conn, config *Config) (net.Conn, error) { 28 | clientHellowID := tls.HelloChrome_Auto 29 | if config != nil { 30 | clientIDPtr := config.ClientID.Load() 31 | if clientIDPtr != nil { 32 | clientHellowID = *clientIDPtr 33 | } 34 | } 35 | restls := &Restls{ 36 | UConn: tls.UClient(conn, config, clientHellowID), 37 | } 38 | if err := restls.HandshakeContext(ctx); err != nil { 39 | return nil, err 40 | } 41 | 42 | return restls, nil 43 | } 44 | -------------------------------------------------------------------------------- /transport/shadowsocks/README.md: -------------------------------------------------------------------------------- 1 | ## Embedded go-shadowsocks2 2 | 3 | from https://github.com/Dreamacro/go-shadowsocks2 4 | 5 | origin https://github.com/riobard/go-shadowsocks2 6 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowstream/old_chacha20.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/cipher" 5 | 6 | "github.com/metacubex/chacha" 7 | ) 8 | 9 | type chacha20key []byte 10 | 11 | func (k chacha20key) IVSize() int { 12 | return chacha.NonceSize 13 | } 14 | func (k chacha20key) Encrypter(iv []byte) cipher.Stream { 15 | c, _ := chacha.NewChaCha20(iv, k) 16 | return c 17 | } 18 | func (k chacha20key) Decrypter(iv []byte) cipher.Stream { 19 | return k.Encrypter(iv) 20 | } 21 | func ChaCha20(key []byte) (Cipher, error) { 22 | return chacha20key(key), nil 23 | } 24 | -------------------------------------------------------------------------------- /transport/ssr/obfs/base.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type Base struct { 4 | Host string 5 | Port int 6 | Key []byte 7 | IVSize int 8 | Param string 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/http_post.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | func init() { 4 | register("http_post", newHTTPPost, 0) 5 | } 6 | 7 | func newHTTPPost(b *Base) Obfs { 8 | return &httpObfs{Base: b, post: true} 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | var ( 10 | errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") 11 | errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") 12 | errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") 13 | ) 14 | 15 | type authData struct { 16 | clientID [32]byte 17 | } 18 | 19 | type Obfs interface { 20 | StreamConn(net.Conn) net.Conn 21 | } 22 | 23 | type obfsCreator func(b *Base) Obfs 24 | 25 | var obfsList = make(map[string]struct { 26 | overhead int 27 | new obfsCreator 28 | }) 29 | 30 | func register(name string, c obfsCreator, o int) { 31 | obfsList[name] = struct { 32 | overhead int 33 | new obfsCreator 34 | }{overhead: o, new: c} 35 | } 36 | 37 | func PickObfs(name string, b *Base) (Obfs, int, error) { 38 | if choice, ok := obfsList[name]; ok { 39 | return choice.new(b), choice.overhead, nil 40 | } 41 | return nil, 0, fmt.Errorf("Obfs %s not supported", name) 42 | } 43 | -------------------------------------------------------------------------------- /transport/ssr/obfs/plain.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import "net" 4 | 5 | type plain struct{} 6 | 7 | func init() { 8 | register("plain", newPlain, 0) 9 | } 10 | 11 | func newPlain(b *Base) Obfs { 12 | return &plain{} 13 | } 14 | 15 | func (p *plain) StreamConn(c net.Conn) net.Conn { return c } 16 | -------------------------------------------------------------------------------- /transport/ssr/protocol/auth_aes128_md5.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "github.com/metacubex/mihomo/transport/ssr/tools" 4 | 5 | func init() { 6 | register("auth_aes128_md5", newAuthAES128MD5, 9) 7 | } 8 | 9 | func newAuthAES128MD5(b *Base) Protocol { 10 | a := &authAES128{ 11 | Base: b, 12 | authData: &authData{}, 13 | authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, 14 | userData: &userData{}, 15 | } 16 | a.initUserData() 17 | return a 18 | } 19 | -------------------------------------------------------------------------------- /transport/ssr/protocol/origin.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | N "github.com/metacubex/mihomo/common/net" 8 | ) 9 | 10 | type origin struct{} 11 | 12 | func init() { register("origin", newOrigin, 0) } 13 | 14 | func newOrigin(b *Base) Protocol { return &origin{} } 15 | 16 | func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } 17 | 18 | func (o *origin) PacketConn(c N.EnhancePacketConn) N.EnhancePacketConn { return c } 19 | 20 | func (o *origin) Decode(dst, src *bytes.Buffer) error { 21 | dst.ReadFrom(src) 22 | return nil 23 | } 24 | 25 | func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { 26 | buf.Write(b) 27 | return nil 28 | } 29 | 30 | func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } 31 | 32 | func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { 33 | buf.Write(b) 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /transport/ssr/protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "net" 5 | 6 | N "github.com/metacubex/mihomo/common/net" 7 | "github.com/metacubex/mihomo/common/pool" 8 | ) 9 | 10 | type PacketConn struct { 11 | N.EnhancePacketConn 12 | Protocol 13 | } 14 | 15 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 16 | buf := pool.GetBuffer() 17 | defer pool.PutBuffer(buf) 18 | err := c.EncodePacket(buf, b) 19 | if err != nil { 20 | return 0, err 21 | } 22 | _, err = c.EnhancePacketConn.WriteTo(buf.Bytes(), addr) 23 | return len(b), err 24 | } 25 | 26 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 27 | n, addr, err := c.EnhancePacketConn.ReadFrom(b) 28 | if err != nil { 29 | return n, addr, err 30 | } 31 | decoded, err := c.DecodePacket(b[:n]) 32 | if err != nil { 33 | return n, addr, err 34 | } 35 | copy(b, decoded) 36 | return len(decoded), addr, nil 37 | } 38 | 39 | func (c *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { 40 | data, put, addr, err = c.EnhancePacketConn.WaitReadFrom() 41 | if err != nil { 42 | return 43 | } 44 | data, err = c.DecodePacket(data) 45 | if err != nil { 46 | if put != nil { 47 | put() 48 | } 49 | data = nil 50 | put = nil 51 | return 52 | } 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /transport/ssr/protocol/stream.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/metacubex/mihomo/common/pool" 8 | ) 9 | 10 | type Conn struct { 11 | net.Conn 12 | Protocol 13 | decoded bytes.Buffer 14 | underDecoded bytes.Buffer 15 | } 16 | 17 | func (c *Conn) Read(b []byte) (int, error) { 18 | if c.decoded.Len() > 0 { 19 | return c.decoded.Read(b) 20 | } 21 | 22 | buf := pool.Get(pool.RelayBufferSize) 23 | defer pool.Put(buf) 24 | n, err := c.Conn.Read(buf) 25 | if err != nil { 26 | return 0, err 27 | } 28 | c.underDecoded.Write(buf[:n]) 29 | err = c.Decode(&c.decoded, &c.underDecoded) 30 | if err != nil { 31 | return 0, err 32 | } 33 | n, _ = c.decoded.Read(b) 34 | return n, nil 35 | } 36 | 37 | func (c *Conn) Write(b []byte) (int, error) { 38 | bLength := len(b) 39 | buf := pool.GetBuffer() 40 | defer pool.PutBuffer(buf) 41 | err := c.Encode(buf, b) 42 | if err != nil { 43 | return 0, err 44 | } 45 | _, err = c.Conn.Write(buf.Bytes()) 46 | if err != nil { 47 | return 0, err 48 | } 49 | return bLength, nil 50 | } 51 | -------------------------------------------------------------------------------- /transport/ssr/tools/bufPool.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | ) 8 | 9 | func AppendRandBytes(b *bytes.Buffer, length int) { 10 | b.ReadFrom(io.LimitReader(rand.Reader, int64(length))) 11 | } 12 | -------------------------------------------------------------------------------- /transport/ssr/tools/crypto.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | ) 8 | 9 | const HmacSHA1Len = 10 10 | 11 | func HmacMD5(key, data []byte) []byte { 12 | hmacMD5 := hmac.New(md5.New, key) 13 | hmacMD5.Write(data) 14 | return hmacMD5.Sum(nil) 15 | } 16 | 17 | func HmacSHA1(key, data []byte) []byte { 18 | hmacSHA1 := hmac.New(sha1.New, key) 19 | hmacSHA1.Write(data) 20 | return hmacSHA1.Sum(nil) 21 | } 22 | 23 | func MD5Sum(b []byte) []byte { 24 | h := md5.New() 25 | h.Write(b) 26 | return h.Sum(nil) 27 | } 28 | 29 | func SHA1Sum(b []byte) []byte { 30 | h := sha1.New() 31 | h.Write(b) 32 | return h.Sum(nil) 33 | } 34 | -------------------------------------------------------------------------------- /transport/ssr/tools/random.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/metacubex/mihomo/common/pool" 7 | ) 8 | 9 | // XorShift128Plus - a pseudorandom number generator 10 | type XorShift128Plus struct { 11 | s [2]uint64 12 | } 13 | 14 | func (r *XorShift128Plus) Next() uint64 { 15 | x := r.s[0] 16 | y := r.s[1] 17 | r.s[0] = y 18 | x ^= x << 23 19 | x ^= y ^ (x >> 17) ^ (y >> 26) 20 | r.s[1] = x 21 | return x + y 22 | } 23 | 24 | func (r *XorShift128Plus) InitFromBin(bin []byte) { 25 | var full []byte 26 | if len(bin) < 16 { 27 | full := pool.Get(16)[:0] 28 | defer pool.Put(full) 29 | full = append(full, bin...) 30 | for len(full) < 16 { 31 | full = append(full, 0) 32 | } 33 | } else { 34 | full = bin 35 | } 36 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 37 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 38 | } 39 | 40 | func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { 41 | var full []byte 42 | if len(bin) < 16 { 43 | full := pool.Get(16)[:0] 44 | defer pool.Put(full) 45 | full = append(full, bin...) 46 | for len(full) < 16 { 47 | full = append(full, 0) 48 | } 49 | } 50 | full = bin 51 | binary.LittleEndian.PutUint16(full, uint16(length)) 52 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 53 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 54 | for i := 0; i < 4; i++ { 55 | r.Next() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /transport/tuic/common/type.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "errors" 7 | "net" 8 | "time" 9 | 10 | N "github.com/metacubex/mihomo/common/net" 11 | C "github.com/metacubex/mihomo/constant" 12 | 13 | "github.com/metacubex/quic-go" 14 | ) 15 | 16 | var ( 17 | ClientClosed = errors.New("tuic: client closed") 18 | TooManyOpenStreams = errors.New("tuic: too many open streams") 19 | ) 20 | 21 | type DialFunc func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) 22 | 23 | type Client interface { 24 | DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) 25 | ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) 26 | OpenStreams() int64 27 | DialerRef() C.Dialer 28 | LastVisited() time.Time 29 | SetLastVisited(last time.Time) 30 | Close() 31 | } 32 | 33 | type ServerHandler interface { 34 | AuthOk() bool 35 | HandleTimeout() 36 | HandleStream(conn *N.BufferedConn) (err error) 37 | HandleMessage(message []byte) (err error) 38 | HandleUniStream(reader *bufio.Reader) (err error) 39 | } 40 | 41 | type UdpRelayMode uint8 42 | 43 | const ( 44 | QUIC UdpRelayMode = iota 45 | NATIVE 46 | ) 47 | -------------------------------------------------------------------------------- /transport/tuic/congestion/bandwidth.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "github.com/metacubex/quic-go/congestion" 8 | ) 9 | 10 | // Bandwidth of a connection 11 | type Bandwidth uint64 12 | 13 | const infBandwidth Bandwidth = math.MaxUint64 14 | 15 | const ( 16 | // BitsPerSecond is 1 bit per second 17 | BitsPerSecond Bandwidth = 1 18 | // BytesPerSecond is 1 byte per second 19 | BytesPerSecond = 8 * BitsPerSecond 20 | ) 21 | 22 | // BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta 23 | func BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth { 24 | return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond 25 | } 26 | -------------------------------------------------------------------------------- /transport/tuic/congestion/clock.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import "time" 4 | 5 | // A Clock returns the current time 6 | type Clock interface { 7 | Now() time.Time 8 | } 9 | 10 | // DefaultClock implements the Clock interface using the Go stdlib clock. 11 | type DefaultClock struct{} 12 | 13 | var _ Clock = DefaultClock{} 14 | 15 | // Now gets the current time 16 | func (DefaultClock) Now() time.Time { 17 | return time.Now() 18 | } 19 | -------------------------------------------------------------------------------- /transport/tuic/congestion/minmax.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "math" 5 | "time" 6 | ) 7 | 8 | // InfDuration is a duration of infinite length 9 | const InfDuration = time.Duration(math.MaxInt64) 10 | 11 | // MinNonZeroDuration return the minimum duration that's not zero. 12 | func MinNonZeroDuration(a, b time.Duration) time.Duration { 13 | if a == 0 { 14 | return b 15 | } 16 | if b == 0 { 17 | return a 18 | } 19 | return Min(a, b) 20 | } 21 | 22 | // AbsDuration returns the absolute value of a time duration 23 | func AbsDuration(d time.Duration) time.Duration { 24 | if d >= 0 { 25 | return d 26 | } 27 | return -d 28 | } 29 | 30 | // MinTime returns the earlier time 31 | func MinTime(a, b time.Time) time.Time { 32 | if a.After(b) { 33 | return b 34 | } 35 | return a 36 | } 37 | 38 | // MinNonZeroTime returns the earlist time that is not time.Time{} 39 | // If both a and b are time.Time{}, it returns time.Time{} 40 | func MinNonZeroTime(a, b time.Time) time.Time { 41 | if a.IsZero() { 42 | return b 43 | } 44 | if b.IsZero() { 45 | return a 46 | } 47 | return MinTime(a, b) 48 | } 49 | 50 | // MaxTime returns the later time 51 | func MaxTime(a, b time.Time) time.Time { 52 | if a.After(b) { 53 | return a 54 | } 55 | return b 56 | } 57 | -------------------------------------------------------------------------------- /transport/tuic/congestion/minmax_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package congestion 4 | 5 | import "golang.org/x/exp/constraints" 6 | 7 | func Max[T constraints.Ordered](a, b T) T { 8 | if a < b { 9 | return b 10 | } 11 | return a 12 | } 13 | 14 | func Min[T constraints.Ordered](a, b T) T { 15 | if a < b { 16 | return a 17 | } 18 | return b 19 | } 20 | -------------------------------------------------------------------------------- /transport/tuic/congestion/minmax_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package congestion 4 | 5 | import "cmp" 6 | 7 | func Max[T cmp.Ordered](a, b T) T { 8 | return max(a, b) 9 | } 10 | 11 | func Min[T cmp.Ordered](a, b T) T { 12 | return min(a, b) 13 | } 14 | -------------------------------------------------------------------------------- /transport/tuic/congestion_v2/bandwidth.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "github.com/metacubex/quic-go/congestion" 8 | ) 9 | 10 | const ( 11 | infBandwidth = Bandwidth(math.MaxUint64) 12 | ) 13 | 14 | // Bandwidth of a connection 15 | type Bandwidth uint64 16 | 17 | const ( 18 | // BitsPerSecond is 1 bit per second 19 | BitsPerSecond Bandwidth = 1 20 | // BytesPerSecond is 1 byte per second 21 | BytesPerSecond = 8 * BitsPerSecond 22 | ) 23 | 24 | // BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta 25 | func BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth { 26 | return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond 27 | } 28 | -------------------------------------------------------------------------------- /transport/tuic/congestion_v2/clock.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import "time" 4 | 5 | // A Clock returns the current time 6 | type Clock interface { 7 | Now() time.Time 8 | } 9 | 10 | // DefaultClock implements the Clock interface using the Go stdlib clock. 11 | type DefaultClock struct{} 12 | 13 | var _ Clock = DefaultClock{} 14 | 15 | // Now gets the current time 16 | func (DefaultClock) Now() time.Time { 17 | return time.Now() 18 | } 19 | -------------------------------------------------------------------------------- /transport/tuic/congestion_v2/minmax_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package congestion 4 | 5 | import "golang.org/x/exp/constraints" 6 | 7 | func Max[T constraints.Ordered](a, b T) T { 8 | if a < b { 9 | return b 10 | } 11 | return a 12 | } 13 | 14 | func Min[T constraints.Ordered](a, b T) T { 15 | if a < b { 16 | return a 17 | } 18 | return b 19 | } 20 | -------------------------------------------------------------------------------- /transport/tuic/congestion_v2/minmax_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package congestion 4 | 5 | import "cmp" 6 | 7 | func Max[T cmp.Ordered](a, b T) T { 8 | return max(a, b) 9 | } 10 | 11 | func Min[T cmp.Ordered](a, b T) T { 12 | return min(a, b) 13 | } 14 | -------------------------------------------------------------------------------- /transport/tuic/tuic.go: -------------------------------------------------------------------------------- 1 | package tuic 2 | 3 | import ( 4 | C "github.com/metacubex/mihomo/constant" 5 | "github.com/metacubex/mihomo/transport/tuic/common" 6 | v4 "github.com/metacubex/mihomo/transport/tuic/v4" 7 | v5 "github.com/metacubex/mihomo/transport/tuic/v5" 8 | ) 9 | 10 | type ClientOptionV4 = v4.ClientOption 11 | type ClientOptionV5 = v5.ClientOption 12 | 13 | type Client = common.Client 14 | 15 | func NewClientV4(clientOption *ClientOptionV4, udp bool, dialerRef C.Dialer) Client { 16 | return v4.NewClient(clientOption, udp, dialerRef) 17 | } 18 | 19 | func NewClientV5(clientOption *ClientOptionV5, udp bool, dialerRef C.Dialer) Client { 20 | return v5.NewClient(clientOption, udp, dialerRef) 21 | } 22 | 23 | type DialFunc = common.DialFunc 24 | 25 | var TooManyOpenStreams = common.TooManyOpenStreams 26 | 27 | const DefaultStreamReceiveWindow = common.DefaultStreamReceiveWindow 28 | const DefaultConnectionReceiveWindow = common.DefaultConnectionReceiveWindow 29 | 30 | var GenTKN = v4.GenTKN 31 | var PacketOverHeadV4 = v4.PacketOverHead 32 | var PacketOverHeadV5 = v5.PacketOverHead 33 | var MaxFragSizeV5 = v5.MaxFragSize 34 | 35 | type UdpRelayMode = common.UdpRelayMode 36 | 37 | const ( 38 | QUIC = common.QUIC 39 | NATIVE = common.NATIVE 40 | ) 41 | -------------------------------------------------------------------------------- /transport/vless/config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mihomo.transport.vless; 4 | option csharp_namespace = "Mihomo.Transport.Vless"; 5 | option go_package = "github.com/metacubex/mihomo/transport/vless"; 6 | option java_package = "com.mihomo.transport.vless"; 7 | option java_multiple_files = true; 8 | 9 | message Addons { 10 | string Flow = 1; 11 | bytes Seed = 2; 12 | } -------------------------------------------------------------------------------- /transport/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | 7 | "github.com/gofrs/uuid/v5" 8 | ) 9 | 10 | // ID cmdKey length 11 | const ( 12 | IDBytesLen = 16 13 | ) 14 | 15 | // The ID of en entity, in the form of a UUID. 16 | type ID struct { 17 | UUID *uuid.UUID 18 | CmdKey []byte 19 | } 20 | 21 | // newID returns an ID with given UUID. 22 | func newID(uuid *uuid.UUID) *ID { 23 | id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)} 24 | md5hash := md5.New() 25 | md5hash.Write(uuid.Bytes()) 26 | md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) 27 | md5hash.Sum(id.CmdKey[:0]) 28 | return id 29 | } 30 | 31 | func nextID(u *uuid.UUID) *uuid.UUID { 32 | md5hash := md5.New() 33 | md5hash.Write(u.Bytes()) 34 | md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) 35 | var newid uuid.UUID 36 | for { 37 | md5hash.Sum(newid[:0]) 38 | if !bytes.Equal(newid.Bytes(), u.Bytes()) { 39 | return &newid 40 | } 41 | md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) 42 | } 43 | } 44 | 45 | func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { 46 | alterIDs := make([]*ID, alterIDCount) 47 | prevID := primary.UUID 48 | for idx := range alterIDs { 49 | newid := nextID(prevID) 50 | alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} 51 | prevID = newid 52 | } 53 | alterIDs = append(alterIDs, primary) 54 | return alterIDs 55 | } 56 | -------------------------------------------------------------------------------- /transport/vmess/websocket_go120.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package vmess 4 | 5 | // Golang1.20's net.http Flush will mistakenly call w.WriteHeader(StatusOK) internally after w.WriteHeader(http.StatusSwitchingProtocols) 6 | // https://github.com/golang/go/issues/59564 7 | const writeHeaderShouldFlush = false 8 | -------------------------------------------------------------------------------- /transport/vmess/websocket_go121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package vmess 4 | 5 | const writeHeaderShouldFlush = true 6 | -------------------------------------------------------------------------------- /tunnel/statistic/patch_android.go: -------------------------------------------------------------------------------- 1 | //go:build android && cmfa 2 | 3 | package statistic 4 | 5 | func (m *Manager) Total() (up, down int64) { 6 | return m.uploadTotal.Load(), m.downloadTotal.Load() 7 | } 8 | --------------------------------------------------------------------------------