Repository: v2ray/v2ray-core Branch: master Commit: d80440f3d57b Files: 752 Total size: 14.0 MB Directory structure: gitextract_4w26szfn/ ├── .dev/ │ └── protoc/ │ ├── linux/ │ │ └── protoc │ └── macos/ │ └── protoc ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_cn.md │ │ ├── bug_en.md │ │ └── other_en.md │ ├── ISSUE_TEMPLATE.md │ ├── dependabot.yml │ ├── linters/ │ │ └── .golangci.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── codeql-analysis.yml │ ├── coverage.yml │ ├── linter.yml │ ├── sign.yml │ ├── stale.yml │ ├── test.yml │ └── updateGeofile.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── WORKSPACE ├── annotations.go ├── app/ │ ├── app.go │ ├── commander/ │ │ ├── commander.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── outbound.go │ │ └── service.go │ ├── dispatcher/ │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── default.go │ │ ├── dispatcher.go │ │ ├── errors.generated.go │ │ ├── sniffer.go │ │ ├── stats.go │ │ └── stats_test.go │ ├── dns/ │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── dns.go │ │ ├── dnscommon.go │ │ ├── dnscommon_test.go │ │ ├── dohdns.go │ │ ├── errors.generated.go │ │ ├── hosts.go │ │ ├── hosts_test.go │ │ ├── nameserver.go │ │ ├── nameserver_test.go │ │ ├── server.go │ │ ├── server_test.go │ │ └── udpns.go │ ├── log/ │ │ ├── command/ │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── config_grpc.pb.go │ │ │ └── errors.generated.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── log.go │ │ ├── log_creator.go │ │ └── log_test.go │ ├── policy/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── manager.go │ │ ├── manager_test.go │ │ └── policy.go │ ├── proxyman/ │ │ ├── command/ │ │ │ ├── command.go │ │ │ ├── command.pb.go │ │ │ ├── command.proto │ │ │ ├── command_grpc.pb.go │ │ │ ├── doc.go │ │ │ └── errors.generated.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── inbound/ │ │ │ ├── always.go │ │ │ ├── dynamic.go │ │ │ ├── errors.generated.go │ │ │ ├── inbound.go │ │ │ └── worker.go │ │ ├── outbound/ │ │ │ ├── errors.generated.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ └── outbound.go │ │ └── proxyman.go │ ├── reverse/ │ │ ├── bridge.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── portal.go │ │ ├── portal_test.go │ │ └── reverse.go │ ├── router/ │ │ ├── balancing.go │ │ ├── command/ │ │ │ ├── command.go │ │ │ ├── command.pb.go │ │ │ ├── command.proto │ │ │ ├── command_grpc.pb.go │ │ │ ├── command_test.go │ │ │ ├── config.go │ │ │ └── errors.generated.go │ │ ├── condition.go │ │ ├── condition_geoip.go │ │ ├── condition_geoip_test.go │ │ ├── condition_test.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── router.go │ │ └── router_test.go │ └── stats/ │ ├── channel.go │ ├── channel_test.go │ ├── command/ │ │ ├── command.go │ │ ├── command.pb.go │ │ ├── command.proto │ │ ├── command_grpc.pb.go │ │ ├── command_test.go │ │ └── errors.generated.go │ ├── config.pb.go │ ├── config.proto │ ├── counter.go │ ├── counter_test.go │ ├── errors.generated.go │ ├── stats.go │ └── stats_test.go ├── azure-pipelines.yml ├── common/ │ ├── antireplay/ │ │ └── antireplay.go │ ├── bitmask/ │ │ ├── byte.go │ │ └── byte_test.go │ ├── buf/ │ │ ├── buf.go │ │ ├── buffer.go │ │ ├── buffer_test.go │ │ ├── copy.go │ │ ├── copy_test.go │ │ ├── errors.generated.go │ │ ├── io.go │ │ ├── io_test.go │ │ ├── multi_buffer.go │ │ ├── multi_buffer_test.go │ │ ├── reader.go │ │ ├── reader_test.go │ │ ├── readv_posix.go │ │ ├── readv_reader.go │ │ ├── readv_reader_wasm.go │ │ ├── readv_test.go │ │ ├── readv_unix.go │ │ ├── readv_windows.go │ │ ├── writer.go │ │ └── writer_test.go │ ├── bytespool/ │ │ └── pool.go │ ├── cmdarg/ │ │ └── cmdarg.go │ ├── common.go │ ├── common_test.go │ ├── crypto/ │ │ ├── aes.go │ │ ├── auth.go │ │ ├── auth_test.go │ │ ├── benchmark_test.go │ │ ├── chacha20.go │ │ ├── chacha20_test.go │ │ ├── chunk.go │ │ ├── chunk_test.go │ │ ├── crypto.go │ │ ├── errors.generated.go │ │ ├── internal/ │ │ │ ├── chacha.go │ │ │ ├── chacha_core.generated.go │ │ │ └── chacha_core_gen.go │ │ └── io.go │ ├── dice/ │ │ ├── dice.go │ │ └── dice_test.go │ ├── errors/ │ │ ├── errorgen/ │ │ │ └── main.go │ │ ├── errors.go │ │ ├── errors_test.go │ │ └── multi_error.go │ ├── errors.generated.go │ ├── interfaces.go │ ├── log/ │ │ ├── access.go │ │ ├── log.go │ │ ├── log.pb.go │ │ ├── log.proto │ │ ├── log_test.go │ │ ├── logger.go │ │ └── logger_test.go │ ├── mux/ │ │ ├── client.go │ │ ├── client_test.go │ │ ├── errors.generated.go │ │ ├── frame.go │ │ ├── frame_test.go │ │ ├── mux.go │ │ ├── mux_test.go │ │ ├── reader.go │ │ ├── server.go │ │ ├── session.go │ │ ├── session_test.go │ │ └── writer.go │ ├── net/ │ │ ├── address.go │ │ ├── address.pb.go │ │ ├── address.proto │ │ ├── address_test.go │ │ ├── connection.go │ │ ├── destination.go │ │ ├── destination.pb.go │ │ ├── destination.proto │ │ ├── destination_test.go │ │ ├── errors.generated.go │ │ ├── net.go │ │ ├── network.go │ │ ├── network.pb.go │ │ ├── network.proto │ │ ├── port.go │ │ ├── port.pb.go │ │ ├── port.proto │ │ ├── port_test.go │ │ └── system.go │ ├── peer/ │ │ ├── latency.go │ │ └── peer.go │ ├── platform/ │ │ ├── ctlcmd/ │ │ │ ├── attr_other.go │ │ │ ├── attr_windows.go │ │ │ ├── ctlcmd.go │ │ │ └── errors.generated.go │ │ ├── filesystem/ │ │ │ └── file.go │ │ ├── others.go │ │ ├── platform.go │ │ ├── platform_test.go │ │ └── windows.go │ ├── protocol/ │ │ ├── account.go │ │ ├── address.go │ │ ├── address_test.go │ │ ├── bittorrent/ │ │ │ └── bittorrent.go │ │ ├── context.go │ │ ├── dns/ │ │ │ ├── errors.generated.go │ │ │ └── io.go │ │ ├── errors.generated.go │ │ ├── headers.go │ │ ├── headers.pb.go │ │ ├── headers.proto │ │ ├── http/ │ │ │ ├── headers.go │ │ │ ├── headers_test.go │ │ │ ├── sniff.go │ │ │ └── sniff_test.go │ │ ├── id.go │ │ ├── id_test.go │ │ ├── payload.go │ │ ├── protocol.go │ │ ├── server_picker.go │ │ ├── server_picker_test.go │ │ ├── server_spec.go │ │ ├── server_spec.pb.go │ │ ├── server_spec.proto │ │ ├── server_spec_test.go │ │ ├── time.go │ │ ├── time_test.go │ │ ├── tls/ │ │ │ ├── cert/ │ │ │ │ ├── .gitignore │ │ │ │ ├── cert.go │ │ │ │ ├── cert_test.go │ │ │ │ ├── errors.generated.go │ │ │ │ └── privateKey.go │ │ │ ├── sniff.go │ │ │ └── sniff_test.go │ │ ├── udp/ │ │ │ ├── packet.go │ │ │ └── udp.go │ │ ├── user.go │ │ ├── user.pb.go │ │ └── user.proto │ ├── retry/ │ │ ├── errors.generated.go │ │ ├── retry.go │ │ └── retry_test.go │ ├── serial/ │ │ ├── serial.go │ │ ├── serial_test.go │ │ ├── string.go │ │ ├── string_test.go │ │ ├── typed_message.go │ │ ├── typed_message.pb.go │ │ ├── typed_message.proto │ │ └── typed_message_test.go │ ├── session/ │ │ ├── context.go │ │ └── session.go │ ├── signal/ │ │ ├── done/ │ │ │ └── done.go │ │ ├── notifier.go │ │ ├── notifier_test.go │ │ ├── pubsub/ │ │ │ ├── pubsub.go │ │ │ └── pubsub_test.go │ │ ├── semaphore/ │ │ │ └── semaphore.go │ │ ├── timer.go │ │ └── timer_test.go │ ├── stack/ │ │ └── bytes.go │ ├── strmatcher/ │ │ ├── benchmark_test.go │ │ ├── domain_matcher.go │ │ ├── domain_matcher_test.go │ │ ├── full_matcher.go │ │ ├── full_matcher_test.go │ │ ├── matchers.go │ │ ├── matchers_test.go │ │ ├── strmatcher.go │ │ └── strmatcher_test.go │ ├── task/ │ │ ├── common.go │ │ ├── periodic.go │ │ ├── periodic_test.go │ │ ├── task.go │ │ └── task_test.go │ ├── type.go │ ├── type_test.go │ └── uuid/ │ ├── uuid.go │ └── uuid_test.go ├── config.go ├── config.pb.go ├── config.proto ├── context.go ├── context_test.go ├── core.go ├── errors.generated.go ├── features/ │ ├── dns/ │ │ ├── client.go │ │ └── localdns/ │ │ └── client.go │ ├── errors.generated.go │ ├── feature.go │ ├── inbound/ │ │ └── inbound.go │ ├── outbound/ │ │ └── outbound.go │ ├── policy/ │ │ ├── default.go │ │ └── policy.go │ ├── routing/ │ │ ├── context.go │ │ ├── dispatcher.go │ │ ├── dns/ │ │ │ ├── context.go │ │ │ └── errors.generated.go │ │ ├── router.go │ │ └── session/ │ │ └── context.go │ └── stats/ │ ├── errors.generated.go │ └── stats.go ├── functions.go ├── functions_test.go ├── go.mod ├── go.sum ├── infra/ │ ├── bazel/ │ │ ├── BUILD │ │ ├── build.bzl │ │ ├── matrix.bzl │ │ └── zip.bzl │ ├── conf/ │ │ ├── api.go │ │ ├── blackhole.go │ │ ├── blackhole_test.go │ │ ├── buildable.go │ │ ├── command/ │ │ │ ├── command.go │ │ │ └── errors.generated.go │ │ ├── common.go │ │ ├── common_test.go │ │ ├── conf.go │ │ ├── dns.go │ │ ├── dns_proxy.go │ │ ├── dns_proxy_test.go │ │ ├── dns_test.go │ │ ├── dokodemo.go │ │ ├── dokodemo_test.go │ │ ├── errors.generated.go │ │ ├── freedom.go │ │ ├── freedom_test.go │ │ ├── general_test.go │ │ ├── http.go │ │ ├── http_test.go │ │ ├── json/ │ │ │ ├── reader.go │ │ │ └── reader_test.go │ │ ├── loader.go │ │ ├── log.go │ │ ├── mtproto.go │ │ ├── mtproto_test.go │ │ ├── policy.go │ │ ├── policy_test.go │ │ ├── reverse.go │ │ ├── reverse_test.go │ │ ├── router.go │ │ ├── router_test.go │ │ ├── serial/ │ │ │ ├── errors.generated.go │ │ │ ├── loader.go │ │ │ ├── loader_test.go │ │ │ └── serial.go │ │ ├── shadowsocks.go │ │ ├── shadowsocks_test.go │ │ ├── socks.go │ │ ├── socks_test.go │ │ ├── transport.go │ │ ├── transport_authenticators.go │ │ ├── transport_internet.go │ │ ├── transport_test.go │ │ ├── trojan.go │ │ ├── v2ray.go │ │ ├── v2ray_test.go │ │ ├── vless.go │ │ ├── vless_test.go │ │ ├── vmess.go │ │ └── vmess_test.go │ ├── control/ │ │ ├── api.go │ │ ├── cert.go │ │ ├── command.go │ │ ├── config.go │ │ ├── control.go │ │ ├── errors.generated.go │ │ ├── fetch.go │ │ ├── love.go │ │ ├── main/ │ │ │ ├── BUILD │ │ │ ├── main.go │ │ │ └── targets.bzl │ │ ├── tlsping.go │ │ ├── uuid.go │ │ └── verify.go │ └── vprotogen/ │ └── main.go ├── main/ │ ├── BUILD │ ├── confloader/ │ │ ├── confloader.go │ │ ├── errors.generated.go │ │ └── external/ │ │ ├── errors.generated.go │ │ └── external.go │ ├── distro/ │ │ ├── all/ │ │ │ └── all.go │ │ └── debug/ │ │ └── debug.go │ ├── errors.generated.go │ ├── json/ │ │ ├── config_json.go │ │ └── errors.generated.go │ ├── jsonem/ │ │ ├── errors.generated.go │ │ └── jsonem.go │ ├── main.go │ ├── main_test.go │ └── targets.bzl ├── mocks.go ├── proto.go ├── proxy/ │ ├── blackhole/ │ │ ├── blackhole.go │ │ ├── blackhole_test.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── config_test.go │ │ └── errors.generated.go │ ├── dns/ │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── dns.go │ │ ├── dns_test.go │ │ └── errors.generated.go │ ├── dokodemo/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── dokodemo.go │ │ └── errors.generated.go │ ├── freedom/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ └── freedom.go │ ├── http/ │ │ ├── client.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── http.go │ │ └── server.go │ ├── mtproto/ │ │ ├── auth.go │ │ ├── auth_test.go │ │ ├── client.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── mtproto.go │ │ └── server.go │ ├── proxy.go │ ├── shadowsocks/ │ │ ├── client.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── config_test.go │ │ ├── errors.generated.go │ │ ├── protocol.go │ │ ├── protocol_test.go │ │ ├── server.go │ │ └── shadowsocks.go │ ├── socks/ │ │ ├── client.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── protocol.go │ │ ├── protocol_test.go │ │ ├── server.go │ │ └── socks.go │ ├── trojan/ │ │ ├── client.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ ├── protocol.go │ │ ├── protocol_test.go │ │ ├── server.go │ │ ├── trojan.go │ │ └── validator.go │ ├── vless/ │ │ ├── account.go │ │ ├── account.pb.go │ │ ├── account.proto │ │ ├── encoding/ │ │ │ ├── addons.go │ │ │ ├── addons.pb.go │ │ │ ├── addons.proto │ │ │ ├── encoding.go │ │ │ ├── encoding_test.go │ │ │ └── errors.generated.go │ │ ├── errors.generated.go │ │ ├── inbound/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── errors.generated.go │ │ │ └── inbound.go │ │ ├── outbound/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── errors.generated.go │ │ │ └── outbound.go │ │ ├── validator.go │ │ └── vless.go │ └── vmess/ │ ├── account.go │ ├── account.pb.go │ ├── account.proto │ ├── aead/ │ │ ├── authid.go │ │ ├── authid_test.go │ │ ├── consts.go │ │ ├── encrypt.go │ │ ├── encrypt_test.go │ │ └── kdf.go │ ├── encoding/ │ │ ├── auth.go │ │ ├── auth_test.go │ │ ├── client.go │ │ ├── commands.go │ │ ├── commands_test.go │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── errors.generated.go │ │ └── server.go │ ├── errors.generated.go │ ├── inbound/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ └── inbound.go │ ├── outbound/ │ │ ├── command.go │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── errors.generated.go │ │ └── outbound.go │ ├── validator.go │ ├── validator_test.go │ ├── vmess.go │ └── vmessCtxInterface.go ├── release/ │ ├── BUILD │ ├── bleedingrelease.sh │ ├── config/ │ │ ├── config.json │ │ ├── systemd/ │ │ │ └── system/ │ │ │ ├── v2ray.service │ │ │ └── v2ray@.service │ │ ├── vpoint_socks_vmess.json │ │ └── vpoint_vmess_freedom.json │ ├── install-release.sh │ ├── mapping.bzl │ ├── mutilate/ │ │ └── removeVSign.sh │ ├── requestsign.sh │ ├── requestsign_github.sh │ ├── tagrelease.sh │ └── user-package.sh ├── testing/ │ ├── coverage/ │ │ ├── coverall │ │ └── coverall2 │ ├── mocks/ │ │ ├── dns.go │ │ ├── io.go │ │ ├── log.go │ │ ├── mux.go │ │ ├── outbound.go │ │ └── proxy.go │ ├── scenarios/ │ │ ├── command_test.go │ │ ├── common.go │ │ ├── common_coverage.go │ │ ├── common_regular.go │ │ ├── dns_test.go │ │ ├── dokodemo_test.go │ │ ├── feature_test.go │ │ ├── http_test.go │ │ ├── policy_test.go │ │ ├── reverse_test.go │ │ ├── shadowsocks_test.go │ │ ├── socks_test.go │ │ ├── tls_test.go │ │ ├── transport_test.go │ │ └── vmess_test.go │ └── servers/ │ ├── http/ │ │ └── http.go │ ├── tcp/ │ │ ├── port.go │ │ └── tcp.go │ └── udp/ │ ├── port.go │ └── udp.go ├── transport/ │ ├── config.go │ ├── config.pb.go │ ├── config.proto │ ├── internet/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── connection.go │ │ ├── dialer.go │ │ ├── dialer_test.go │ │ ├── domainsocket/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── dial.go │ │ │ ├── errgen.go │ │ │ ├── errors.generated.go │ │ │ ├── listener.go │ │ │ └── listener_test.go │ │ ├── errors.generated.go │ │ ├── header.go │ │ ├── header_test.go │ │ ├── headers/ │ │ │ ├── http/ │ │ │ │ ├── config.go │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ ├── errors.generated.go │ │ │ │ ├── http.go │ │ │ │ ├── http_test.go │ │ │ │ ├── linkedreadRequest.go │ │ │ │ └── resp.go │ │ │ ├── noop/ │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ └── noop.go │ │ │ ├── srtp/ │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ ├── srtp.go │ │ │ │ └── srtp_test.go │ │ │ ├── tls/ │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ ├── dtls.go │ │ │ │ └── dtls_test.go │ │ │ ├── utp/ │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ ├── utp.go │ │ │ │ └── utp_test.go │ │ │ ├── wechat/ │ │ │ │ ├── config.pb.go │ │ │ │ ├── config.proto │ │ │ │ ├── wechat.go │ │ │ │ └── wechat_test.go │ │ │ └── wireguard/ │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ └── wireguard.go │ │ ├── http/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── dialer.go │ │ │ ├── errors.generated.go │ │ │ ├── http.go │ │ │ ├── http_test.go │ │ │ └── hub.go │ │ ├── internet.go │ │ ├── kcp/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── connection.go │ │ │ ├── connection_test.go │ │ │ ├── crypt.go │ │ │ ├── crypt_test.go │ │ │ ├── cryptreal.go │ │ │ ├── dialer.go │ │ │ ├── errors.generated.go │ │ │ ├── io.go │ │ │ ├── io_test.go │ │ │ ├── kcp.go │ │ │ ├── kcp_test.go │ │ │ ├── listener.go │ │ │ ├── output.go │ │ │ ├── receiving.go │ │ │ ├── segment.go │ │ │ ├── segment_test.go │ │ │ ├── sending.go │ │ │ ├── xor.go │ │ │ ├── xor_amd64.go │ │ │ └── xor_amd64.s │ │ ├── memory_settings.go │ │ ├── quic/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── conn.go │ │ │ ├── dialer.go │ │ │ ├── errors.generated.go │ │ │ ├── hub.go │ │ │ ├── pool.go │ │ │ ├── quic.go │ │ │ └── quic_test.go │ │ ├── sockopt.go │ │ ├── sockopt_darwin.go │ │ ├── sockopt_freebsd.go │ │ ├── sockopt_linux.go │ │ ├── sockopt_linux_test.go │ │ ├── sockopt_other.go │ │ ├── sockopt_test.go │ │ ├── sockopt_windows.go │ │ ├── system_dialer.go │ │ ├── system_listener.go │ │ ├── system_listener_test.go │ │ ├── tcp/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── dialer.go │ │ │ ├── errors.generated.go │ │ │ ├── hub.go │ │ │ ├── sockopt_freebsd.go │ │ │ ├── sockopt_linux.go │ │ │ ├── sockopt_linux_test.go │ │ │ ├── sockopt_other.go │ │ │ └── tcp.go │ │ ├── tcp_hub.go │ │ ├── tls/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── config_other.go │ │ │ ├── config_test.go │ │ │ ├── config_windows.go │ │ │ ├── errors.generated.go │ │ │ └── tls.go │ │ ├── udp/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── dialer.go │ │ │ ├── dispatcher.go │ │ │ ├── dispatcher_test.go │ │ │ ├── errors.generated.go │ │ │ ├── hub.go │ │ │ ├── hub_freebsd.go │ │ │ ├── hub_linux.go │ │ │ ├── hub_other.go │ │ │ └── udp.go │ │ ├── websocket/ │ │ │ ├── config.go │ │ │ ├── config.pb.go │ │ │ ├── config.proto │ │ │ ├── connection.go │ │ │ ├── dialer.go │ │ │ ├── errors.generated.go │ │ │ ├── hub.go │ │ │ ├── ws.go │ │ │ └── ws_test.go │ │ └── xtls/ │ │ ├── config.go │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── config_other.go │ │ ├── config_test.go │ │ ├── config_windows.go │ │ ├── errors.generated.go │ │ └── xtls.go │ ├── link.go │ └── pipe/ │ ├── impl.go │ ├── pipe.go │ ├── pipe_test.go │ ├── reader.go │ └── writer.go ├── v2ray.go └── v2ray_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dev/protoc/macos/protoc ================================================ [File too large to display: 11.8 MB] ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at love@v2ray.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_cn.md ================================================ --- name: V2Ray 程序问题 about: "提交一个 V2Ray 的程序问题报告。" --- 除非特殊情况,请完整填写所有问题。不按模板发的 issue 将直接被关闭。 如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 1) 你正在使用哪个版本的 V2Ray?(如果服务器和客户端使用了不同版本,请注明) 2) 你的使用场景是什么?比如使用 Chrome 通过 Socks/VMess 代理观看 YouTube 视频。 3) 你看到的不正常的现象是什么?(请描述具体现象,比如访问超时,TLS 证书错误等) 4) 你期待看到的正确表现是怎样的? 5) 请附上你的配置(提交 Issue 前请隐藏服务器端IP地址)。 服务器端配置: ```javascript // 在这里附上服务器端配置文件 ``` 客户端配置: ```javascript // 在这里附上客户端配置 ``` 6) 请附上出错时软件输出的错误日志。在 Linux 中,日志通常在 `/var/log/v2ray/error.log` 文件中。 服务器端错误日志: ```javascript // 在这里附上服务器端日志 ``` 客户端错误日志: ```javascript // 在这里附上客户端日志 ``` 7) 请附上访问日志。在 Linux 中,日志通常在 `/var/log/v2ray/access.log` 文件中。 ```javascript // 在这里附上服务器端日志 ``` 8) 其它相关的配置文件(如 Nginx)和相关日志。 9) 如果 V2Ray 无法启动,请附上 `--test` 输出。 通常的命令为 `/usr/bin/v2ray/v2ray --test --config /etc/v2ray/config.json`。请按实际情况修改。 10) 如果 V2Ray 服务运行不正常,请附上 journal 日志。 通常的命令为 `journalctl -u v2ray`。 请预览一下你填的内容再提交。 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_en.md ================================================ --- name: Bug report about: "Create a bug report to help us improve" --- Please answer all the questions with enough information. All issues not following this template will be closed immediately. If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. 1) What version of V2Ray are you using (If you deploy different version on server and client, please explicitly point out)? 2) What's your scenario of using V2Ray? E.g., Watching YouTube videos in Chrome via Socks/VMess proxy. 3) What did you see? (Please describe in detail, such as timeout, fake TLS certificate etc) 4) What's your expectation? 5) Please attach your configuration file (**Mask IP addresses before submit this issue**). Server configuration: ```javascript // Please attach your server configuration here. ``` Client configuration: ```javascript // Please attach your client configuration here. ``` 6) Please attach error logs, especially the bottom lines if the file is large. Error log file is usually at `/var/log/v2ray/error.log` on Linux. Server error log: ```javascript // Please attach your server error log here. ``` Client error log: ```javascript // Please attach your client error log here. ``` 7) Please attach access log. Access log is usually at '/var/log/v2ray/access.log' on Linux. ```javascript // Please attach your server access log here. ``` 8) Other configurations (such as Nginx) and logs. 9) If V2Ray doesn't run, please attach output from `--test`. The command is usually `/usr/bin/v2ray/v2ray --test --config /etc/v2ray/config.json`, but may vary according to your scenario. 10) If V2Ray service doesn't run, please attach journal log. Usual command is `journalctl -u v2ray`. Please review your issue before submitting. ================================================ FILE: .github/ISSUE_TEMPLATE/other_en.md ================================================ --- name: Other about: "其它问题请使用 https://github.com/v2fly/discussion/issues 进行讨论 / Please discuss other issues at https://github.com/v2fly/discussion/issues" --- 如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 此 Issue 会被立即关闭。 If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. This issue will be closed immediately. ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ 如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 此 Issue 会被立即关闭。 If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. This issue will be closed immediately. ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/linters/.golangci.yml ================================================ run: timeout: 5m skip-files: - generated.* issues: new: true linters: enable: - bodyclose - depguard - gocritic - gofmt - goimports - golint - goprintffuncname - gosimple - govet - ineffassign - misspell - nakedret - noctx - nolintlint - rowserrcheck - scopelint - staticcheck - structcheck - stylecheck - typecheck - unconvert - unparam - varcheck - whitespace disable: - deadcode - errcheck - unused ================================================ FILE: .github/pull_request_template.md ================================================ Please Move to https://github.com/v2fly/v2ray-core/pulls ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: CodeQL on: push: branches: [master] paths: - "**/*.go" pull_request: branches: [master] types: [opened, synchronize, reopened] paths: - "**/*.go" schedule: - cron: '0 0 * * 1' jobs: analyze: if: github.repository != 'v2ray/v2ray-core' runs-on: ubuntu-latest strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ['go'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 ================================================ FILE: .github/workflows/coverage.yml ================================================ name: Coverage on: push: branches: [master] paths: - "**/*.go" jobs: coverage: if: github.repository != 'v2ray/v2ray-core' runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.15 - name: Checkout codebase uses: actions/checkout@v2 - name: Cache go module uses: actions/cache@v2 id: cache-gomodules with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Get dependencies if: steps.cache-gomodules.outputs.cache-hit != 'true' run: | go get -v -t -d ./... - name: Run coverage run: ./testing/coverage/coverall2 - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: file: ./coverage.txt fail_ci_if_error: true ================================================ FILE: .github/workflows/linter.yml ================================================ name: Linter on: push: branches: [master] paths: - "**/*.go" pull_request: branches: [master] types: [opened, synchronize, reopened] paths: - "**/*.go" jobs: lint: if: github.repository != 'v2ray/v2ray-core' runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.15 - name: Checkout codebase uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.31 args: --config=.github/linters/.golangci.yml only-new-issues: true ================================================ FILE: .github/workflows/sign.yml ================================================ name: Sign on: release: types: [released] jobs: sign: if: github.repository != 'v2ray/v2ray-core' runs-on: ubuntu-latest steps: - name: Checkout default branch uses: actions/checkout@v2 - name: Grant it execution permission run: | chmod +x $GITHUB_WORKSPACE/release/requestsign_github.sh chmod +x $GITHUB_WORKSPACE/release/requestsign.sh - name: Invoke release signing env: SIGN_SERVICE_PASSWORD: ${{ secrets.SIGN_SERVICE_PASSWORD }} SIGN_SERIVCE_URL: ${{ secrets.SIGN_SERIVCE_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: $GITHUB_WORKSPACE/release/requestsign_github.sh ================================================ FILE: .github/workflows/stale.yml ================================================ name: Mark stale issues and pull requests on: schedule: - cron: "30 1 * * *" jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v3.0.13 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: "This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days" stale-pr-message: 'It has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' days-before-stale: 120 days-before-close: 5 ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: push: branches: [master] paths: - "**/*.go" - "go.mod" - "go.sum" pull_request: branches: [master] types: [opened, synchronize, reopened] paths: - "**/*.go" - "go.mod" - "go.sum" jobs: test: if: github.repository != 'v2ray/v2ray-core' runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.15 - name: Checkout codebase uses: actions/checkout@v2 - name: Cache go module uses: actions/cache@v2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go- - name: Test run: go test -timeout 1h -v ./... ================================================ FILE: .github/workflows/updateGeofile.yml ================================================ name: Update Geofiles on: schedule: - cron: "0 0 * * FRI" jobs: update: if: github.repository == 'v2fly/v2ray-core' runs-on: ubuntu-latest steps: - name: Checkout codebase uses: actions/checkout@v2 - name: Download run: | curl -L -o release/config/geoip.dat "https://github.com/v2fly/geoip/raw/release/geoip.dat" curl -L -o release/config/geosite.dat "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat" - name: push run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git commit -am "update geoip, geosite" git push -v --progress ================================================ FILE: .gitignore ================================================ *.DS_Store bazel-* .idea ================================================ FILE: Dockerfile ================================================ ############################ # STEP 1 build executable binary ############################ FROM golang:alpine AS builder RUN apk update && apk add --no-cache git bash wget curl WORKDIR /build RUN git clone --progress https://github.com/v2fly/v2ray-core.git . && \ bash ./release/user-package.sh nosource noconf codename=$(git describe --abbrev=0 --tags) buildname=docker-fly abpathtgz=/tmp/v2ray.tgz ############################ # STEP 2 build a small image ############################ FROM alpine LABEL maintainer "V2Fly Community " COPY --from=builder /tmp/v2ray.tgz /tmp RUN apk update && apk add ca-certificates && \ mkdir -p /usr/bin/v2ray && \ tar xvfz /tmp/v2ray.tgz -C /usr/bin/v2ray #ENTRYPOINT ["/usr/bin/v2ray/v2ray"] ENV PATH /usr/bin/v2ray:$PATH CMD ["v2ray", "-config=/etc/v2ray/config.json"] ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-2020 V2Fly Community Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Move To https://github.com/v2fly/v2ray-core *** # Project V [![GitHub Test Badge][1]][2] [![codecov.io][3]][4] [![GoDoc][5]][6] [![codebeat][7]][8] [![Downloads][9]][10] [![Downloads][11]][12] [1]: https://github.com/v2fly/v2ray-core/workflows/Test/badge.svg "GitHub Test Badge" [2]: https://github.com/v2fly/v2ray-core/actions "GitHub Actions Page" [3]: https://codecov.io/gh/v2fly/v2ray-core/branch/master/graph/badge.svg?branch=master "Coverage Badge" [4]: https://codecov.io/gh/v2fly/v2ray-core?branch=master "Codecov Status" [5]: https://godoc.org/v2ray.com/core?status.svg "GoDoc Badge" [6]: https://godoc.org/v2ray.com/core "GoDoc" [7]: https://goreportcard.com/badge/github.com/v2fly/v2ray-core "Goreportcard Badge" [8]: https://goreportcard.com/report/github.com/v2fly/v2ray-core "Goreportcard Result" [9]: https://img.shields.io/github/downloads/v2ray/v2ray-core/total.svg "v2ray/v2ray-core downloads count" [10]: https://github.com/v2ray/v2ray-core/releases "v2ray/v2ray-core release page" [11]: https://img.shields.io/github/downloads/v2fly/v2ray-core/total.svg "v2fly/v2ray-core downloads count" [12]: https://github.com/v2fly/v2ray-core/releases "v2fly/v2ray-core release page" Project V is a set of network tools that help you to build your own computer network. It secures your network connections and thus protects your privacy. See [our website](https://www.v2fly.org/) for more information. ## License [The MIT License (MIT)](https://raw.githubusercontent.com/v2fly/v2ray-core/master/LICENSE) ## Credits This repo relies on the following third-party projects: - In production: - [gorilla/websocket](https://github.com/gorilla/websocket) - [gRPC](https://google.golang.org/grpc) - For testing only: - [miekg/dns](https://github.com/miekg/dns) - [h12w/socks](https://github.com/h12w/socks) ================================================ FILE: SECURITY.md ================================================ # 安全策略 Security Policy ## 受支持的版本 Supported Versions 目前 v2ray-core 项目由 [V2Fly 社区](https://github.com/v2fly) 继续提供代码维护,由于精力有限且项目复杂度较高,只维护主线代码的功能和安全性完整。原则上主页的兼容性保证继续遵循, 如有例外另行说明。 Currently v2ray-core project is maintained by [V2Fly community](https://github.com/v2fly). Feature and security guarantee may only be limited to the master branch, though we would still try our best to follow the compatiblity claims listed on the official website. ## 汇报安全风险 Reporting a Vulnerability 使用邮箱: security |at| v2fly.org。 Report to email: security |at| v2fly.org. GPG public key: ``` pub rsa4096 2020-06-02 [SC] [有效至:2022-01-02] E2E35E27914FB007C0D4B6DDB117BA3BE8B494A7 uid [ 绝对 ] V2Fly Developers sub rsa4096 2020-06-02 [E] [有效至:2022-01-02] sub rsa4096 2020-11-08 [S] [有效至:2022-01-02] // 用于 Debian / Ubuntu 签名 -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBF7V7pQBEACozcw4/BlPgFWaz4AdN8HKSrCDLlN+/g7m4AKZIo13fAnDh+sJ 2H4NrWNr0xxgovbco5Xw4OSSwY1BuUhnb4AmIyxbwqUQD2UADe5xD6gMBwNiJTP4 02VCHhh7DnWTeLbAsUgRotxUCxsWVvd2F08SYGfJggOVftOnG+VNnwzTOvHWFVEw 1Pv1DaY7bKSA0voACerRbAPCYqhmElAGJHYNjBMaxqCaWFJWpAFfBxkvS1FDVyZk BsABhn6sOcGJn8EYSHUIXhWwqtkQCjBB4OOik+Jn+S2DFGyk5l1NrGRQtX8C0BYn nc7VaxtFOp5fnJ4y0GNd4AM9KO0/Ojosi6b64l407Fj9i9OXznmZUACQw2u+VcL3 qNy768hsTmka3pXzpRHZwYcOLOEr3jGHmLOtXgQ656OjF8Xd9DJ4cB42X8iBeqTp iQchHIdBpnu27ZbBFy09OMak+STB5zA0JmxDaC8b48mVkc0BMRXdYl7wWXJsEJf1 roAOr3RCBKiE840w0PLOTnUljfqazPYTwzs91oP+SeZjBmGOpaAh7bh5BVOpzPSE bdA61/n01GEb5bpOKpaTi9GviF3RCbfFnLKJnBq0vHvW9BqKTVFRPAKkBGuOPBdy 8MBNY+VY/2aP3ukZUoYe8Ypl9Q7dVPRjnoWaH0sEMzftoh+3s7GSSgAylQARAQAB tCBWMkZseSBEZXZlbG9wZXJzIDxkZXZAdjJmbHkub3JnPokCVAQTAQgAPgIbAwUL CQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBOLjXieRT7AHwNS23bEXujvotJSnBQJf p4leBQkC+1DKAAoJELEXujvotJSn124P/0swu9POvEQtxVlRzNh2VjAGHZ5NEDnl pMrhfC5ryCYtlVS/kc2WwRhIRHKzr9nbamgSxUCiyLagfnIjhIvAohun49grYNzG MZWRURiuFrCnYbD7juJTvfbzZCzJk7LPsdnqHWr8fYcOZMTOZVzQiQB2jUx2KeRm yV8aV21Z8gMLqSGjs06a0UaRbKB0FSysTURm91/jFmiH43aG1s/LcB9/lKf5HpNl 9or6LrEOrokAwtkMSBYTqm7Dp1j+cK0iOMw2CmMqmQZkV+i6msYrQRiX/X6YufiM wfMMSdOZOz9KG+k+C6N1swSbGeDMrJfnDUDbvrAXKhDjNgY7UBwbk69Abd7Y9aQz /jVmrFEWt4lisBxglBot60CRUTM2boK/uQS5zBCJhemeg14F9Q/FRiUTlS8jQoeK PWeK2lagYJS8lpJZLXkqe4xSpjCgoT0Z+lYSfTjx+T0AFF+xz5E243Lb5kDxwnR9 Y5CZt3vV6GWBYOt9MEL3pk7AnYyNT1y1KIiMyONh/Z1koUdHr4J9exllnsmAJQUa W/j0UtVsLsvUjFv9RTr9w5p/U0J0VLIN0YOpx4wYaBEwFIa8lsL+Ey1Vphkvvjfz uMRAHe4v+axWb1f1hVCBjtyCVyvzf+i9RTAYsBJ3MJ0C8cvvrm10N9B7MHh0JZA5 PcJSilailp1TuQINBF7V7pQBEADkQdO75smeKnmPt0/aNNlb7JDOSWW5VY0kYgx3 6Toh139JstIQ2xz0CLSGReizUFB6eR3DXmezLrmhkgN2Aq5A+hCtFAJwWKuKr1HS usvJ1el9h0oh7IO+tF8E/gNYwWfabjPX27FGVCHR1qG7ffN51Bghrnwi1T4YC98E R9EGU6N0Xs9DeIJL9WQPH/DF22251i/OAXkqKVGn3PNe2cBsp0yKxr9mlSyzjrha KXokPiPcvNqlnkiDCgfiRj7c2C2Lyl9PoEiGpsNZaCZYkMPgjM0xiLenQddwRyOU z2cLG3d8WdCTRyHSZd/YQtSi5R6AnkJEsVtUiDN5zwNFVpQlTq3jNHsVUpjFU2nK ourTZVCCLbAC60VTdxLN6eFO0f+lS2WjyJ7uZ9SGbS6uP0jMNphH/QjVF848bWXs 1CuZty5QQY7+MTNUAhSWWntrpTkdXYqT0zUqiOc1YNnkfg3hvC4d0dbnFTfcyZnB Sg8e7/9n6+ms75/deYgnLuA6h7pkIcflm7pUMfVKXKz5Vlc8FC9ia0UtobeKBKqi jObfiO/zmNL0HQBeX0e8GkJrCyv6ikD8cUqsmVtgw7jdxGsV0SL5CddDnGKsc68O pGDmkAuRqR3QtXju/4r7a8IEVveGWc3rUvddYrtqbbCNWCN0JKX13PEvbNAm+2eD MGQtcQARAQABiQI8BBgBCAAmAhsMFiEE4uNeJ5FPsAfA1LbdsRe6O+i0lKcFAl+n dwcFCQL7PnMACgkQsRe6O+i0lKeWfxAApopL5I9p4btmkcLIg2lkA1n+czFekbdr 2tjFKrBER4QWkyDCUE8QaVo/ECveTHmnxrTB/djW6xqPVS77PL8xOATIYTo6qU38 oTCB1T7/P2L9qI72BzcRY5f9ZPyJhCtrkvjCPzjUjw+ZIPIOgQcWgKHWnE+OyUKD 0GkVEUME3QP5S4Nr3XGrgS7oxDAmD52u7pn0mSk5WmEcLW0oGwsVdc4aDXxpX+u/ gkBZysmAuomPov7iXVosMakl+4rz30yPcrL9A81m1WAeB3PGkpaO3B++8Ql+FBCQ OrLtPn/nnIzEuAXB1Hd8vYzxtRM2CZvhRExM7xofnhkBJOtR/ddfbJa7H5+Aruc0 4S0JIaqMCrC6tZezjTACAzrWULmZZGmrHbLrmXBuLk0huRkeIRnDzHP+DoE2UciL 3hR9EGOHX9O/dGb3bb3y11LAf7GI28ZG7So1GeoFkEOga1IJnsBnXCqwM8vbDDWq /7aLb3/m0gT7DUfjeXKfWPJXcnaq8r4llHzDn2i6ax4Uq/brCOLj9ovVGIctZTbt yvsFOc1bVkSuUM+pMkCtBx80/sJSB2Nu94S6osdaUlRE+jaCcqEbPd+G68Yd0Khi CL8zF1a3dX1dpuVFTLNpXOgrviGBzXQmzFeil7mWFs0l+1XZOPz9nhmRrMn6wV3n i4KItRSJAXy5Ag0EX6d5hQEQAMsVyLTXdybeei2nWDb5jtzzC3AtSnPWtKG4B86C BXncaZpU43hKI3oduW2+42eM8n8KTvO11r9xv4zKATfaHBZq2hkKZdDQjuSstovr a3hapHHknHeNVTg3yuiakKzpr6FK23W/GE1lJfhz254v9+dRV0KazWksXvpGEdgI +6sC4Nr5bKgJVEQibyrrL0gmzlVB/oQU/W4eGvk21zmgMlHri+edBLpVtlCmn7k/ 0t+2X9D1Pq2nkjMUurB9EJ1z24LMldmPOl6P7iJCx9kSUjcHrEg56q5VSZq50FAj DeSjAqsdussI8cdstCMktE9nhizxVKFXpbXifqoYfJwCo23wFqQJpyPgQqHIT12s GWRUa/MF6hRYg/5CyeadDmkmnKPTPjmQ2S2SFNXX7xs+dZKvIvXP30z4cpuVY8i8 chZSRNb8K0L9T0Jme7CPm28F6lvDUkNDQ1WErXZruHbOKwQOfQBdXK3nedOiUpBt 401HVlGUJSInfEb3JXU01tRqnnzI/y5z7cWCGEMEa6TeaCrMbVvl8xeAA1w/nw0y zHz6/Pnf4TITuCH22aa7+xfgpq8gRLhUUws89mbQT+9fd8tT65+Q8xcaLCyzrLAq zND5sVZ4/PwaYc8UNZcHjeQR7aYWI1xgr/IwY1wyDWZLbWgkk0HVxpvYdMEpJryD AyMdABEBAAGJBHIEGAEIACYWIQTi414nkU+wB8DUtt2xF7o76LSUpwUCX6d5hQIb AgUJAim2AAJACRCxF7o76LSUp8F0IAQZAQgAHRYhBK8FZLGpNMztuW02YbJOz+X/ ddOBBQJfp3mFAAoJELJOz+X/ddOBNKQP/0nwIC4R9gQhY53vME7VA7elIrBiSM6d Va26a7J1nrCcpDAE7Lp0TqzrDMqyen+IL4X5QK5sKTgenYTgjppEJIQn+Wup54ix I+YOQ8MVLfN6/3QPACWMngSPRF+UKDg4hyTCEL+/GCgTp58oXrl/YIO6Oqt5drog w4+4ufU1/eKTb2ruGULGl9jZvFSZpLdsvJ19xJB2kC1k8GVNu7MnUL+S2pU/9kO4 5EZ/jEa1wT45zev+HdmzX5TYW6SLaI9HKHMqbQz2EHc3tRYIDaz3FE3s4VdMjqpp e42SvkOYaguc6cXToDbzBmU+iWGlXCTHfNhxwxoUYcKZlDEkEtvSYHJOb0k9eqbT gvMb5GjbAgqqwOBwtN3v790j8jEG+cdXR3qHcEx0bw3F2Bd18U7j946OxHLKE5Xk 2sWEG422maVrE9o1DdeTV5oDFNNPBzqfjgGBZCCKrjkpldhDOHeoDU2aFMJ7yVqw ZwKwJ5f8fdNS13UnQVwGsZ2BsW1cox5ZGZ/C5A7mfSF1WAgJcYIw4M2JQbDn4Yuw yqjyg53lT3OurBONbEZ7unnsLqpT9qKwZ1qCemqGRJieXXxJwl7G4gBgZbH0rBJR 6dhbyt4c2JE8MMdC65mDWneltNM6pttC/j5jCuvIlZGACZ91UuLLediJJWAlOJ+1 fBQ0m8TD6d8ZrakP/RFMLZrxh9WPaFB43sW/b1Fq2h933HQ29oSQFuXhsHsx1Vaq HTRTcBB7kywAr9+zMYsOsk0/WnoZNGoMkUWu/gFkb6CdUcsdEumgyZ8S24VoBCHB T1fD/8eOA5K82hwAFcKbPwuuTLtf9b9HB4/xsObfcczTeqIknzIPsGlgVz4w1c9a StSo4iI4bCSLL+/mqiXZ+ArXJ/z4Vejl92fNLWVOlOrjkBV+AY6iAFCCsxJ1O5ud 5a5r1bUeBXd0BcQ1m/hpjawMC1y0SkIBTQCgxIQoPoxJ27hHNIN1R2nkqfY9vboQ 7O0uIHF8fmuz93xg68ZTW0JHwOw4Mz88lGibE2laHApjKWZAtF/i+LlhbnewtESL EuGTT7gt7cSHgnBiDEIm5UJVEGeM0sMReztxy9V7glohH5DV8GpVK/GncKlsrh1K BuEuz7IrqKlBzhsDy0SrNZpX7EzsiU1uvoA6teT4EPey8qXH+7WR9B2ad1Zc5yE3 zv4BpnWkkJp8qdYu4fdCs/mrmnBR5G1YdOAIlNWhU74Wdyq+W4HfTWMgvJHmElnZ UvQ9RDTWnw2+3n2ATeLf9ZwW1g4/Dqh55OaLtJZo5me8vU9W+vkm34xzfVfD/mus ljogw5eiGyj8j3lUVjYWu28l/bz0zDUueWmHhV8E8z0Cn7OhrHPpUCHx2Aep =quYd -----END PGP PUBLIC KEY BLOCK----- ``` ================================================ FILE: WORKSPACE ================================================ workspace(name = "v2ray_core") ================================================ FILE: annotations.go ================================================ package core // Annotation is a concept in V2Ray. This struct is only for documentation. It is not used anywhere. // Annotations begin with "v2ray:" in comment, as metadata of functions or types. type Annotation struct { // API is for types or functions that can be used in other libs. Possible values are: // // * v2ray:api:beta for types or functions that are ready for use, but maybe changed in the future. // * v2ray:api:stable for types or functions with guarantee of backward compatibility. // * v2ray:api:deprecated for types or functions that should not be used anymore. // // Types or functions without api annotation should not be used externally. API string } ================================================ FILE: app/app.go ================================================ // Package app contains feature implementations of V2Ray. The features may be enabled during runtime. package app ================================================ FILE: app/commander/commander.go ================================================ // +build !confonly package commander //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "net" "sync" "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/signal/done" "v2ray.com/core/features/outbound" ) // Commander is a V2Ray feature that provides gRPC methods to external clients. type Commander struct { sync.Mutex server *grpc.Server services []Service ohm outbound.Manager tag string } // NewCommander creates a new Commander based on the given config. func NewCommander(ctx context.Context, config *Config) (*Commander, error) { c := &Commander{ tag: config.Tag, } common.Must(core.RequireFeatures(ctx, func(om outbound.Manager) { c.ohm = om })) for _, rawConfig := range config.Service { config, err := rawConfig.GetInstance() if err != nil { return nil, err } rawService, err := common.CreateObject(ctx, config) if err != nil { return nil, err } service, ok := rawService.(Service) if !ok { return nil, newError("not a Service.") } c.services = append(c.services, service) } return c, nil } // Type implements common.HasType. func (c *Commander) Type() interface{} { return (*Commander)(nil) } // Start implements common.Runnable. func (c *Commander) Start() error { c.Lock() c.server = grpc.NewServer() for _, service := range c.services { service.Register(c.server) } c.Unlock() listener := &OutboundListener{ buffer: make(chan net.Conn, 4), done: done.New(), } go func() { if err := c.server.Serve(listener); err != nil { newError("failed to start grpc server").Base(err).AtError().WriteToLog() } }() if err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil { newError("failed to remove existing handler").WriteToLog() } return c.ohm.AddHandler(context.Background(), &Outbound{ tag: c.tag, listener: listener, }) } // Close implements common.Closable. func (c *Commander) Close() error { c.Lock() defer c.Unlock() if c.server != nil { c.server.Stop() c.server = nil } return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { return NewCommander(ctx, cfg.(*Config)) })) } ================================================ FILE: app/commander/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/commander/config.proto package commander import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Config is the settings for Commander. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Tag of the outbound handler that handles grpc connections. Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` // Services that supported by this server. All services must implement Service // interface. Service []*serial.TypedMessage `protobuf:"bytes,2,rep,name=service,proto3" json:"service,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_commander_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_commander_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_commander_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetTag() string { if x != nil { return x.Tag } return "" } func (x *Config) GetService() []*serial.TypedMessage { if x != nil { return x.Service } return nil } var File_app_commander_config_proto protoreflect.FileDescriptor var file_app_commander_config_proto_rawDesc = []byte{ 0x0a, 0x1a, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5c, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x40, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x59, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_commander_config_proto_rawDescOnce sync.Once file_app_commander_config_proto_rawDescData = file_app_commander_config_proto_rawDesc ) func file_app_commander_config_proto_rawDescGZIP() []byte { file_app_commander_config_proto_rawDescOnce.Do(func() { file_app_commander_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_commander_config_proto_rawDescData) }) return file_app_commander_config_proto_rawDescData } var file_app_commander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_app_commander_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.app.commander.Config (*serial.TypedMessage)(nil), // 1: v2ray.core.common.serial.TypedMessage } var file_app_commander_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.app.commander.Config.service:type_name -> v2ray.core.common.serial.TypedMessage 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_app_commander_config_proto_init() } func file_app_commander_config_proto_init() { if File_app_commander_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_commander_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_commander_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_commander_config_proto_goTypes, DependencyIndexes: file_app_commander_config_proto_depIdxs, MessageInfos: file_app_commander_config_proto_msgTypes, }.Build() File_app_commander_config_proto = out.File file_app_commander_config_proto_rawDesc = nil file_app_commander_config_proto_goTypes = nil file_app_commander_config_proto_depIdxs = nil } ================================================ FILE: app/commander/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.commander; option csharp_namespace = "V2Ray.Core.App.Commander"; option go_package = "v2ray.com/core/app/commander"; option java_package = "com.v2ray.core.app.commander"; option java_multiple_files = true; import "common/serial/typed_message.proto"; // Config is the settings for Commander. message Config { // Tag of the outbound handler that handles grpc connections. string tag = 1; // Services that supported by this server. All services must implement Service // interface. repeated v2ray.core.common.serial.TypedMessage service = 2; } ================================================ FILE: app/commander/errors.generated.go ================================================ package commander import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/commander/outbound.go ================================================ // +build !confonly package commander import ( "context" "sync" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/signal/done" "v2ray.com/core/transport" ) // OutboundListener is a net.Listener for listening gRPC connections. type OutboundListener struct { buffer chan net.Conn done *done.Instance } func (l *OutboundListener) add(conn net.Conn) { select { case l.buffer <- conn: case <-l.done.Wait(): conn.Close() // nolint: errcheck default: conn.Close() // nolint: errcheck } } // Accept implements net.Listener. func (l *OutboundListener) Accept() (net.Conn, error) { select { case <-l.done.Wait(): return nil, newError("listen closed") case c := <-l.buffer: return c, nil } } // Close implement net.Listener. func (l *OutboundListener) Close() error { common.Must(l.done.Close()) L: for { select { case c := <-l.buffer: c.Close() // nolint: errcheck default: break L } } return nil } // Addr implements net.Listener. func (l *OutboundListener) Addr() net.Addr { return &net.TCPAddr{ IP: net.IP{0, 0, 0, 0}, Port: 0, } } // Outbound is a outbound.Handler that handles gRPC connections. type Outbound struct { tag string listener *OutboundListener access sync.RWMutex closed bool } // Dispatch implements outbound.Handler. func (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) { co.access.RLock() if co.closed { common.Interrupt(link.Reader) common.Interrupt(link.Writer) co.access.RUnlock() return } closeSignal := done.New() c := net.NewConnection(net.ConnectionInputMulti(link.Writer), net.ConnectionOutputMulti(link.Reader), net.ConnectionOnClose(closeSignal)) co.listener.add(c) co.access.RUnlock() <-closeSignal.Wait() } // Tag implements outbound.Handler. func (co *Outbound) Tag() string { return co.tag } // Start implements common.Runnable. func (co *Outbound) Start() error { co.access.Lock() co.closed = false co.access.Unlock() return nil } // Close implements common.Closable. func (co *Outbound) Close() error { co.access.Lock() defer co.access.Unlock() co.closed = true return co.listener.Close() } ================================================ FILE: app/commander/service.go ================================================ // +build !confonly package commander import ( "google.golang.org/grpc" ) // Service is a Commander service. type Service interface { // Register registers the service itself to a gRPC server. Register(*grpc.Server) } ================================================ FILE: app/dispatcher/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/dispatcher/config.proto package dispatcher import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type SessionConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *SessionConfig) Reset() { *x = SessionConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_dispatcher_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SessionConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SessionConfig) ProtoMessage() {} func (x *SessionConfig) ProtoReflect() protoreflect.Message { mi := &file_app_dispatcher_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead. func (*SessionConfig) Descriptor() ([]byte, []int) { return file_app_dispatcher_config_proto_rawDescGZIP(), []int{0} } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_dispatcher_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_dispatcher_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_dispatcher_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetSettings() *SessionConfig { if x != nil { return x.Settings } return nil } var File_app_dispatcher_config_proto protoreflect.FileDescriptor var file_app_dispatcher_config_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x4e, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x44, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x5c, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x1d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x19, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_dispatcher_config_proto_rawDescOnce sync.Once file_app_dispatcher_config_proto_rawDescData = file_app_dispatcher_config_proto_rawDesc ) func file_app_dispatcher_config_proto_rawDescGZIP() []byte { file_app_dispatcher_config_proto_rawDescOnce.Do(func() { file_app_dispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dispatcher_config_proto_rawDescData) }) return file_app_dispatcher_config_proto_rawDescData } var file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_app_dispatcher_config_proto_goTypes = []interface{}{ (*SessionConfig)(nil), // 0: v2ray.core.app.dispatcher.SessionConfig (*Config)(nil), // 1: v2ray.core.app.dispatcher.Config } var file_app_dispatcher_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.app.dispatcher.Config.settings:type_name -> v2ray.core.app.dispatcher.SessionConfig 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_app_dispatcher_config_proto_init() } func file_app_dispatcher_config_proto_init() { if File_app_dispatcher_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_dispatcher_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SessionConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_dispatcher_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_dispatcher_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_dispatcher_config_proto_goTypes, DependencyIndexes: file_app_dispatcher_config_proto_depIdxs, MessageInfos: file_app_dispatcher_config_proto_msgTypes, }.Build() File_app_dispatcher_config_proto = out.File file_app_dispatcher_config_proto_rawDesc = nil file_app_dispatcher_config_proto_goTypes = nil file_app_dispatcher_config_proto_depIdxs = nil } ================================================ FILE: app/dispatcher/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.dispatcher; option csharp_namespace = "V2Ray.Core.App.Dispatcher"; option go_package = "v2ray.com/core/app/dispatcher"; option java_package = "com.v2ray.core.app.dispatcher"; option java_multiple_files = true; message SessionConfig { reserved 1; } message Config { SessionConfig settings = 1; } ================================================ FILE: app/dispatcher/default.go ================================================ // +build !confonly package dispatcher //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "strings" "sync" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/features/outbound" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" routing_session "v2ray.com/core/features/routing/session" "v2ray.com/core/features/stats" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) var ( errSniffingTimeout = newError("timeout on sniffing") ) type cachedReader struct { sync.Mutex reader *pipe.Reader cache buf.MultiBuffer } func (r *cachedReader) Cache(b *buf.Buffer) { mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100) r.Lock() if !mb.IsEmpty() { r.cache, _ = buf.MergeMulti(r.cache, mb) } b.Clear() rawBytes := b.Extend(buf.Size) n := r.cache.Copy(rawBytes) b.Resize(0, int32(n)) r.Unlock() } func (r *cachedReader) readInternal() buf.MultiBuffer { r.Lock() defer r.Unlock() if r.cache != nil && !r.cache.IsEmpty() { mb := r.cache r.cache = nil return mb } return nil } func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) { mb := r.readInternal() if mb != nil { return mb, nil } return r.reader.ReadMultiBuffer() } func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) { mb := r.readInternal() if mb != nil { return mb, nil } return r.reader.ReadMultiBufferTimeout(timeout) } func (r *cachedReader) Interrupt() { r.Lock() if r.cache != nil { r.cache = buf.ReleaseMulti(r.cache) } r.Unlock() r.reader.Interrupt() } // DefaultDispatcher is a default implementation of Dispatcher. type DefaultDispatcher struct { ohm outbound.Manager router routing.Router policy policy.Manager stats stats.Manager } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { d := new(DefaultDispatcher) if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error { return d.Init(config.(*Config), om, router, pm, sm) }); err != nil { return nil, err } return d, nil })) } // Init initializes DefaultDispatcher. func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error { d.ohm = om d.router = router d.policy = pm d.stats = sm return nil } // Type implements common.HasType. func (*DefaultDispatcher) Type() interface{} { return routing.DispatcherType() } // Start implements common.Runnable. func (*DefaultDispatcher) Start() error { return nil } // Close implements common.Closable. func (*DefaultDispatcher) Close() error { return nil } func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) { opt := pipe.OptionsFromContext(ctx) uplinkReader, uplinkWriter := pipe.New(opt...) downlinkReader, downlinkWriter := pipe.New(opt...) inboundLink := &transport.Link{ Reader: downlinkReader, Writer: uplinkWriter, } outboundLink := &transport.Link{ Reader: uplinkReader, Writer: downlinkWriter, } sessionInbound := session.InboundFromContext(ctx) var user *protocol.MemoryUser if sessionInbound != nil { user = sessionInbound.User } if user != nil && len(user.Email) > 0 { p := d.policy.ForLevel(user.Level) if p.Stats.UserUplink { name := "user>>>" + user.Email + ">>>traffic>>>uplink" if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { inboundLink.Writer = &SizeStatWriter{ Counter: c, Writer: inboundLink.Writer, } } } if p.Stats.UserDownlink { name := "user>>>" + user.Email + ">>>traffic>>>downlink" if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { outboundLink.Writer = &SizeStatWriter{ Counter: c, Writer: outboundLink.Writer, } } } } return inboundLink, outboundLink } func shouldOverride(result SniffResult, domainOverride []string) bool { for _, p := range domainOverride { if strings.HasPrefix(result.Protocol(), p) { return true } } return false } // Dispatch implements routing.Dispatcher. func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) { if !destination.IsValid() { panic("Dispatcher: Invalid destination.") } ob := &session.Outbound{ Target: destination, } ctx = session.ContextWithOutbound(ctx, ob) inbound, outbound := d.getLink(ctx) content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } sniffingRequest := content.SniffingRequest if destination.Network != net.Network_TCP || !sniffingRequest.Enabled { go d.routedDispatch(ctx, outbound, destination) } else { go func() { cReader := &cachedReader{ reader: outbound.Reader.(*pipe.Reader), } outbound.Reader = cReader result, err := sniffer(ctx, cReader) if err == nil { content.Protocol = result.Protocol() } if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) { domain := result.Domain() newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) destination.Address = net.ParseAddress(domain) ob.Target = destination } d.routedDispatch(ctx, outbound, destination) }() } return inbound, nil } func sniffer(ctx context.Context, cReader *cachedReader) (SniffResult, error) { payload := buf.New() defer payload.Release() sniffer := NewSniffer() totalAttempt := 0 for { select { case <-ctx.Done(): return nil, ctx.Err() default: totalAttempt++ if totalAttempt > 2 { return nil, errSniffingTimeout } cReader.Cache(payload) if !payload.IsEmpty() { result, err := sniffer.Sniff(payload.Bytes()) if err != common.ErrNoClue { return result, err } } if payload.IsFull() { return nil, errUnknownContent } } } } func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { var handler outbound.Handler skipRoutePick := false if content := session.ContentFromContext(ctx); content != nil { skipRoutePick = content.SkipRoutePick } if d.router != nil && !skipRoutePick { if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil { tag := route.GetOutboundTag() if h := d.ohm.GetHandler(tag); h != nil { newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) handler = h } else { newError("non existing tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } } else { newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx)) } } if handler == nil { handler = d.ohm.GetDefaultHandler() } if handler == nil { newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx)) common.Close(link.Writer) common.Interrupt(link.Reader) return } if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil { if tag := handler.Tag(); tag != "" { accessMessage.Detour = tag } log.Record(accessMessage) } handler.Dispatch(ctx, link) } ================================================ FILE: app/dispatcher/dispatcher.go ================================================ // +build !confonly package dispatcher //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: app/dispatcher/errors.generated.go ================================================ package dispatcher import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/dispatcher/sniffer.go ================================================ // +build !confonly package dispatcher import ( "v2ray.com/core/common" "v2ray.com/core/common/protocol/bittorrent" "v2ray.com/core/common/protocol/http" "v2ray.com/core/common/protocol/tls" ) type SniffResult interface { Protocol() string Domain() string } type protocolSniffer func([]byte) (SniffResult, error) type Sniffer struct { sniffer []protocolSniffer } func NewSniffer() *Sniffer { return &Sniffer{ sniffer: []protocolSniffer{ func(b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, func(b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, func(b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, }, } } var errUnknownContent = newError("unknown content") func (s *Sniffer) Sniff(payload []byte) (SniffResult, error) { var pendingSniffer []protocolSniffer for _, s := range s.sniffer { result, err := s(payload) if err == common.ErrNoClue { pendingSniffer = append(pendingSniffer, s) continue } if err == nil && result != nil { return result, nil } } if len(pendingSniffer) > 0 { s.sniffer = pendingSniffer return nil, common.ErrNoClue } return nil, errUnknownContent } ================================================ FILE: app/dispatcher/stats.go ================================================ // +build !confonly package dispatcher import ( "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/features/stats" ) type SizeStatWriter struct { Counter stats.Counter Writer buf.Writer } func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { w.Counter.Add(int64(mb.Len())) return w.Writer.WriteMultiBuffer(mb) } func (w *SizeStatWriter) Close() error { return common.Close(w.Writer) } func (w *SizeStatWriter) Interrupt() { common.Interrupt(w.Writer) } ================================================ FILE: app/dispatcher/stats_test.go ================================================ package dispatcher_test import ( "testing" . "v2ray.com/core/app/dispatcher" "v2ray.com/core/common" "v2ray.com/core/common/buf" ) type TestCounter int64 func (c *TestCounter) Value() int64 { return int64(*c) } func (c *TestCounter) Add(v int64) int64 { x := int64(*c) + v *c = TestCounter(x) return x } func (c *TestCounter) Set(v int64) int64 { *c = TestCounter(v) return v } func TestStatsWriter(t *testing.T) { var c TestCounter writer := &SizeStatWriter{ Counter: &c, Writer: buf.Discard, } mb := buf.MergeBytes(nil, []byte("abcd")) common.Must(writer.WriteMultiBuffer(mb)) mb = buf.MergeBytes(nil, []byte("efg")) common.Must(writer.WriteMultiBuffer(mb)) if c.Value() != 7 { t.Fatal("unexpected counter value. want 7, but got ", c.Value()) } } ================================================ FILE: app/dns/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/dns/config.proto package dns import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" router "v2ray.com/core/app/router" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type DomainMatchingType int32 const ( DomainMatchingType_Full DomainMatchingType = 0 DomainMatchingType_Subdomain DomainMatchingType = 1 DomainMatchingType_Keyword DomainMatchingType = 2 DomainMatchingType_Regex DomainMatchingType = 3 ) // Enum value maps for DomainMatchingType. var ( DomainMatchingType_name = map[int32]string{ 0: "Full", 1: "Subdomain", 2: "Keyword", 3: "Regex", } DomainMatchingType_value = map[string]int32{ "Full": 0, "Subdomain": 1, "Keyword": 2, "Regex": 3, } ) func (x DomainMatchingType) Enum() *DomainMatchingType { p := new(DomainMatchingType) *p = x return p } func (x DomainMatchingType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor { return file_app_dns_config_proto_enumTypes[0].Descriptor() } func (DomainMatchingType) Type() protoreflect.EnumType { return &file_app_dns_config_proto_enumTypes[0] } func (x DomainMatchingType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use DomainMatchingType.Descriptor instead. func (DomainMatchingType) EnumDescriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{0} } type NameServer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"` Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"` OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"` } func (x *NameServer) Reset() { *x = NameServer{} if protoimpl.UnsafeEnabled { mi := &file_app_dns_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *NameServer) String() string { return protoimpl.X.MessageStringOf(x) } func (*NameServer) ProtoMessage() {} func (x *NameServer) ProtoReflect() protoreflect.Message { mi := &file_app_dns_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use NameServer.ProtoReflect.Descriptor instead. func (*NameServer) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{0} } func (x *NameServer) GetAddress() *net.Endpoint { if x != nil { return x.Address } return nil } func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain { if x != nil { return x.PrioritizedDomain } return nil } func (x *NameServer) GetGeoip() []*router.GeoIP { if x != nil { return x.Geoip } return nil } func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule { if x != nil { return x.OriginalRules } return nil } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Nameservers used by this DNS. Only traditional UDP servers are support at // the moment. A special value 'localhost' as a domain address can be set to // use DNS on local system. // // Deprecated: Do not use. NameServers []*net.Endpoint `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"` // NameServer list used by this DNS client. NameServer []*NameServer `protobuf:"bytes,5,rep,name=name_server,json=nameServer,proto3" json:"name_server,omitempty"` // Static hosts. Domain to IP. // Deprecated. Use static_hosts. // // Deprecated: Do not use. Hosts map[string]*net.IPOrDomain `protobuf:"bytes,2,rep,name=Hosts,proto3" json:"Hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes // (IPv6). ClientIp []byte `protobuf:"bytes,3,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"` // Tag is the inbound tag of DNS client. Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_dns_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_dns_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{1} } // Deprecated: Do not use. func (x *Config) GetNameServers() []*net.Endpoint { if x != nil { return x.NameServers } return nil } func (x *Config) GetNameServer() []*NameServer { if x != nil { return x.NameServer } return nil } // Deprecated: Do not use. func (x *Config) GetHosts() map[string]*net.IPOrDomain { if x != nil { return x.Hosts } return nil } func (x *Config) GetClientIp() []byte { if x != nil { return x.ClientIp } return nil } func (x *Config) GetStaticHosts() []*Config_HostMapping { if x != nil { return x.StaticHosts } return nil } func (x *Config) GetTag() string { if x != nil { return x.Tag } return "" } type NameServer_PriorityDomain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.dns.DomainMatchingType" json:"type,omitempty"` Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` } func (x *NameServer_PriorityDomain) Reset() { *x = NameServer_PriorityDomain{} if protoimpl.UnsafeEnabled { mi := &file_app_dns_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *NameServer_PriorityDomain) String() string { return protoimpl.X.MessageStringOf(x) } func (*NameServer_PriorityDomain) ProtoMessage() {} func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message { mi := &file_app_dns_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead. func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0} } func (x *NameServer_PriorityDomain) GetType() DomainMatchingType { if x != nil { return x.Type } return DomainMatchingType_Full } func (x *NameServer_PriorityDomain) GetDomain() string { if x != nil { return x.Domain } return "" } type NameServer_OriginalRule struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Rule string `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"` Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` } func (x *NameServer_OriginalRule) Reset() { *x = NameServer_OriginalRule{} if protoimpl.UnsafeEnabled { mi := &file_app_dns_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *NameServer_OriginalRule) String() string { return protoimpl.X.MessageStringOf(x) } func (*NameServer_OriginalRule) ProtoMessage() {} func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message { mi := &file_app_dns_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead. func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1} } func (x *NameServer_OriginalRule) GetRule() string { if x != nil { return x.Rule } return "" } func (x *NameServer_OriginalRule) GetSize() uint32 { if x != nil { return x.Size } return 0 } type Config_HostMapping struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.dns.DomainMatchingType" json:"type,omitempty"` Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"` // ProxiedDomain indicates the mapped domain has the same IP address on this // domain. V2Ray will use this domain for IP queries. This field is only // effective if ip is empty. ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"` } func (x *Config_HostMapping) Reset() { *x = Config_HostMapping{} if protoimpl.UnsafeEnabled { mi := &file_app_dns_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config_HostMapping) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config_HostMapping) ProtoMessage() {} func (x *Config_HostMapping) ProtoReflect() protoreflect.Message { mi := &file_app_dns_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config_HostMapping.ProtoReflect.Descriptor instead. func (*Config_HostMapping) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1} } func (x *Config_HostMapping) GetType() DomainMatchingType { if x != nil { return x.Type } return DomainMatchingType_Full } func (x *Config_HostMapping) GetDomain() string { if x != nil { return x.Domain } return "" } func (x *Config_HostMapping) GetIp() [][]byte { if x != nil { return x.Ip } return nil } func (x *Config_HostMapping) GetProxiedDomain() string { if x != nil { return x.ProxiedDomain } return "" } var File_app_dns_config_proto protoreflect.FileDescriptor var file_app_dns_config_proto_rawDesc = []byte{ 0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x5c, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x52, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x64, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xc3, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x45, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x3f, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x49, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x1a, 0x5b, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x98, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42, 0x47, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x12, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_dns_config_proto_rawDescOnce sync.Once file_app_dns_config_proto_rawDescData = file_app_dns_config_proto_rawDesc ) func file_app_dns_config_proto_rawDescGZIP() []byte { file_app_dns_config_proto_rawDescOnce.Do(func() { file_app_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dns_config_proto_rawDescData) }) return file_app_dns_config_proto_rawDescData } var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_app_dns_config_proto_goTypes = []interface{}{ (DomainMatchingType)(0), // 0: v2ray.core.app.dns.DomainMatchingType (*NameServer)(nil), // 1: v2ray.core.app.dns.NameServer (*Config)(nil), // 2: v2ray.core.app.dns.Config (*NameServer_PriorityDomain)(nil), // 3: v2ray.core.app.dns.NameServer.PriorityDomain (*NameServer_OriginalRule)(nil), // 4: v2ray.core.app.dns.NameServer.OriginalRule nil, // 5: v2ray.core.app.dns.Config.HostsEntry (*Config_HostMapping)(nil), // 6: v2ray.core.app.dns.Config.HostMapping (*net.Endpoint)(nil), // 7: v2ray.core.common.net.Endpoint (*router.GeoIP)(nil), // 8: v2ray.core.app.router.GeoIP (*net.IPOrDomain)(nil), // 9: v2ray.core.common.net.IPOrDomain } var file_app_dns_config_proto_depIdxs = []int32{ 7, // 0: v2ray.core.app.dns.NameServer.address:type_name -> v2ray.core.common.net.Endpoint 3, // 1: v2ray.core.app.dns.NameServer.prioritized_domain:type_name -> v2ray.core.app.dns.NameServer.PriorityDomain 8, // 2: v2ray.core.app.dns.NameServer.geoip:type_name -> v2ray.core.app.router.GeoIP 4, // 3: v2ray.core.app.dns.NameServer.original_rules:type_name -> v2ray.core.app.dns.NameServer.OriginalRule 7, // 4: v2ray.core.app.dns.Config.NameServers:type_name -> v2ray.core.common.net.Endpoint 1, // 5: v2ray.core.app.dns.Config.name_server:type_name -> v2ray.core.app.dns.NameServer 5, // 6: v2ray.core.app.dns.Config.Hosts:type_name -> v2ray.core.app.dns.Config.HostsEntry 6, // 7: v2ray.core.app.dns.Config.static_hosts:type_name -> v2ray.core.app.dns.Config.HostMapping 0, // 8: v2ray.core.app.dns.NameServer.PriorityDomain.type:type_name -> v2ray.core.app.dns.DomainMatchingType 9, // 9: v2ray.core.app.dns.Config.HostsEntry.value:type_name -> v2ray.core.common.net.IPOrDomain 0, // 10: v2ray.core.app.dns.Config.HostMapping.type:type_name -> v2ray.core.app.dns.DomainMatchingType 11, // [11:11] is the sub-list for method output_type 11, // [11:11] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name } func init() { file_app_dns_config_proto_init() } func file_app_dns_config_proto_init() { if File_app_dns_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NameServer); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_dns_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NameServer_PriorityDomain); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_dns_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NameServer_OriginalRule); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_dns_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config_HostMapping); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_dns_config_proto_rawDesc, NumEnums: 1, NumMessages: 6, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_dns_config_proto_goTypes, DependencyIndexes: file_app_dns_config_proto_depIdxs, EnumInfos: file_app_dns_config_proto_enumTypes, MessageInfos: file_app_dns_config_proto_msgTypes, }.Build() File_app_dns_config_proto = out.File file_app_dns_config_proto_rawDesc = nil file_app_dns_config_proto_goTypes = nil file_app_dns_config_proto_depIdxs = nil } ================================================ FILE: app/dns/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.dns; option csharp_namespace = "V2Ray.Core.App.Dns"; option go_package = "v2ray.com/core/app/dns"; option java_package = "com.v2ray.core.app.dns"; option java_multiple_files = true; import "common/net/address.proto"; import "common/net/destination.proto"; import "app/router/config.proto"; message NameServer { v2ray.core.common.net.Endpoint address = 1; message PriorityDomain { DomainMatchingType type = 1; string domain = 2; } message OriginalRule { string rule = 1; uint32 size = 2; } repeated PriorityDomain prioritized_domain = 2; repeated v2ray.core.app.router.GeoIP geoip = 3; repeated OriginalRule original_rules = 4; } enum DomainMatchingType { Full = 0; Subdomain = 1; Keyword = 2; Regex = 3; } message Config { // Nameservers used by this DNS. Only traditional UDP servers are support at // the moment. A special value 'localhost' as a domain address can be set to // use DNS on local system. repeated v2ray.core.common.net.Endpoint NameServers = 1 [deprecated = true]; // NameServer list used by this DNS client. repeated NameServer name_server = 5; // Static hosts. Domain to IP. // Deprecated. Use static_hosts. map Hosts = 2 [deprecated = true]; // Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes // (IPv6). bytes client_ip = 3; message HostMapping { DomainMatchingType type = 1; string domain = 2; repeated bytes ip = 3; // ProxiedDomain indicates the mapped domain has the same IP address on this // domain. V2Ray will use this domain for IP queries. This field is only // effective if ip is empty. string proxied_domain = 4; } repeated HostMapping static_hosts = 4; // Tag is the inbound tag of DNS client. string tag = 6; } ================================================ FILE: app/dns/dns.go ================================================ // Package dns is an implementation of core.DNS feature. package dns //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: app/dns/dnscommon.go ================================================ // +build !confonly package dns import ( "encoding/binary" "time" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" dns_feature "v2ray.com/core/features/dns" ) // Fqdn normalize domain make sure it ends with '.' func Fqdn(domain string) string { if len(domain) > 0 && domain[len(domain)-1] == '.' { return domain } return domain + "." } type record struct { A *IPRecord AAAA *IPRecord } // IPRecord is a cacheable item for a resolved domain type IPRecord struct { ReqID uint16 IP []net.Address Expire time.Time RCode dnsmessage.RCode } func (r *IPRecord) getIPs() ([]net.Address, error) { if r == nil || r.Expire.Before(time.Now()) { return nil, errRecordNotFound } if r.RCode != dnsmessage.RCodeSuccess { return nil, dns_feature.RCodeError(r.RCode) } return r.IP, nil } func isNewer(baseRec *IPRecord, newRec *IPRecord) bool { if newRec == nil { return false } if baseRec == nil { return true } return baseRec.Expire.Before(newRec.Expire) } var ( errRecordNotFound = errors.New("record not found") ) type dnsRequest struct { reqType dnsmessage.Type domain string start time.Time expire time.Time msg *dnsmessage.Message } func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource { if len(clientIP) == 0 { return nil } var netmask int var family uint16 if len(clientIP) == 4 { family = 1 netmask = 24 // 24 for IPV4, 96 for IPv6 } else { family = 2 netmask = 96 } b := make([]byte, 4) binary.BigEndian.PutUint16(b[0:], family) b[2] = byte(netmask) b[3] = 0 switch family { case 1: ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8)) needLength := (netmask + 8 - 1) / 8 // division rounding up b = append(b, ip[:needLength]...) case 2: ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8)) needLength := (netmask + 8 - 1) / 8 // division rounding up b = append(b, ip[:needLength]...) } const EDNS0SUBNET = 0x08 opt := new(dnsmessage.Resource) common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true)) opt.Body = &dnsmessage.OPTResource{ Options: []dnsmessage.Option{ { Code: EDNS0SUBNET, Data: b, }, }, } return opt } func buildReqMsgs(domain string, option IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest { qA := dnsmessage.Question{ Name: dnsmessage.MustNewName(domain), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, } qAAAA := dnsmessage.Question{ Name: dnsmessage.MustNewName(domain), Type: dnsmessage.TypeAAAA, Class: dnsmessage.ClassINET, } var reqs []*dnsRequest now := time.Now() if option.IPv4Enable { msg := new(dnsmessage.Message) msg.Header.ID = reqIDGen() msg.Header.RecursionDesired = true msg.Questions = []dnsmessage.Question{qA} if reqOpts != nil { msg.Additionals = append(msg.Additionals, *reqOpts) } reqs = append(reqs, &dnsRequest{ reqType: dnsmessage.TypeA, domain: domain, start: now, msg: msg, }) } if option.IPv6Enable { msg := new(dnsmessage.Message) msg.Header.ID = reqIDGen() msg.Header.RecursionDesired = true msg.Questions = []dnsmessage.Question{qAAAA} if reqOpts != nil { msg.Additionals = append(msg.Additionals, *reqOpts) } reqs = append(reqs, &dnsRequest{ reqType: dnsmessage.TypeAAAA, domain: domain, start: now, msg: msg, }) } return reqs } // parseResponse parse DNS answers from the returned payload func parseResponse(payload []byte) (*IPRecord, error) { var parser dnsmessage.Parser h, err := parser.Start(payload) if err != nil { return nil, newError("failed to parse DNS response").Base(err).AtWarning() } if err := parser.SkipAllQuestions(); err != nil { return nil, newError("failed to skip questions in DNS response").Base(err).AtWarning() } now := time.Now() ipRecord := &IPRecord{ ReqID: h.ID, RCode: h.RCode, Expire: now.Add(time.Second * 600), } L: for { ah, err := parser.AnswerHeader() if err != nil { if err != dnsmessage.ErrSectionDone { newError("failed to parse answer section for domain: ", ah.Name.String()).Base(err).WriteToLog() } break } ttl := ah.TTL if ttl == 0 { ttl = 600 } expire := now.Add(time.Duration(ttl) * time.Second) if ipRecord.Expire.After(expire) { ipRecord.Expire = expire } switch ah.Type { case dnsmessage.TypeA: ans, err := parser.AResource() if err != nil { newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog() break L } ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:])) case dnsmessage.TypeAAAA: ans, err := parser.AAAAResource() if err != nil { newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog() break L } ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:])) default: if err := parser.SkipAnswer(); err != nil { newError("failed to skip answer").Base(err).WriteToLog() break L } continue } } return ipRecord, nil } ================================================ FILE: app/dns/dnscommon_test.go ================================================ // +build !confonly package dns import ( "math/rand" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/miekg/dns" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core/common" "v2ray.com/core/common/net" ) func Test_parseResponse(t *testing.T) { var p [][]byte ans := new(dns.Msg) ans.Id = 0 p = append(p, common.Must2(ans.Pack()).([]byte)) p = append(p, []byte{}) ans = new(dns.Msg) ans.Id = 1 ans.Answer = append(ans.Answer, common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")).(dns.RR), common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")).(dns.RR), ) p = append(p, common.Must2(ans.Pack()).([]byte)) ans = new(dns.Msg) ans.Id = 2 ans.Answer = append(ans.Answer, common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")).(dns.RR), common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")).(dns.RR), common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")).(dns.RR), ) p = append(p, common.Must2(ans.Pack()).([]byte)) tests := []struct { name string want *IPRecord wantErr bool }{ {"empty", &IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess}, false, }, {"error", nil, true, }, {"a record", &IPRecord{1, []net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")}, time.Time{}, dnsmessage.RCodeSuccess}, false, }, {"aaaa record", &IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess}, false, }, } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseResponse(p[i]) if (err != nil) != tt.wantErr { t.Errorf("handleResponse() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { // reset the time got.Expire = time.Time{} } if cmp.Diff(got, tt.want) != "" { t.Errorf(cmp.Diff(got, tt.want)) // t.Errorf("handleResponse() = %#v, want %#v", got, tt.want) } }) } } func Test_buildReqMsgs(t *testing.T) { stubID := func() uint16 { return uint16(rand.Uint32()) } type args struct { domain string option IPOption reqOpts *dnsmessage.Resource } tests := []struct { name string args args want int }{ {"dual stack", args{"test.com", IPOption{true, true}, nil}, 2}, {"ipv4 only", args{"test.com", IPOption{true, false}, nil}, 1}, {"ipv6 only", args{"test.com", IPOption{false, true}, nil}, 1}, {"none/error", args{"test.com", IPOption{false, false}, nil}, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) { t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want) } }) } } func Test_genEDNS0Options(t *testing.T) { type args struct { clientIP net.IP } tests := []struct { name string args args want *dnsmessage.Resource }{ // TODO: Add test cases. {"ipv4", args{net.ParseIP("4.3.2.1")}, nil}, {"ipv6", args{net.ParseIP("2001::4321")}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := genEDNS0Options(tt.args.clientIP); got == nil { t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want) } }) } } func TestFqdn(t *testing.T) { type args struct { domain string } tests := []struct { name string args args want string }{ {"with fqdn", args{"www.v2ray.com."}, "www.v2ray.com."}, {"without fqdn", args{"www.v2ray.com"}, "www.v2ray.com."}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Fqdn(tt.args.domain); got != tt.want { t.Errorf("Fqdn() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: app/dns/dohdns.go ================================================ // +build !confonly package dns import ( "bytes" "context" "fmt" "io" "io/ioutil" "net/http" "net/url" "sync" "sync/atomic" "time" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/dns" "v2ray.com/core/common/session" "v2ray.com/core/common/signal/pubsub" "v2ray.com/core/common/task" dns_feature "v2ray.com/core/features/dns" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" ) // DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format, // which is compatible with traditional dns over udp(RFC1035), // thus most of the DOH implementation is copied from udpns.go type DoHNameServer struct { sync.RWMutex ips map[string]record pub *pubsub.Service cleanup *task.Periodic reqID uint32 clientIP net.IP httpClient *http.Client dohURL string name string } // NewDoHNameServer creates DOH client object for remote resolving func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.IP) (*DoHNameServer, error) { newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog() s := baseDOHNameServer(url, "DOH", clientIP) // Dispatched connection will be closed (interrupted) after each request // This makes DOH inefficient without a keep-alived connection // See: core/app/proxyman/outbound/handler.go:113 // Using mux (https request wrapped in a stream layer) improves the situation. // Recommend to use NewDoHLocalNameServer (DOHL:) if v2ray instance is running on // a normal network eg. the server side of v2ray tr := &http.Transport{ MaxIdleConns: 30, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 30 * time.Second, ForceAttemptHTTP2: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { dest, err := net.ParseDestination(network + ":" + addr) if err != nil { return nil, err } link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return nil, err } return net.NewConnection( net.ConnectionInputMulti(link.Writer), net.ConnectionOutputMulti(link.Reader), ), nil }, } dispatchedClient := &http.Client{ Transport: tr, Timeout: 60 * time.Second, } s.httpClient = dispatchedClient return s, nil } // NewDoHLocalNameServer creates DOH client object for local resolving func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer { url.Scheme = "https" s := baseDOHNameServer(url, "DOHL", clientIP) tr := &http.Transport{ IdleConnTimeout: 90 * time.Second, ForceAttemptHTTP2: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { dest, err := net.ParseDestination(network + ":" + addr) if err != nil { return nil, err } conn, err := internet.DialSystem(ctx, dest, nil) if err != nil { return nil, err } return conn, nil }, } s.httpClient = &http.Client{ Timeout: time.Second * 180, Transport: tr, } newError("DNS: created Local DOH client for ", url.String()).AtInfo().WriteToLog() return s } func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameServer { s := &DoHNameServer{ ips: make(map[string]record), clientIP: clientIP, pub: pubsub.NewService(), name: prefix + "//" + url.Host, dohURL: url.String(), } s.cleanup = &task.Periodic{ Interval: time.Minute, Execute: s.Cleanup, } return s } // Name returns client name func (s *DoHNameServer) Name() string { return s.name } // Cleanup clears expired items from cache func (s *DoHNameServer) Cleanup() error { now := time.Now() s.Lock() defer s.Unlock() if len(s.ips) == 0 { return newError("nothing to do. stopping...") } for domain, record := range s.ips { if record.A != nil && record.A.Expire.Before(now) { record.A = nil } if record.AAAA != nil && record.AAAA.Expire.Before(now) { record.AAAA = nil } if record.A == nil && record.AAAA == nil { newError(s.name, " cleanup ", domain).AtDebug().WriteToLog() delete(s.ips, domain) } else { s.ips[domain] = record } } if len(s.ips) == 0 { s.ips = make(map[string]record) } return nil } func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) { elapsed := time.Since(req.start) s.Lock() rec := s.ips[req.domain] updated := false switch req.reqType { case dnsmessage.TypeA: if isNewer(rec.A, ipRec) { rec.A = ipRec updated = true } case dnsmessage.TypeAAAA: addr := make([]net.Address, 0) for _, ip := range ipRec.IP { if len(ip.IP()) == net.IPv6len { addr = append(addr, ip) } } ipRec.IP = addr if isNewer(rec.AAAA, ipRec) { rec.AAAA = ipRec updated = true } } newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() if updated { s.ips[req.domain] = rec } switch req.reqType { case dnsmessage.TypeA: s.pub.Publish(req.domain+"4", nil) case dnsmessage.TypeAAAA: s.pub.Publish(req.domain+"6", nil) } s.Unlock() common.Must(s.cleanup.Start()) } func (s *DoHNameServer) newReqID() uint16 { return uint16(atomic.AddUint32(&s.reqID, 1)) } func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) var deadline time.Time if d, ok := ctx.Deadline(); ok { deadline = d } else { deadline = time.Now().Add(time.Second * 5) } for _, req := range reqs { go func(r *dnsRequest) { // generate new context for each req, using same context // may cause reqs all aborted if any one encounter an error dnsCtx := context.Background() // reserve internal dns server requested Inbound if inbound := session.InboundFromContext(ctx); inbound != nil { dnsCtx = session.ContextWithInbound(dnsCtx, inbound) } dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{ Protocol: "https", SkipRoutePick: true, }) // forced to use mux for DOH dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true) var cancel context.CancelFunc dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline) defer cancel() b, err := dns.PackMessage(r.msg) if err != nil { newError("failed to pack dns query").Base(err).AtError().WriteToLog() return } resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) if err != nil { newError("failed to retrieve response").Base(err).AtError().WriteToLog() return } rec, err := parseResponse(resp) if err != nil { newError("failed to handle DOH response").Base(err).AtError().WriteToLog() return } s.updateIP(r, rec) }(req) } } func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) { body := bytes.NewBuffer(b) req, err := http.NewRequest("POST", s.dohURL, body) if err != nil { return nil, err } req.Header.Add("Accept", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message") resp, err := s.httpClient.Do(req.WithContext(ctx)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { io.Copy(ioutil.Discard, resp.Body) // flush resp.Body so that the conn is reusable return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode) } return ioutil.ReadAll(resp.Body) } func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { s.RLock() record, found := s.ips[domain] s.RUnlock() if !found { return nil, errRecordNotFound } var ips []net.Address var lastErr error if option.IPv6Enable && record.AAAA != nil && record.AAAA.RCode == dnsmessage.RCodeSuccess { aaaa, err := record.AAAA.getIPs() if err != nil { lastErr = err } ips = append(ips, aaaa...) } if option.IPv4Enable && record.A != nil && record.A.RCode == dnsmessage.RCodeSuccess { a, err := record.A.getIPs() if err != nil { lastErr = err } ips = append(ips, a...) } if len(ips) > 0 { return toNetIP(ips), nil } if lastErr != nil { return nil, lastErr } if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) { return nil, dns_feature.ErrEmptyResponse } return nil, errRecordNotFound } // QueryIP is called from dns.Server->queryIPTimeout func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { fqdn := Fqdn(domain) ips, err := s.findIPsForDomain(fqdn, option) if err != errRecordNotFound { newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog() return ips, err } // ipv4 and ipv6 belong to different subscription groups var sub4, sub6 *pubsub.Subscriber if option.IPv4Enable { sub4 = s.pub.Subscribe(fqdn + "4") defer sub4.Close() } if option.IPv6Enable { sub6 = s.pub.Subscribe(fqdn + "6") defer sub6.Close() } done := make(chan interface{}) go func() { if sub4 != nil { select { case <-sub4.Wait(): case <-ctx.Done(): } } if sub6 != nil { select { case <-sub6.Wait(): case <-ctx.Done(): } } close(done) }() s.sendQuery(ctx, fqdn, option) for { ips, err := s.findIPsForDomain(fqdn, option) if err != errRecordNotFound { return ips, err } select { case <-ctx.Done(): return nil, ctx.Err() case <-done: } } } ================================================ FILE: app/dns/errors.generated.go ================================================ package dns import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/dns/hosts.go ================================================ // +build !confonly package dns import ( "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/strmatcher" "v2ray.com/core/features" ) // StaticHosts represents static domain-ip mapping in DNS server. type StaticHosts struct { ips [][]net.Address matchers *strmatcher.MatcherGroup } var typeMap = map[DomainMatchingType]strmatcher.Type{ DomainMatchingType_Full: strmatcher.Full, DomainMatchingType_Subdomain: strmatcher.Domain, DomainMatchingType_Keyword: strmatcher.Substr, DomainMatchingType_Regex: strmatcher.Regex, } func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) { strMType, f := typeMap[t] if !f { return nil, newError("unknown mapping type", t).AtWarning() } matcher, err := strMType.New(domain) if err != nil { return nil, newError("failed to create str matcher").Base(err) } return matcher, nil } // NewStaticHosts creates a new StaticHosts instance. func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) { g := new(strmatcher.MatcherGroup) sh := &StaticHosts{ ips: make([][]net.Address, len(hosts)+len(legacy)+16), matchers: g, } if legacy != nil { features.PrintDeprecatedFeatureWarning("simple host mapping") for domain, ip := range legacy { matcher, err := strmatcher.Full.New(domain) common.Must(err) id := g.Add(matcher) address := ip.AsAddress() if address.Family().IsDomain() { return nil, newError("invalid domain address in static hosts: ", address.Domain()).AtWarning() } sh.ips[id] = []net.Address{address} } } for _, mapping := range hosts { matcher, err := toStrMatcher(mapping.Type, mapping.Domain) if err != nil { return nil, newError("failed to create domain matcher").Base(err) } id := g.Add(matcher) ips := make([]net.Address, 0, len(mapping.Ip)+1) if len(mapping.Ip) > 0 { for _, ip := range mapping.Ip { addr := net.IPAddress(ip) if addr == nil { return nil, newError("invalid IP address in static hosts: ", ip).AtWarning() } ips = append(ips, addr) } } else if len(mapping.ProxiedDomain) > 0 { ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) } else { return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning() } // Special handling for localhost IPv6. This is a dirty workaround as JSON config supports only single IP mapping. if len(ips) == 1 && ips[0] == net.LocalHostIP { ips = append(ips, net.LocalHostIPv6) } sh.ips[id] = ips } return sh, nil } func filterIP(ips []net.Address, option IPOption) []net.Address { filtered := make([]net.Address, 0, len(ips)) for _, ip := range ips { if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) { filtered = append(filtered, ip) } } if len(filtered) == 0 { return nil } return filtered } // LookupIP returns IP address for the given domain, if exists in this StaticHosts. func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.Address { indices := h.matchers.Match(domain) if len(indices) == 0 { return nil } ips := []net.Address{} for _, id := range indices { ips = append(ips, h.ips[id]...) } if len(ips) == 1 && ips[0].Family().IsDomain() { return ips } return filterIP(ips, option) } ================================================ FILE: app/dns/hosts_test.go ================================================ package dns_test import ( "testing" "github.com/google/go-cmp/cmp" . "v2ray.com/core/app/dns" "v2ray.com/core/common" "v2ray.com/core/common/net" ) func TestStaticHosts(t *testing.T) { pb := []*Config_HostMapping{ { Type: DomainMatchingType_Full, Domain: "v2ray.com", Ip: [][]byte{ {1, 1, 1, 1}, }, }, { Type: DomainMatchingType_Subdomain, Domain: "v2ray.cn", Ip: [][]byte{ {2, 2, 2, 2}, }, }, { Type: DomainMatchingType_Subdomain, Domain: "baidu.com", Ip: [][]byte{ {127, 0, 0, 1}, }, }, } hosts, err := NewStaticHosts(pb, nil) common.Must(err) { ips := hosts.LookupIP("v2ray.com", IPOption{ IPv4Enable: true, IPv6Enable: true, }) if len(ips) != 1 { t.Error("expect 1 IP, but got ", len(ips)) } if diff := cmp.Diff([]byte(ips[0].IP()), []byte{1, 1, 1, 1}); diff != "" { t.Error(diff) } } { ips := hosts.LookupIP("www.v2ray.cn", IPOption{ IPv4Enable: true, IPv6Enable: true, }) if len(ips) != 1 { t.Error("expect 1 IP, but got ", len(ips)) } if diff := cmp.Diff([]byte(ips[0].IP()), []byte{2, 2, 2, 2}); diff != "" { t.Error(diff) } } { ips := hosts.LookupIP("baidu.com", IPOption{ IPv4Enable: false, IPv6Enable: true, }) if len(ips) != 1 { t.Error("expect 1 IP, but got ", len(ips)) } if diff := cmp.Diff([]byte(ips[0].IP()), []byte(net.LocalHostIPv6.IP())); diff != "" { t.Error(diff) } } } ================================================ FILE: app/dns/nameserver.go ================================================ // +build !confonly package dns import ( "context" "v2ray.com/core/common/net" "v2ray.com/core/features/dns/localdns" ) // IPOption is an object for IP query options. type IPOption struct { IPv4Enable bool IPv6Enable bool } // Client is the interface for DNS client. type Client interface { // Name of the Client. Name() string // QueryIP sends IP queries to its configured server. QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) } type localNameServer struct { client *localdns.Client } func (s *localNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { if option.IPv4Enable && option.IPv6Enable { return s.client.LookupIP(domain) } if option.IPv4Enable { return s.client.LookupIPv4(domain) } if option.IPv6Enable { return s.client.LookupIPv6(domain) } return nil, newError("neither IPv4 nor IPv6 is enabled") } func (s *localNameServer) Name() string { return "localhost" } func NewLocalNameServer() *localNameServer { newError("DNS: created localhost client").AtInfo().WriteToLog() return &localNameServer{ client: localdns.New(), } } ================================================ FILE: app/dns/nameserver_test.go ================================================ package dns_test import ( "context" "testing" "time" . "v2ray.com/core/app/dns" "v2ray.com/core/common" ) func TestLocalNameServer(t *testing.T) { s := NewLocalNameServer() ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) ips, err := s.QueryIP(ctx, "google.com", IPOption{ IPv4Enable: true, IPv6Enable: true, }) cancel() common.Must(err) if len(ips) == 0 { t.Error("expect some ips, but got 0") } } ================================================ FILE: app/dns/server.go ================================================ // +build !confonly package dns //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "fmt" "log" "net/url" "strings" "sync" "time" "v2ray.com/core" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/common/strmatcher" "v2ray.com/core/common/uuid" "v2ray.com/core/features" "v2ray.com/core/features/dns" "v2ray.com/core/features/routing" ) // Server is a DNS rely server. type Server struct { sync.Mutex hosts *StaticHosts clientIP net.IP clients []Client // clientIdx -> Client ipIndexMap []*MultiGeoIPMatcher // clientIdx -> *MultiGeoIPMatcher domainRules [][]string // clientIdx -> domainRuleIdx -> DomainRule domainMatcher strmatcher.IndexMatcher matcherInfos []DomainMatcherInfo // matcherIdx -> DomainMatcherInfo tag string } // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher type DomainMatcherInfo struct { clientIdx uint16 domainRuleIdx uint16 } // MultiGeoIPMatcher for match type MultiGeoIPMatcher struct { matchers []*router.GeoIPMatcher } var errExpectedIPNonMatch = errors.New("expectIPs not match") // Match check ip match func (c *MultiGeoIPMatcher) Match(ip net.IP) bool { for _, matcher := range c.matchers { if matcher.Match(ip) { return true } } return false } // HasMatcher check has matcher func (c *MultiGeoIPMatcher) HasMatcher() bool { return len(c.matchers) > 0 } func generateRandomTag() string { id := uuid.New() return "v2ray.system." + id.String() } // New creates a new DNS server with given configuration. func New(ctx context.Context, config *Config) (*Server, error) { server := &Server{ clients: make([]Client, 0, len(config.NameServers)+len(config.NameServer)), tag: config.Tag, } if server.tag == "" { server.tag = generateRandomTag() } if len(config.ClientIp) > 0 { if len(config.ClientIp) != net.IPv4len && len(config.ClientIp) != net.IPv6len { return nil, newError("unexpected IP length", len(config.ClientIp)) } server.clientIP = net.IP(config.ClientIp) } hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts) if err != nil { return nil, newError("failed to create hosts").Base(err) } server.hosts = hosts addNameServer := func(ns *NameServer) int { endpoint := ns.Address address := endpoint.Address.AsAddress() if address.Family().IsDomain() && address.Domain() == "localhost" { server.clients = append(server.clients, NewLocalNameServer()) // Priotize local domains with specific TLDs or without any dot to local DNS // References: // https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml // https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan localTLDsAndDotlessDomains := []*NameServer_PriorityDomain{ {Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot {Type: DomainMatchingType_Subdomain, Domain: "local"}, {Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, {Type: DomainMatchingType_Subdomain, Domain: "localhost"}, {Type: DomainMatchingType_Subdomain, Domain: "lan"}, {Type: DomainMatchingType_Subdomain, Domain: "home.arpa"}, {Type: DomainMatchingType_Subdomain, Domain: "example"}, {Type: DomainMatchingType_Subdomain, Domain: "invalid"}, {Type: DomainMatchingType_Subdomain, Domain: "test"}, } ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...) } else if address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://") { // URI schemed string treated as domain // DOH Local mode u, err := url.Parse(address.Domain()) if err != nil { log.Fatalln(newError("DNS config error").Base(err)) } server.clients = append(server.clients, NewDoHLocalNameServer(u, server.clientIP)) } else if address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https://") { // DOH Remote mode u, err := url.Parse(address.Domain()) if err != nil { log.Fatalln(newError("DNS config error").Base(err)) } idx := len(server.clients) server.clients = append(server.clients, nil) // need the core dispatcher, register DOHClient at callback common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { c, err := NewDoHNameServer(u, d, server.clientIP) if err != nil { log.Fatalln(newError("DNS config error").Base(err)) } server.clients[idx] = c })) } else { // UDP classic DNS mode dest := endpoint.AsDestination() if dest.Network == net.Network_Unknown { dest.Network = net.Network_UDP } if dest.Network == net.Network_UDP { idx := len(server.clients) server.clients = append(server.clients, nil) common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { server.clients[idx] = NewClassicNameServer(dest, d, server.clientIP) })) } } server.ipIndexMap = append(server.ipIndexMap, nil) return len(server.clients) - 1 } if len(config.NameServers) > 0 { features.PrintDeprecatedFeatureWarning("simple DNS server") for _, destPB := range config.NameServers { addNameServer(&NameServer{Address: destPB}) } } if len(config.NameServer) > 0 { clientIndices := []int{} domainRuleCount := 0 for _, ns := range config.NameServer { idx := addNameServer(ns) clientIndices = append(clientIndices, idx) domainRuleCount += len(ns.PrioritizedDomain) } domainRules := make([][]string, len(server.clients)) domainMatcher := &strmatcher.MatcherGroup{} matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1) // matcher index starts from 1 var geoIPMatcherContainer router.GeoIPMatcherContainer for nidx, ns := range config.NameServer { idx := clientIndices[nidx] // Establish domain rule matcher rules := []string{} ruleCurr := 0 ruleIter := 0 for _, domain := range ns.PrioritizedDomain { matcher, err := toStrMatcher(domain.Type, domain.Domain) if err != nil { return nil, newError("failed to create prioritized domain").Base(err).AtWarning() } midx := domainMatcher.Add(matcher) if midx >= uint32(len(matcherInfos)) { // This rarely happens according to current matcher's implementation newError("expanding domain matcher info array to size ", midx, " when adding ", matcher).AtDebug().WriteToLog() matcherInfos = append(matcherInfos, make([]DomainMatcherInfo, midx-uint32(len(matcherInfos))+1)...) } info := &matcherInfos[midx] info.clientIdx = uint16(idx) if ruleCurr < len(ns.OriginalRules) { info.domainRuleIdx = uint16(ruleCurr) rule := ns.OriginalRules[ruleCurr] if ruleCurr >= len(rules) { rules = append(rules, rule.Rule) } ruleIter++ if ruleIter >= int(rule.Size) { ruleIter = 0 ruleCurr++ } } else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests) info.domainRuleIdx = uint16(len(rules)) rules = append(rules, matcher.String()) } } domainRules[idx] = rules // only add to ipIndexMap if GeoIP is configured if len(ns.Geoip) > 0 { var matchers []*router.GeoIPMatcher for _, geoip := range ns.Geoip { matcher, err := geoIPMatcherContainer.Add(geoip) if err != nil { return nil, newError("failed to create ip matcher").Base(err).AtWarning() } matchers = append(matchers, matcher) } matcher := &MultiGeoIPMatcher{matchers: matchers} server.ipIndexMap[idx] = matcher } } server.domainRules = domainRules server.domainMatcher = domainMatcher server.matcherInfos = matcherInfos } if len(server.clients) == 0 { server.clients = append(server.clients, NewLocalNameServer()) server.ipIndexMap = append(server.ipIndexMap, nil) } return server, nil } // Type implements common.HasType. func (*Server) Type() interface{} { return dns.ClientType() } // Start implements common.Runnable. func (s *Server) Start() error { return nil } // Close implements common.Closable. func (s *Server) Close() error { return nil } func (s *Server) IsOwnLink(ctx context.Context) bool { inbound := session.InboundFromContext(ctx) return inbound != nil && inbound.Tag == s.tag } // Match check dns ip match geoip func (s *Server) Match(idx int, client Client, domain string, ips []net.IP) ([]net.IP, error) { var matcher *MultiGeoIPMatcher if idx < len(s.ipIndexMap) { matcher = s.ipIndexMap[idx] } if matcher == nil { return ips, nil } if !matcher.HasMatcher() { newError("domain ", domain, " server has no valid matcher: ", client.Name(), " idx:", idx).AtDebug().WriteToLog() return ips, nil } newIps := []net.IP{} for _, ip := range ips { if matcher.Match(ip) { newIps = append(newIps, ip) } } if len(newIps) == 0 { return nil, errExpectedIPNonMatch } newError("domain ", domain, " expectIPs ", newIps, " matched at server ", client.Name(), " idx:", idx).AtDebug().WriteToLog() return newIps, nil } func (s *Server) queryIPTimeout(idx int, client Client, domain string, option IPOption) ([]net.IP, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*4) if len(s.tag) > 0 { ctx = session.ContextWithInbound(ctx, &session.Inbound{ Tag: s.tag, }) } ips, err := client.QueryIP(ctx, domain, option) cancel() if err != nil { return ips, err } ips, err = s.Match(idx, client, domain, ips) return ips, err } // LookupIP implements dns.Client. func (s *Server) LookupIP(domain string) ([]net.IP, error) { return s.lookupIPInternal(domain, IPOption{ IPv4Enable: true, IPv6Enable: true, }) } // LookupIPv4 implements dns.IPv4Lookup. func (s *Server) LookupIPv4(domain string) ([]net.IP, error) { return s.lookupIPInternal(domain, IPOption{ IPv4Enable: true, IPv6Enable: false, }) } // LookupIPv6 implements dns.IPv6Lookup. func (s *Server) LookupIPv6(domain string) ([]net.IP, error) { return s.lookupIPInternal(domain, IPOption{ IPv4Enable: false, IPv6Enable: true, }) } func (s *Server) lookupStatic(domain string, option IPOption, depth int32) []net.Address { ips := s.hosts.LookupIP(domain, option) if ips == nil { return nil } if ips[0].Family().IsDomain() && depth < 5 { if newIPs := s.lookupStatic(ips[0].Domain(), option, depth+1); newIPs != nil { return newIPs } } return ips } func toNetIP(ips []net.Address) []net.IP { if len(ips) == 0 { return nil } netips := make([]net.IP, 0, len(ips)) for _, ip := range ips { netips = append(netips, ip.IP()) } return netips } func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) { if domain == "" { return nil, newError("empty domain name") } // normalize the FQDN form query if domain[len(domain)-1] == '.' { domain = domain[:len(domain)-1] } ips := s.lookupStatic(domain, option, 0) if ips != nil && ips[0].Family().IsIP() { newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog() return toNetIP(ips), nil } if ips != nil && ips[0].Family().IsDomain() { newdomain := ips[0].Domain() newError("domain replaced: ", domain, " -> ", newdomain).WriteToLog() domain = newdomain } var lastErr error var matchedClient Client if s.domainMatcher != nil { indices := s.domainMatcher.Match(domain) domainRules := []string{} matchingDNS := []string{} for _, idx := range indices { info := s.matcherInfos[idx] rule := s.domainRules[info.clientIdx][info.domainRuleIdx] domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", rule, info.clientIdx)) matchingDNS = append(matchingDNS, s.clients[info.clientIdx].Name()) } if len(domainRules) > 0 { newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog() } if len(matchingDNS) > 0 { newError("domain ", domain, " uses following DNS first: ", matchingDNS).AtDebug().WriteToLog() } for _, idx := range indices { clientIdx := int(s.matcherInfos[idx].clientIdx) matchedClient = s.clients[clientIdx] ips, err := s.queryIPTimeout(clientIdx, matchedClient, domain, option) if len(ips) > 0 { return ips, nil } if err == dns.ErrEmptyResponse { return nil, err } if err != nil { newError("failed to lookup ip for domain ", domain, " at server ", matchedClient.Name()).Base(err).WriteToLog() lastErr = err } } } for idx, client := range s.clients { if client == matchedClient { newError("domain ", domain, " at server ", client.Name(), " idx:", idx, " already lookup failed, just ignore").AtDebug().WriteToLog() continue } ips, err := s.queryIPTimeout(idx, client, domain, option) if len(ips) > 0 { return ips, nil } if err != nil { newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog() lastErr = err } if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch { return nil, err } } return nil, newError("returning nil for domain ", domain).Base(lastErr) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) } ================================================ FILE: app/dns/server_test.go ================================================ package dns_test import ( "testing" "time" "github.com/google/go-cmp/cmp" "github.com/miekg/dns" "v2ray.com/core" "v2ray.com/core/app/dispatcher" . "v2ray.com/core/app/dns" "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" _ "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" feature_dns "v2ray.com/core/features/dns" "v2ray.com/core/proxy/freedom" "v2ray.com/core/testing/servers/udp" ) type staticHandler struct { } func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { ans := new(dns.Msg) ans.Id = r.Id var clientIP net.IP opt := r.IsEdns0() if opt != nil { for _, o := range opt.Option { if o.Option() == dns.EDNS0SUBNET { subnet := o.(*dns.EDNS0_SUBNET) clientIP = subnet.Address } } } for _, q := range r.Question { if q.Name == "google.com." && q.Qtype == dns.TypeA { if clientIP == nil { rr, _ := dns.NewRR("google.com. IN A 8.8.8.8") ans.Answer = append(ans.Answer, rr) } else { rr, _ := dns.NewRR("google.com. IN A 8.8.4.4") ans.Answer = append(ans.Answer, rr) } } else if q.Name == "api.google.com." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("api.google.com. IN A 8.8.7.7") ans.Answer = append(ans.Answer, rr) } else if q.Name == "v2.api.google.com." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("v2.api.google.com. IN A 8.8.7.8") ans.Answer = append(ans.Answer, rr) } else if q.Name == "facebook.com." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("facebook.com. IN A 9.9.9.9") ans.Answer = append(ans.Answer, rr) } else if q.Name == "ipv6.google.com." && q.Qtype == dns.TypeA { rr, err := dns.NewRR("ipv6.google.com. IN A 8.8.8.7") common.Must(err) ans.Answer = append(ans.Answer, rr) } else if q.Name == "ipv6.google.com." && q.Qtype == dns.TypeAAAA { rr, err := dns.NewRR("ipv6.google.com. IN AAAA 2001:4860:4860::8888") common.Must(err) ans.Answer = append(ans.Answer, rr) } else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA { ans.MsgHdr.Rcode = dns.RcodeNameError } else if q.Name == "hostname." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("hostname. IN A 127.0.0.1") ans.Answer = append(ans.Answer, rr) } else if q.Name == "hostname.local." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("hostname.local. IN A 127.0.0.1") ans.Answer = append(ans.Answer, rr) } else if q.Name == "hostname.localdomain." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("hostname.localdomain. IN A 127.0.0.1") ans.Answer = append(ans.Answer, rr) } else if q.Name == "localhost." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("localhost. IN A 127.0.0.2") ans.Answer = append(ans.Answer, rr) } else if q.Name == "localhost-a." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("localhost-a. IN A 127.0.0.3") ans.Answer = append(ans.Answer, rr) } else if q.Name == "localhost-b." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4") ans.Answer = append(ans.Answer, rr) } else if q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1") ans.Answer = append(ans.Answer, rr) } } w.WriteMsg(ans) } func TestUDPServerSubnet(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, ClientIp: []byte{7, 8, 9, 10}, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 4, 4}}); r != "" { t.Fatal(r) } } func TestUDPServer(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } { ips, err := client.LookupIP("facebook.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{9, 9, 9, 9}}); r != "" { t.Fatal(r) } } { _, err := client.LookupIP("notexist.google.com") if err == nil { t.Fatal("nil error") } if r := feature_dns.RCodeFromError(err); r != uint16(dns.RcodeNameError) { t.Fatal("expected NameError, but got ", r) } } { clientv6 := client.(feature_dns.IPv6Lookup) ips, err := clientv6.LookupIPv6("ipv4only.google.com") if err != feature_dns.ErrEmptyResponse { t.Fatal("error: ", err) } if len(ips) != 0 { t.Fatal("ips: ", ips) } } dnsServer.Shutdown() { ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } } func TestPrioritizedDomain(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 9999, /* unreachable */ }, }, NameServer: []*NameServer{ { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ { Type: DomainMatchingType_Full, Domain: "google.com", }, }, }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) startTime := time.Now() { ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } endTime := time.Now() if startTime.After(endTime.Add(time.Second * 2)) { t.Error("DNS query doesn't finish in 2 seconds.") } } func TestUDPServerIPv6(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) client6 := client.(feature_dns.IPv6Lookup) { ips, err := client6.LookupIPv6("ipv6.google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{32, 1, 72, 96, 72, 96, 0, 0, 0, 0, 0, 0, 0, 0, 136, 136}}); r != "" { t.Fatal(r) } } } func TestStaticHostDomain(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, StaticHosts: []*Config_HostMapping{ { Type: DomainMatchingType_Full, Domain: "example.com", ProxiedDomain: "google.com", }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) { ips, err := client.LookupIP("example.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } dnsServer.Shutdown() } func TestIPMatch(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServer: []*NameServer{ // private dns, not match { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, Geoip: []*router.GeoIP{ { CountryCode: "local", Cidr: []*router.CIDR{ { // inner ip, will not match Ip: []byte{192, 168, 11, 1}, Prefix: 32, }, }, }, }, }, // second dns, match ip { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, Geoip: []*router.GeoIP{ { CountryCode: "test", Cidr: []*router.CIDR{ { Ip: []byte{8, 8, 8, 8}, Prefix: 32, }, }, }, { CountryCode: "test", Cidr: []*router.CIDR{ { Ip: []byte{8, 8, 8, 4}, Prefix: 32, }, }, }, }, }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) startTime := time.Now() { ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } endTime := time.Now() if startTime.After(endTime.Add(time.Second * 2)) { t.Error("DNS query doesn't finish in 2 seconds.") } } func TestLocalDomain(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 9999, /* unreachable */ }, }, NameServer: []*NameServer{ { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ // Equivalent of dotless:localhost {Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"}, }, Geoip: []*router.GeoIP{ { // Will match localhost, localhost-a and localhost-b, CountryCode: "local", Cidr: []*router.CIDR{ {Ip: []byte{127, 0, 0, 2}, Prefix: 32}, {Ip: []byte{127, 0, 0, 3}, Prefix: 32}, {Ip: []byte{127, 0, 0, 4}, Prefix: 32}, }, }, }, }, { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ // Equivalent of dotless: and domain:local {Type: DomainMatchingType_Regex, Domain: "^[^.]*$"}, {Type: DomainMatchingType_Subdomain, Domain: "local"}, {Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, }, }, }, StaticHosts: []*Config_HostMapping{ { Type: DomainMatchingType_Full, Domain: "hostnamestatic", Ip: [][]byte{{127, 0, 0, 53}}, }, { Type: DomainMatchingType_Full, Domain: "hostnamealias", ProxiedDomain: "hostname.localdomain", }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) startTime := time.Now() { // Will match dotless: ips, err := client.LookupIP("hostname") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { t.Fatal(r) } } { // Will match domain:local ips, err := client.LookupIP("hostname.local") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { t.Fatal(r) } } { // Will match static ip ips, err := client.LookupIP("hostnamestatic") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 53}}); r != "" { t.Fatal(r) } } { // Will match domain replacing ips, err := client.LookupIP("hostnamealias") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { t.Fatal(r) } } { // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless: ips, err := client.LookupIP("localhost") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 2}}); r != "" { t.Fatal(r) } } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 ips, err := client.LookupIP("localhost-a") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 3}}); r != "" { t.Fatal(r) } } { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 ips, err := client.LookupIP("localhost-b") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 4}}); r != "" { t.Fatal(r) } } { // Will match dotless: ips, err := client.LookupIP("Mijia Cloud") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { t.Fatal(r) } } endTime := time.Now() if startTime.After(endTime.Add(time.Second * 2)) { t.Error("DNS query doesn't finish in 2 seconds.") } } func TestMultiMatchPrioritizedDomain(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } go dnsServer.ListenAndServe() time.Sleep(time.Second) config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 9999, /* unreachable */ }, }, NameServer: []*NameServer{ { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ { Type: DomainMatchingType_Subdomain, Domain: "google.com", }, }, Geoip: []*router.GeoIP{ { // Will only match 8.8.8.8 and 8.8.4.4 Cidr: []*router.CIDR{ {Ip: []byte{8, 8, 8, 8}, Prefix: 32}, {Ip: []byte{8, 8, 4, 4}, Prefix: 32}, }, }, }, }, { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ { Type: DomainMatchingType_Subdomain, Domain: "google.com", }, }, Geoip: []*router.GeoIP{ { // Will match 8.8.8.8 and 8.8.8.7, etc Cidr: []*router.CIDR{ {Ip: []byte{8, 8, 8, 7}, Prefix: 24}, }, }, }, }, { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ { Type: DomainMatchingType_Subdomain, Domain: "api.google.com", }, }, Geoip: []*router.GeoIP{ { // Will only match 8.8.7.7 (api.google.com) Cidr: []*router.CIDR{ {Ip: []byte{8, 8, 7, 7}, Prefix: 32}, }, }, }, }, { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, PrioritizedDomain: []*NameServer_PriorityDomain{ { Type: DomainMatchingType_Full, Domain: "v2.api.google.com", }, }, Geoip: []*router.GeoIP{ { // Will only match 8.8.7.8 (v2.api.google.com) Cidr: []*router.CIDR{ {Ip: []byte{8, 8, 7, 8}, Prefix: 32}, }, }, }, }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } v, err := core.New(config) common.Must(err) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) startTime := time.Now() { // Will match server 1,2 and server 1 returns expected ip ips, err := client.LookupIP("google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { t.Fatal(r) } } { // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one clientv4 := client.(feature_dns.IPv4Lookup) ips, err := clientv4.LookupIPv4("ipv6.google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 7}}); r != "" { t.Fatal(r) } } { // Will match server 3,1,2 and server 3 returns expected one ips, err := client.LookupIP("api.google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 7}}); r != "" { t.Fatal(r) } } { // Will match server 4,3,1,2 and server 4 returns expected one ips, err := client.LookupIP("v2.api.google.com") if err != nil { t.Fatal("unexpected error: ", err) } if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 8}}); r != "" { t.Fatal(r) } } endTime := time.Now() if startTime.After(endTime.Add(time.Second * 2)) { t.Error("DNS query doesn't finish in 2 seconds.") } } ================================================ FILE: app/dns/udpns.go ================================================ // +build !confonly package dns import ( "context" "strings" "sync" "sync/atomic" "time" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/dns" udp_proto "v2ray.com/core/common/protocol/udp" "v2ray.com/core/common/session" "v2ray.com/core/common/signal/pubsub" "v2ray.com/core/common/task" dns_feature "v2ray.com/core/features/dns" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet/udp" ) type ClassicNameServer struct { sync.RWMutex name string address net.Destination ips map[string]record requests map[uint16]dnsRequest pub *pubsub.Service udpServer *udp.Dispatcher cleanup *task.Periodic reqID uint32 clientIP net.IP } func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, clientIP net.IP) *ClassicNameServer { // default to 53 if unspecific if address.Port == 0 { address.Port = net.Port(53) } s := &ClassicNameServer{ address: address, ips: make(map[string]record), requests: make(map[uint16]dnsRequest), clientIP: clientIP, pub: pubsub.NewService(), name: strings.ToUpper(address.String()), } s.cleanup = &task.Periodic{ Interval: time.Minute, Execute: s.Cleanup, } s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse) newError("DNS: created udp client inited for ", address.NetAddr()).AtInfo().WriteToLog() return s } func (s *ClassicNameServer) Name() string { return s.name } func (s *ClassicNameServer) Cleanup() error { now := time.Now() s.Lock() defer s.Unlock() if len(s.ips) == 0 && len(s.requests) == 0 { return newError(s.name, " nothing to do. stopping...") } for domain, record := range s.ips { if record.A != nil && record.A.Expire.Before(now) { record.A = nil } if record.AAAA != nil && record.AAAA.Expire.Before(now) { record.AAAA = nil } if record.A == nil && record.AAAA == nil { delete(s.ips, domain) } else { s.ips[domain] = record } } if len(s.ips) == 0 { s.ips = make(map[string]record) } for id, req := range s.requests { if req.expire.Before(now) { delete(s.requests, id) } } if len(s.requests) == 0 { s.requests = make(map[uint16]dnsRequest) } return nil } func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) { ipRec, err := parseResponse(packet.Payload.Bytes()) if err != nil { newError(s.name, " fail to parse responded DNS udp").AtError().WriteToLog() return } s.Lock() id := ipRec.ReqID req, ok := s.requests[id] if ok { // remove the pending request delete(s.requests, id) } s.Unlock() if !ok { newError(s.name, " cannot find the pending request").AtError().WriteToLog() return } var rec record switch req.reqType { case dnsmessage.TypeA: rec.A = ipRec case dnsmessage.TypeAAAA: rec.AAAA = ipRec } elapsed := time.Since(req.start) newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) { s.updateIP(req.domain, rec) } } func (s *ClassicNameServer) updateIP(domain string, newRec record) { s.Lock() newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog() rec := s.ips[domain] updated := false if isNewer(rec.A, newRec.A) { rec.A = newRec.A updated = true } if isNewer(rec.AAAA, newRec.AAAA) { rec.AAAA = newRec.AAAA updated = true } if updated { s.ips[domain] = rec } if newRec.A != nil { s.pub.Publish(domain+"4", nil) } if newRec.AAAA != nil { s.pub.Publish(domain+"6", nil) } s.Unlock() common.Must(s.cleanup.Start()) } func (s *ClassicNameServer) newReqID() uint16 { return uint16(atomic.AddUint32(&s.reqID, 1)) } func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) { s.Lock() defer s.Unlock() id := req.msg.ID req.expire = time.Now().Add(time.Second * 8) s.requests[id] = *req } func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) for _, req := range reqs { s.addPendingRequest(req) b, _ := dns.PackMessage(req.msg) udpCtx := context.Background() if inbound := session.InboundFromContext(ctx); inbound != nil { udpCtx = session.ContextWithInbound(udpCtx, inbound) } udpCtx = session.ContextWithContent(udpCtx, &session.Content{ Protocol: "dns", }) s.udpServer.Dispatch(udpCtx, s.address, b) } } func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { s.RLock() record, found := s.ips[domain] s.RUnlock() if !found { return nil, errRecordNotFound } var ips []net.Address var lastErr error if option.IPv4Enable { a, err := record.A.getIPs() if err != nil { lastErr = err } ips = append(ips, a...) } if option.IPv6Enable { aaaa, err := record.AAAA.getIPs() if err != nil { lastErr = err } ips = append(ips, aaaa...) } if len(ips) > 0 { return toNetIP(ips), nil } if lastErr != nil { return nil, lastErr } return nil, dns_feature.ErrEmptyResponse } func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { fqdn := Fqdn(domain) ips, err := s.findIPsForDomain(fqdn, option) if err != errRecordNotFound { newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog() return ips, err } // ipv4 and ipv6 belong to different subscription groups var sub4, sub6 *pubsub.Subscriber if option.IPv4Enable { sub4 = s.pub.Subscribe(fqdn + "4") defer sub4.Close() } if option.IPv6Enable { sub6 = s.pub.Subscribe(fqdn + "6") defer sub6.Close() } done := make(chan interface{}) go func() { if sub4 != nil { select { case <-sub4.Wait(): case <-ctx.Done(): } } if sub6 != nil { select { case <-sub6.Wait(): case <-ctx.Done(): } } close(done) }() s.sendQuery(ctx, fqdn, option) for { ips, err := s.findIPsForDomain(fqdn, option) if err != errRecordNotFound { return ips, err } select { case <-ctx.Done(): return nil, ctx.Err() case <-done: } } } ================================================ FILE: app/log/command/command.go ================================================ // +build !confonly package command //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" grpc "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/common" ) type LoggerServer struct { V *core.Instance } // RestartLogger implements LoggerService. func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) { logger := s.V.GetFeature((*log.Instance)(nil)) if logger == nil { return nil, newError("unable to get logger instance") } if err := logger.Close(); err != nil { return nil, newError("failed to close logger").Base(err) } if err := logger.Start(); err != nil { return nil, newError("failed to start logger").Base(err) } return &RestartLoggerResponse{}, nil } func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {} type service struct { v *core.Instance } func (s *service) Register(server *grpc.Server) { RegisterLoggerServiceServer(server, &LoggerServer{ V: s.v, }) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { s := core.MustFromContext(ctx) return &service{v: s}, nil })) } ================================================ FILE: app/log/command/command_test.go ================================================ package command_test import ( "context" "testing" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/log" . "v2ray.com/core/app/log/command" "v2ray.com/core/app/proxyman" _ "v2ray.com/core/app/proxyman/inbound" _ "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/common" "v2ray.com/core/common/serial" ) func TestLoggerRestart(t *testing.T) { v, err := core.New(&core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{}), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, }) common.Must(err) common.Must(v.Start()) server := &LoggerServer{ V: v, } common.Must2(server.RestartLogger(context.Background(), &RestartLoggerRequest{})) } ================================================ FILE: app/log/command/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/log/command/config.proto package command import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_log_command_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_log_command_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_log_command_config_proto_rawDescGZIP(), []int{0} } type RestartLoggerRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RestartLoggerRequest) Reset() { *x = RestartLoggerRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_log_command_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RestartLoggerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*RestartLoggerRequest) ProtoMessage() {} func (x *RestartLoggerRequest) ProtoReflect() protoreflect.Message { mi := &file_app_log_command_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RestartLoggerRequest.ProtoReflect.Descriptor instead. func (*RestartLoggerRequest) Descriptor() ([]byte, []int) { return file_app_log_command_config_proto_rawDescGZIP(), []int{1} } type RestartLoggerResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RestartLoggerResponse) Reset() { *x = RestartLoggerResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_log_command_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RestartLoggerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*RestartLoggerResponse) ProtoMessage() {} func (x *RestartLoggerResponse) ProtoReflect() protoreflect.Message { mi := &file_app_log_command_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RestartLoggerResponse.ProtoReflect.Descriptor instead. func (*RestartLoggerResponse) Descriptor() ([]byte, []int) { return file_app_log_command_config_proto_rawDescGZIP(), []int{2} } var File_app_log_command_config_proto protoreflect.FileDescriptor var file_app_log_command_config_proto_rawDesc = []byte{ 0x0a, 0x1c, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x5f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_log_command_config_proto_rawDescOnce sync.Once file_app_log_command_config_proto_rawDescData = file_app_log_command_config_proto_rawDesc ) func file_app_log_command_config_proto_rawDescGZIP() []byte { file_app_log_command_config_proto_rawDescOnce.Do(func() { file_app_log_command_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_log_command_config_proto_rawDescData) }) return file_app_log_command_config_proto_rawDescData } var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_app_log_command_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.app.log.command.Config (*RestartLoggerRequest)(nil), // 1: v2ray.core.app.log.command.RestartLoggerRequest (*RestartLoggerResponse)(nil), // 2: v2ray.core.app.log.command.RestartLoggerResponse } var file_app_log_command_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.app.log.command.LoggerService.RestartLogger:input_type -> v2ray.core.app.log.command.RestartLoggerRequest 2, // 1: v2ray.core.app.log.command.LoggerService.RestartLogger:output_type -> v2ray.core.app.log.command.RestartLoggerResponse 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_app_log_command_config_proto_init() } func file_app_log_command_config_proto_init() { if File_app_log_command_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_log_command_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_log_command_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RestartLoggerRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_log_command_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RestartLoggerResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_log_command_config_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 1, }, GoTypes: file_app_log_command_config_proto_goTypes, DependencyIndexes: file_app_log_command_config_proto_depIdxs, MessageInfos: file_app_log_command_config_proto_msgTypes, }.Build() File_app_log_command_config_proto = out.File file_app_log_command_config_proto_rawDesc = nil file_app_log_command_config_proto_goTypes = nil file_app_log_command_config_proto_depIdxs = nil } ================================================ FILE: app/log/command/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.log.command; option csharp_namespace = "V2Ray.Core.App.Log.Command"; option go_package = "v2ray.com/core/app/log/command"; option java_package = "com.v2ray.core.app.log.command"; option java_multiple_files = true; message Config {} message RestartLoggerRequest {} message RestartLoggerResponse {} service LoggerService { rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {} } ================================================ FILE: app/log/command/config_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package command import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion7 // LoggerServiceClient is the client API for LoggerService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type LoggerServiceClient interface { RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) } type loggerServiceClient struct { cc grpc.ClientConnInterface } func NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient { return &loggerServiceClient{cc} } func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) { out := new(RestartLoggerResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.log.command.LoggerService/RestartLogger", in, out, opts...) if err != nil { return nil, err } return out, nil } // LoggerServiceServer is the server API for LoggerService service. // All implementations must embed UnimplementedLoggerServiceServer // for forward compatibility type LoggerServiceServer interface { RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) mustEmbedUnimplementedLoggerServiceServer() } // UnimplementedLoggerServiceServer must be embedded to have forward compatible implementations. type UnimplementedLoggerServiceServer struct { } func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented") } func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {} // UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to LoggerServiceServer will // result in compilation errors. type UnsafeLoggerServiceServer interface { mustEmbedUnimplementedLoggerServiceServer() } func RegisterLoggerServiceServer(s *grpc.Server, srv LoggerServiceServer) { s.RegisterService(&_LoggerService_serviceDesc, srv) } func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RestartLoggerRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LoggerServiceServer).RestartLogger(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.log.command.LoggerService/RestartLogger", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest)) } return interceptor(ctx, in, info, handler) } var _LoggerService_serviceDesc = grpc.ServiceDesc{ ServiceName: "v2ray.core.app.log.command.LoggerService", HandlerType: (*LoggerServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "RestartLogger", Handler: _LoggerService_RestartLogger_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "app/log/command/config.proto", } ================================================ FILE: app/log/command/errors.generated.go ================================================ package command import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/log/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/log/config.proto package log import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" log "v2ray.com/core/common/log" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type LogType int32 const ( LogType_None LogType = 0 LogType_Console LogType = 1 LogType_File LogType = 2 LogType_Event LogType = 3 ) // Enum value maps for LogType. var ( LogType_name = map[int32]string{ 0: "None", 1: "Console", 2: "File", 3: "Event", } LogType_value = map[string]int32{ "None": 0, "Console": 1, "File": 2, "Event": 3, } ) func (x LogType) Enum() *LogType { p := new(LogType) *p = x return p } func (x LogType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (LogType) Descriptor() protoreflect.EnumDescriptor { return file_app_log_config_proto_enumTypes[0].Descriptor() } func (LogType) Type() protoreflect.EnumType { return &file_app_log_config_proto_enumTypes[0] } func (x LogType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use LogType.Descriptor instead. func (LogType) EnumDescriptor() ([]byte, []int) { return file_app_log_config_proto_rawDescGZIP(), []int{0} } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ErrorLogType LogType `protobuf:"varint,1,opt,name=error_log_type,json=errorLogType,proto3,enum=v2ray.core.app.log.LogType" json:"error_log_type,omitempty"` ErrorLogLevel log.Severity `protobuf:"varint,2,opt,name=error_log_level,json=errorLogLevel,proto3,enum=v2ray.core.common.log.Severity" json:"error_log_level,omitempty"` ErrorLogPath string `protobuf:"bytes,3,opt,name=error_log_path,json=errorLogPath,proto3" json:"error_log_path,omitempty"` AccessLogType LogType `protobuf:"varint,4,opt,name=access_log_type,json=accessLogType,proto3,enum=v2ray.core.app.log.LogType" json:"access_log_type,omitempty"` AccessLogPath string `protobuf:"bytes,5,opt,name=access_log_path,json=accessLogPath,proto3" json:"access_log_path,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_log_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_log_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_log_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetErrorLogType() LogType { if x != nil { return x.ErrorLogType } return LogType_None } func (x *Config) GetErrorLogLevel() log.Severity { if x != nil { return x.ErrorLogLevel } return log.Severity_Unknown } func (x *Config) GetErrorLogPath() string { if x != nil { return x.ErrorLogPath } return "" } func (x *Config) GetAccessLogType() LogType { if x != nil { return x.AccessLogType } return LogType_None } func (x *Config) GetAccessLogPath() string { if x != nil { return x.AccessLogPath } return "" } var File_app_log_config_proto protoreflect.FileDescriptor var file_app_log_config_proto_rawDesc = []byte{ 0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x1a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x43, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x2a, 0x35, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x10, 0x03, 0x42, 0x47, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x50, 0x01, 0x5a, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0xaa, 0x02, 0x12, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_log_config_proto_rawDescOnce sync.Once file_app_log_config_proto_rawDescData = file_app_log_config_proto_rawDesc ) func file_app_log_config_proto_rawDescGZIP() []byte { file_app_log_config_proto_rawDescOnce.Do(func() { file_app_log_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_log_config_proto_rawDescData) }) return file_app_log_config_proto_rawDescData } var file_app_log_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_app_log_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_app_log_config_proto_goTypes = []interface{}{ (LogType)(0), // 0: v2ray.core.app.log.LogType (*Config)(nil), // 1: v2ray.core.app.log.Config (log.Severity)(0), // 2: v2ray.core.common.log.Severity } var file_app_log_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.app.log.Config.error_log_type:type_name -> v2ray.core.app.log.LogType 2, // 1: v2ray.core.app.log.Config.error_log_level:type_name -> v2ray.core.common.log.Severity 0, // 2: v2ray.core.app.log.Config.access_log_type:type_name -> v2ray.core.app.log.LogType 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_app_log_config_proto_init() } func file_app_log_config_proto_init() { if File_app_log_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_log_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_log_config_proto_rawDesc, NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_log_config_proto_goTypes, DependencyIndexes: file_app_log_config_proto_depIdxs, EnumInfos: file_app_log_config_proto_enumTypes, MessageInfos: file_app_log_config_proto_msgTypes, }.Build() File_app_log_config_proto = out.File file_app_log_config_proto_rawDesc = nil file_app_log_config_proto_goTypes = nil file_app_log_config_proto_depIdxs = nil } ================================================ FILE: app/log/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.log; option csharp_namespace = "V2Ray.Core.App.Log"; option go_package = "v2ray.com/core/app/log"; option java_package = "com.v2ray.core.app.log"; option java_multiple_files = true; import "common/log/log.proto"; enum LogType { None = 0; Console = 1; File = 2; Event = 3; } message Config { LogType error_log_type = 1; v2ray.core.common.log.Severity error_log_level = 2; string error_log_path = 3; LogType access_log_type = 4; string access_log_path = 5; } ================================================ FILE: app/log/errors.generated.go ================================================ package log import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/log/log.go ================================================ // +build !confonly package log //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "sync" "v2ray.com/core/common" "v2ray.com/core/common/log" ) // Instance is a log.Handler that handles logs. type Instance struct { sync.RWMutex config *Config accessLogger log.Handler errorLogger log.Handler active bool } // New creates a new log.Instance based on the given config. func New(ctx context.Context, config *Config) (*Instance, error) { g := &Instance{ config: config, active: false, } log.RegisterHandler(g) // start logger instantly on inited // other modules would log during init if err := g.startInternal(); err != nil { return nil, err } newError("Logger started").AtDebug().WriteToLog() return g, nil } func (g *Instance) initAccessLogger() error { handler, err := createHandler(g.config.AccessLogType, HandlerCreatorOptions{ Path: g.config.AccessLogPath, }) if err != nil { return err } g.accessLogger = handler return nil } func (g *Instance) initErrorLogger() error { handler, err := createHandler(g.config.ErrorLogType, HandlerCreatorOptions{ Path: g.config.ErrorLogPath, }) if err != nil { return err } g.errorLogger = handler return nil } // Type implements common.HasType. func (*Instance) Type() interface{} { return (*Instance)(nil) } func (g *Instance) startInternal() error { g.Lock() defer g.Unlock() if g.active { return nil } g.active = true if err := g.initAccessLogger(); err != nil { return newError("failed to initialize access logger").Base(err).AtWarning() } if err := g.initErrorLogger(); err != nil { return newError("failed to initialize error logger").Base(err).AtWarning() } return nil } // Start implements common.Runnable.Start(). func (g *Instance) Start() error { return g.startInternal() } // Handle implements log.Handler. func (g *Instance) Handle(msg log.Message) { g.RLock() defer g.RUnlock() if !g.active { return } switch msg := msg.(type) { case *log.AccessMessage: if g.accessLogger != nil { g.accessLogger.Handle(msg) } case *log.GeneralMessage: if g.errorLogger != nil && msg.Severity <= g.config.ErrorLogLevel { g.errorLogger.Handle(msg) } default: // Swallow } } // Close implements common.Closable.Close(). func (g *Instance) Close() error { newError("Logger closing").AtDebug().WriteToLog() g.Lock() defer g.Unlock() if !g.active { return nil } g.active = false common.Close(g.accessLogger) // nolint: errcheck g.accessLogger = nil common.Close(g.errorLogger) // nolint: errcheck g.errorLogger = nil return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) } ================================================ FILE: app/log/log_creator.go ================================================ // +build !confonly package log import ( "v2ray.com/core/common" "v2ray.com/core/common/log" ) type HandlerCreatorOptions struct { Path string } type HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error) var ( handlerCreatorMap = make(map[LogType]HandlerCreator) ) func RegisterHandlerCreator(logType LogType, f HandlerCreator) error { if f == nil { return newError("nil HandlerCreator") } handlerCreatorMap[logType] = f return nil } func createHandler(logType LogType, options HandlerCreatorOptions) (log.Handler, error) { creator, found := handlerCreatorMap[logType] if !found { return nil, newError("unable to create log handler for ", logType) } return creator(logType, options) } func init() { common.Must(RegisterHandlerCreator(LogType_Console, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { return log.NewLogger(log.CreateStdoutLogWriter()), nil })) common.Must(RegisterHandlerCreator(LogType_File, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { creator, err := log.CreateFileLogWriter(options.Path) if err != nil { return nil, err } return log.NewLogger(creator), nil })) common.Must(RegisterHandlerCreator(LogType_None, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { return nil, nil })) } ================================================ FILE: app/log/log_test.go ================================================ package log_test import ( "context" "testing" "github.com/golang/mock/gomock" "v2ray.com/core/app/log" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/testing/mocks" ) func TestCustomLogHandler(t *testing.T) { mockCtl := gomock.NewController(t) defer mockCtl.Finish() var loggedValue []string mockHandler := mocks.NewLogHandler(mockCtl) mockHandler.EXPECT().Handle(gomock.Any()).AnyTimes().DoAndReturn(func(msg clog.Message) { loggedValue = append(loggedValue, msg.String()) }) log.RegisterHandlerCreator(log.LogType_Console, func(lt log.LogType, options log.HandlerCreatorOptions) (clog.Handler, error) { return mockHandler, nil }) logger, err := log.New(context.Background(), &log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, AccessLogType: log.LogType_None, }) common.Must(err) common.Must(logger.Start()) clog.Record(&clog.GeneralMessage{ Severity: clog.Severity_Debug, Content: "test", }) if len(loggedValue) < 2 { t.Fatal("expected 2 log messages, but actually ", loggedValue) } if loggedValue[1] != "[Debug] test" { t.Fatal("expected '[Debug] test', but actually ", loggedValue[1]) } common.Must(logger.Close()) } ================================================ FILE: app/policy/config.go ================================================ package policy import ( "time" "v2ray.com/core/features/policy" ) // Duration converts Second to time.Duration. func (s *Second) Duration() time.Duration { if s == nil { return 0 } return time.Second * time.Duration(s.Value) } func defaultPolicy() *Policy { p := policy.SessionDefault() return &Policy{ Timeout: &Policy_Timeout{ Handshake: &Second{Value: uint32(p.Timeouts.Handshake / time.Second)}, ConnectionIdle: &Second{Value: uint32(p.Timeouts.ConnectionIdle / time.Second)}, UplinkOnly: &Second{Value: uint32(p.Timeouts.UplinkOnly / time.Second)}, DownlinkOnly: &Second{Value: uint32(p.Timeouts.DownlinkOnly / time.Second)}, }, Buffer: &Policy_Buffer{ Connection: p.Buffer.PerConnection, }, } } func (p *Policy_Timeout) overrideWith(another *Policy_Timeout) { if another.Handshake != nil { p.Handshake = &Second{Value: another.Handshake.Value} } if another.ConnectionIdle != nil { p.ConnectionIdle = &Second{Value: another.ConnectionIdle.Value} } if another.UplinkOnly != nil { p.UplinkOnly = &Second{Value: another.UplinkOnly.Value} } if another.DownlinkOnly != nil { p.DownlinkOnly = &Second{Value: another.DownlinkOnly.Value} } } func (p *Policy) overrideWith(another *Policy) { if another.Timeout != nil { p.Timeout.overrideWith(another.Timeout) } if another.Stats != nil && p.Stats == nil { p.Stats = &Policy_Stats{} p.Stats = another.Stats } if another.Buffer != nil { p.Buffer = &Policy_Buffer{ Connection: another.Buffer.Connection, } } } // ToCorePolicy converts this Policy to policy.Session. func (p *Policy) ToCorePolicy() policy.Session { cp := policy.SessionDefault() if p.Timeout != nil { cp.Timeouts.ConnectionIdle = p.Timeout.ConnectionIdle.Duration() cp.Timeouts.Handshake = p.Timeout.Handshake.Duration() cp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration() cp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration() } if p.Stats != nil { cp.Stats.UserUplink = p.Stats.UserUplink cp.Stats.UserDownlink = p.Stats.UserDownlink } if p.Buffer != nil { cp.Buffer.PerConnection = p.Buffer.Connection } return cp } // ToCorePolicy converts this SystemPolicy to policy.System. func (p *SystemPolicy) ToCorePolicy() policy.System { return policy.System{ Stats: policy.SystemStats{ InboundUplink: p.Stats.InboundUplink, InboundDownlink: p.Stats.InboundDownlink, OutboundUplink: p.Stats.OutboundUplink, OutboundDownlink: p.Stats.OutboundDownlink, }, } } ================================================ FILE: app/policy/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/policy/config.proto package policy import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Second struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *Second) Reset() { *x = Second{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Second) String() string { return protoimpl.X.MessageStringOf(x) } func (*Second) ProtoMessage() {} func (x *Second) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Second.ProtoReflect.Descriptor instead. func (*Second) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{0} } func (x *Second) GetValue() uint32 { if x != nil { return x.Value } return 0 } type Policy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Timeout *Policy_Timeout `protobuf:"bytes,1,opt,name=timeout,proto3" json:"timeout,omitempty"` Stats *Policy_Stats `protobuf:"bytes,2,opt,name=stats,proto3" json:"stats,omitempty"` Buffer *Policy_Buffer `protobuf:"bytes,3,opt,name=buffer,proto3" json:"buffer,omitempty"` } func (x *Policy) Reset() { *x = Policy{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Policy) String() string { return protoimpl.X.MessageStringOf(x) } func (*Policy) ProtoMessage() {} func (x *Policy) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Policy.ProtoReflect.Descriptor instead. func (*Policy) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{1} } func (x *Policy) GetTimeout() *Policy_Timeout { if x != nil { return x.Timeout } return nil } func (x *Policy) GetStats() *Policy_Stats { if x != nil { return x.Stats } return nil } func (x *Policy) GetBuffer() *Policy_Buffer { if x != nil { return x.Buffer } return nil } type SystemPolicy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Stats *SystemPolicy_Stats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` } func (x *SystemPolicy) Reset() { *x = SystemPolicy{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SystemPolicy) String() string { return protoimpl.X.MessageStringOf(x) } func (*SystemPolicy) ProtoMessage() {} func (x *SystemPolicy) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SystemPolicy.ProtoReflect.Descriptor instead. func (*SystemPolicy) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{2} } func (x *SystemPolicy) GetStats() *SystemPolicy_Stats { if x != nil { return x.Stats } return nil } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Level map[uint32]*Policy `protobuf:"bytes,1,rep,name=level,proto3" json:"level,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` System *SystemPolicy `protobuf:"bytes,2,opt,name=system,proto3" json:"system,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{3} } func (x *Config) GetLevel() map[uint32]*Policy { if x != nil { return x.Level } return nil } func (x *Config) GetSystem() *SystemPolicy { if x != nil { return x.System } return nil } // Timeout is a message for timeout settings in various stages, in seconds. type Policy_Timeout struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Handshake *Second `protobuf:"bytes,1,opt,name=handshake,proto3" json:"handshake,omitempty"` ConnectionIdle *Second `protobuf:"bytes,2,opt,name=connection_idle,json=connectionIdle,proto3" json:"connection_idle,omitempty"` UplinkOnly *Second `protobuf:"bytes,3,opt,name=uplink_only,json=uplinkOnly,proto3" json:"uplink_only,omitempty"` DownlinkOnly *Second `protobuf:"bytes,4,opt,name=downlink_only,json=downlinkOnly,proto3" json:"downlink_only,omitempty"` } func (x *Policy_Timeout) Reset() { *x = Policy_Timeout{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Policy_Timeout) String() string { return protoimpl.X.MessageStringOf(x) } func (*Policy_Timeout) ProtoMessage() {} func (x *Policy_Timeout) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Policy_Timeout.ProtoReflect.Descriptor instead. func (*Policy_Timeout) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{1, 0} } func (x *Policy_Timeout) GetHandshake() *Second { if x != nil { return x.Handshake } return nil } func (x *Policy_Timeout) GetConnectionIdle() *Second { if x != nil { return x.ConnectionIdle } return nil } func (x *Policy_Timeout) GetUplinkOnly() *Second { if x != nil { return x.UplinkOnly } return nil } func (x *Policy_Timeout) GetDownlinkOnly() *Second { if x != nil { return x.DownlinkOnly } return nil } type Policy_Stats struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields UserUplink bool `protobuf:"varint,1,opt,name=user_uplink,json=userUplink,proto3" json:"user_uplink,omitempty"` UserDownlink bool `protobuf:"varint,2,opt,name=user_downlink,json=userDownlink,proto3" json:"user_downlink,omitempty"` } func (x *Policy_Stats) Reset() { *x = Policy_Stats{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Policy_Stats) String() string { return protoimpl.X.MessageStringOf(x) } func (*Policy_Stats) ProtoMessage() {} func (x *Policy_Stats) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Policy_Stats.ProtoReflect.Descriptor instead. func (*Policy_Stats) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{1, 1} } func (x *Policy_Stats) GetUserUplink() bool { if x != nil { return x.UserUplink } return false } func (x *Policy_Stats) GetUserDownlink() bool { if x != nil { return x.UserDownlink } return false } type Policy_Buffer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Buffer size per connection, in bytes. -1 for unlimited buffer. Connection int32 `protobuf:"varint,1,opt,name=connection,proto3" json:"connection,omitempty"` } func (x *Policy_Buffer) Reset() { *x = Policy_Buffer{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Policy_Buffer) String() string { return protoimpl.X.MessageStringOf(x) } func (*Policy_Buffer) ProtoMessage() {} func (x *Policy_Buffer) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Policy_Buffer.ProtoReflect.Descriptor instead. func (*Policy_Buffer) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{1, 2} } func (x *Policy_Buffer) GetConnection() int32 { if x != nil { return x.Connection } return 0 } type SystemPolicy_Stats struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields InboundUplink bool `protobuf:"varint,1,opt,name=inbound_uplink,json=inboundUplink,proto3" json:"inbound_uplink,omitempty"` InboundDownlink bool `protobuf:"varint,2,opt,name=inbound_downlink,json=inboundDownlink,proto3" json:"inbound_downlink,omitempty"` OutboundUplink bool `protobuf:"varint,3,opt,name=outbound_uplink,json=outboundUplink,proto3" json:"outbound_uplink,omitempty"` OutboundDownlink bool `protobuf:"varint,4,opt,name=outbound_downlink,json=outboundDownlink,proto3" json:"outbound_downlink,omitempty"` } func (x *SystemPolicy_Stats) Reset() { *x = SystemPolicy_Stats{} if protoimpl.UnsafeEnabled { mi := &file_app_policy_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SystemPolicy_Stats) String() string { return protoimpl.X.MessageStringOf(x) } func (*SystemPolicy_Stats) ProtoMessage() {} func (x *SystemPolicy_Stats) ProtoReflect() protoreflect.Message { mi := &file_app_policy_config_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SystemPolicy_Stats.ProtoReflect.Descriptor instead. func (*SystemPolicy_Stats) Descriptor() ([]byte, []int) { return file_app_policy_config_proto_rawDescGZIP(), []int{2, 0} } func (x *SystemPolicy_Stats) GetInboundUplink() bool { if x != nil { return x.InboundUplink } return false } func (x *SystemPolicy_Stats) GetInboundDownlink() bool { if x != nil { return x.InboundDownlink } return false } func (x *SystemPolicy_Stats) GetOutboundUplink() bool { if x != nil { return x.OutboundUplink } return false } func (x *SystemPolicy_Stats) GetOutboundDownlink() bool { if x != nil { return x.OutboundDownlink } return false } var File_app_policy_config_proto protoreflect.FileDescriptor var file_app_policy_config_proto_rawDesc = []byte{ 0x0a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x1e, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xd0, 0x04, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3f, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0x92, 0x02, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x3b, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x46, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x0a, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x42, 0x0a, 0x0d, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x1a, 0x4d, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x1a, 0x28, 0x0a, 0x06, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x81, 0x02, 0x0a, 0x0c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0xaf, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xde, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x1a, 0x57, 0x0a, 0x0a, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_policy_config_proto_rawDescOnce sync.Once file_app_policy_config_proto_rawDescData = file_app_policy_config_proto_rawDesc ) func file_app_policy_config_proto_rawDescGZIP() []byte { file_app_policy_config_proto_rawDescOnce.Do(func() { file_app_policy_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_policy_config_proto_rawDescData) }) return file_app_policy_config_proto_rawDescData } var file_app_policy_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_app_policy_config_proto_goTypes = []interface{}{ (*Second)(nil), // 0: v2ray.core.app.policy.Second (*Policy)(nil), // 1: v2ray.core.app.policy.Policy (*SystemPolicy)(nil), // 2: v2ray.core.app.policy.SystemPolicy (*Config)(nil), // 3: v2ray.core.app.policy.Config (*Policy_Timeout)(nil), // 4: v2ray.core.app.policy.Policy.Timeout (*Policy_Stats)(nil), // 5: v2ray.core.app.policy.Policy.Stats (*Policy_Buffer)(nil), // 6: v2ray.core.app.policy.Policy.Buffer (*SystemPolicy_Stats)(nil), // 7: v2ray.core.app.policy.SystemPolicy.Stats nil, // 8: v2ray.core.app.policy.Config.LevelEntry } var file_app_policy_config_proto_depIdxs = []int32{ 4, // 0: v2ray.core.app.policy.Policy.timeout:type_name -> v2ray.core.app.policy.Policy.Timeout 5, // 1: v2ray.core.app.policy.Policy.stats:type_name -> v2ray.core.app.policy.Policy.Stats 6, // 2: v2ray.core.app.policy.Policy.buffer:type_name -> v2ray.core.app.policy.Policy.Buffer 7, // 3: v2ray.core.app.policy.SystemPolicy.stats:type_name -> v2ray.core.app.policy.SystemPolicy.Stats 8, // 4: v2ray.core.app.policy.Config.level:type_name -> v2ray.core.app.policy.Config.LevelEntry 2, // 5: v2ray.core.app.policy.Config.system:type_name -> v2ray.core.app.policy.SystemPolicy 0, // 6: v2ray.core.app.policy.Policy.Timeout.handshake:type_name -> v2ray.core.app.policy.Second 0, // 7: v2ray.core.app.policy.Policy.Timeout.connection_idle:type_name -> v2ray.core.app.policy.Second 0, // 8: v2ray.core.app.policy.Policy.Timeout.uplink_only:type_name -> v2ray.core.app.policy.Second 0, // 9: v2ray.core.app.policy.Policy.Timeout.downlink_only:type_name -> v2ray.core.app.policy.Second 1, // 10: v2ray.core.app.policy.Config.LevelEntry.value:type_name -> v2ray.core.app.policy.Policy 11, // [11:11] is the sub-list for method output_type 11, // [11:11] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name } func init() { file_app_policy_config_proto_init() } func file_app_policy_config_proto_init() { if File_app_policy_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_policy_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Second); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Policy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SystemPolicy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Policy_Timeout); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Policy_Stats); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Policy_Buffer); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_policy_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SystemPolicy_Stats); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_policy_config_proto_rawDesc, NumEnums: 0, NumMessages: 9, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_policy_config_proto_goTypes, DependencyIndexes: file_app_policy_config_proto_depIdxs, MessageInfos: file_app_policy_config_proto_msgTypes, }.Build() File_app_policy_config_proto = out.File file_app_policy_config_proto_rawDesc = nil file_app_policy_config_proto_goTypes = nil file_app_policy_config_proto_depIdxs = nil } ================================================ FILE: app/policy/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.policy; option csharp_namespace = "V2Ray.Core.App.Policy"; option go_package = "v2ray.com/core/app/policy"; option java_package = "com.v2ray.core.app.policy"; option java_multiple_files = true; message Second { uint32 value = 1; } message Policy { // Timeout is a message for timeout settings in various stages, in seconds. message Timeout { Second handshake = 1; Second connection_idle = 2; Second uplink_only = 3; Second downlink_only = 4; } message Stats { bool user_uplink = 1; bool user_downlink = 2; } message Buffer { // Buffer size per connection, in bytes. -1 for unlimited buffer. int32 connection = 1; } Timeout timeout = 1; Stats stats = 2; Buffer buffer = 3; } message SystemPolicy { message Stats { bool inbound_uplink = 1; bool inbound_downlink = 2; bool outbound_uplink = 3; bool outbound_downlink = 4; } Stats stats = 1; } message Config { map level = 1; SystemPolicy system = 2; } ================================================ FILE: app/policy/errors.generated.go ================================================ package policy import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/policy/manager.go ================================================ package policy import ( "context" "v2ray.com/core/common" "v2ray.com/core/features/policy" ) // Instance is an instance of Policy manager. type Instance struct { levels map[uint32]*Policy system *SystemPolicy } // New creates new Policy manager instance. func New(ctx context.Context, config *Config) (*Instance, error) { m := &Instance{ levels: make(map[uint32]*Policy), system: config.System, } if len(config.Level) > 0 { for lv, p := range config.Level { pp := defaultPolicy() pp.overrideWith(p) m.levels[lv] = pp } } return m, nil } // Type implements common.HasType. func (*Instance) Type() interface{} { return policy.ManagerType() } // ForLevel implements policy.Manager. func (m *Instance) ForLevel(level uint32) policy.Session { if p, ok := m.levels[level]; ok { return p.ToCorePolicy() } return policy.SessionDefault() } // ForSystem implements policy.Manager. func (m *Instance) ForSystem() policy.System { if m.system == nil { return policy.System{} } return m.system.ToCorePolicy() } // Start implements common.Runnable.Start(). func (m *Instance) Start() error { return nil } // Close implements common.Closable.Close(). func (m *Instance) Close() error { return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) } ================================================ FILE: app/policy/manager_test.go ================================================ package policy_test import ( "context" "testing" "time" . "v2ray.com/core/app/policy" "v2ray.com/core/common" "v2ray.com/core/features/policy" ) func TestPolicy(t *testing.T) { manager, err := New(context.Background(), &Config{ Level: map[uint32]*Policy{ 0: { Timeout: &Policy_Timeout{ Handshake: &Second{ Value: 2, }, }, }, }, }) common.Must(err) pDefault := policy.SessionDefault() { p := manager.ForLevel(0) if p.Timeouts.Handshake != 2*time.Second { t.Error("expect 2 sec timeout, but got ", p.Timeouts.Handshake) } if p.Timeouts.ConnectionIdle != pDefault.Timeouts.ConnectionIdle { t.Error("expect ", pDefault.Timeouts.ConnectionIdle, " sec timeout, but got ", p.Timeouts.ConnectionIdle) } } { p := manager.ForLevel(1) if p.Timeouts.Handshake != pDefault.Timeouts.Handshake { t.Error("expect ", pDefault.Timeouts.Handshake, " sec timeout, but got ", p.Timeouts.Handshake) } } } ================================================ FILE: app/policy/policy.go ================================================ // Package policy is an implementation of policy.Manager feature. package policy //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: app/proxyman/command/command.go ================================================ // +build !confonly package command import ( "context" grpc "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/features/inbound" "v2ray.com/core/features/outbound" "v2ray.com/core/proxy" ) // InboundOperation is the interface for operations that applies to inbound handlers. type InboundOperation interface { // ApplyInbound applies this operation to the given inbound handler. ApplyInbound(context.Context, inbound.Handler) error } // OutboundOperation is the interface for operations that applies to outbound handlers. type OutboundOperation interface { // ApplyOutbound applies this operation to the given outbound handler. ApplyOutbound(context.Context, outbound.Handler) error } func getInbound(handler inbound.Handler) (proxy.Inbound, error) { gi, ok := handler.(proxy.GetInbound) if !ok { return nil, newError("can't get inbound proxy from handler.") } return gi.GetInbound(), nil } // ApplyInbound implements InboundOperation. func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error { p, err := getInbound(handler) if err != nil { return err } um, ok := p.(proxy.UserManager) if !ok { return newError("proxy is not a UserManager") } mUser, err := op.User.ToMemoryUser() if err != nil { return newError("failed to parse user").Base(err) } return um.AddUser(ctx, mUser) } // ApplyInbound implements InboundOperation. func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error { p, err := getInbound(handler) if err != nil { return err } um, ok := p.(proxy.UserManager) if !ok { return newError("proxy is not a UserManager") } return um.RemoveUser(ctx, op.Email) } type handlerServer struct { s *core.Instance ihm inbound.Manager ohm outbound.Manager } func (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) { if err := core.AddInboundHandler(s.s, request.Inbound); err != nil { return nil, err } return &AddInboundResponse{}, nil } func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) { return &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag) } func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) { rawOperation, err := request.Operation.GetInstance() if err != nil { return nil, newError("unknown operation").Base(err) } operation, ok := rawOperation.(InboundOperation) if !ok { return nil, newError("not an inbound operation") } handler, err := s.ihm.GetHandler(ctx, request.Tag) if err != nil { return nil, newError("failed to get handler: ", request.Tag).Base(err) } return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler) } func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) { if err := core.AddOutboundHandler(s.s, request.Outbound); err != nil { return nil, err } return &AddOutboundResponse{}, nil } func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) { return &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag) } func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) { rawOperation, err := request.Operation.GetInstance() if err != nil { return nil, newError("unknown operation").Base(err) } operation, ok := rawOperation.(OutboundOperation) if !ok { return nil, newError("not an outbound operation") } handler := s.ohm.GetHandler(request.Tag) return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler) } func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {} type service struct { v *core.Instance } func (s *service) Register(server *grpc.Server) { hs := &handlerServer{ s: s.v, } common.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) { hs.ihm = im hs.ohm = om })) RegisterHandlerServiceServer(server, hs) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { s := core.MustFromContext(ctx) return &service{v: s}, nil })) } ================================================ FILE: app/proxyman/command/command.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/proxyman/command/command.proto package command import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" core "v2ray.com/core" protocol "v2ray.com/core/common/protocol" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type AddUserOperation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields User *protocol.User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` } func (x *AddUserOperation) Reset() { *x = AddUserOperation{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddUserOperation) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddUserOperation) ProtoMessage() {} func (x *AddUserOperation) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddUserOperation.ProtoReflect.Descriptor instead. func (*AddUserOperation) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{0} } func (x *AddUserOperation) GetUser() *protocol.User { if x != nil { return x.User } return nil } type RemoveUserOperation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` } func (x *RemoveUserOperation) Reset() { *x = RemoveUserOperation{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveUserOperation) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveUserOperation) ProtoMessage() {} func (x *RemoveUserOperation) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RemoveUserOperation.ProtoReflect.Descriptor instead. func (*RemoveUserOperation) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{1} } func (x *RemoveUserOperation) GetEmail() string { if x != nil { return x.Email } return "" } type AddInboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Inbound *core.InboundHandlerConfig `protobuf:"bytes,1,opt,name=inbound,proto3" json:"inbound,omitempty"` } func (x *AddInboundRequest) Reset() { *x = AddInboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddInboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddInboundRequest) ProtoMessage() {} func (x *AddInboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddInboundRequest.ProtoReflect.Descriptor instead. func (*AddInboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{2} } func (x *AddInboundRequest) GetInbound() *core.InboundHandlerConfig { if x != nil { return x.Inbound } return nil } type AddInboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AddInboundResponse) Reset() { *x = AddInboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddInboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddInboundResponse) ProtoMessage() {} func (x *AddInboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddInboundResponse.ProtoReflect.Descriptor instead. func (*AddInboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{3} } type RemoveInboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *RemoveInboundRequest) Reset() { *x = RemoveInboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveInboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveInboundRequest) ProtoMessage() {} func (x *RemoveInboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RemoveInboundRequest.ProtoReflect.Descriptor instead. func (*RemoveInboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{4} } func (x *RemoveInboundRequest) GetTag() string { if x != nil { return x.Tag } return "" } type RemoveInboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RemoveInboundResponse) Reset() { *x = RemoveInboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveInboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveInboundResponse) ProtoMessage() {} func (x *RemoveInboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RemoveInboundResponse.ProtoReflect.Descriptor instead. func (*RemoveInboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{5} } type AlterInboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Operation *serial.TypedMessage `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` } func (x *AlterInboundRequest) Reset() { *x = AlterInboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlterInboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlterInboundRequest) ProtoMessage() {} func (x *AlterInboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AlterInboundRequest.ProtoReflect.Descriptor instead. func (*AlterInboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{6} } func (x *AlterInboundRequest) GetTag() string { if x != nil { return x.Tag } return "" } func (x *AlterInboundRequest) GetOperation() *serial.TypedMessage { if x != nil { return x.Operation } return nil } type AlterInboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AlterInboundResponse) Reset() { *x = AlterInboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlterInboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlterInboundResponse) ProtoMessage() {} func (x *AlterInboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AlterInboundResponse.ProtoReflect.Descriptor instead. func (*AlterInboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{7} } type AddOutboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Outbound *core.OutboundHandlerConfig `protobuf:"bytes,1,opt,name=outbound,proto3" json:"outbound,omitempty"` } func (x *AddOutboundRequest) Reset() { *x = AddOutboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddOutboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddOutboundRequest) ProtoMessage() {} func (x *AddOutboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddOutboundRequest.ProtoReflect.Descriptor instead. func (*AddOutboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{8} } func (x *AddOutboundRequest) GetOutbound() *core.OutboundHandlerConfig { if x != nil { return x.Outbound } return nil } type AddOutboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AddOutboundResponse) Reset() { *x = AddOutboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AddOutboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AddOutboundResponse) ProtoMessage() {} func (x *AddOutboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AddOutboundResponse.ProtoReflect.Descriptor instead. func (*AddOutboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{9} } type RemoveOutboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *RemoveOutboundRequest) Reset() { *x = RemoveOutboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveOutboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveOutboundRequest) ProtoMessage() {} func (x *RemoveOutboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RemoveOutboundRequest.ProtoReflect.Descriptor instead. func (*RemoveOutboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{10} } func (x *RemoveOutboundRequest) GetTag() string { if x != nil { return x.Tag } return "" } type RemoveOutboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *RemoveOutboundResponse) Reset() { *x = RemoveOutboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RemoveOutboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*RemoveOutboundResponse) ProtoMessage() {} func (x *RemoveOutboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RemoveOutboundResponse.ProtoReflect.Descriptor instead. func (*RemoveOutboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{11} } type AlterOutboundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Operation *serial.TypedMessage `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` } func (x *AlterOutboundRequest) Reset() { *x = AlterOutboundRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlterOutboundRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlterOutboundRequest) ProtoMessage() {} func (x *AlterOutboundRequest) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AlterOutboundRequest.ProtoReflect.Descriptor instead. func (*AlterOutboundRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{12} } func (x *AlterOutboundRequest) GetTag() string { if x != nil { return x.Tag } return "" } func (x *AlterOutboundRequest) GetOperation() *serial.TypedMessage { if x != nil { return x.Operation } return nil } type AlterOutboundResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *AlterOutboundResponse) Reset() { *x = AlterOutboundResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AlterOutboundResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*AlterOutboundResponse) ProtoMessage() {} func (x *AlterOutboundResponse) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AlterOutboundResponse.ProtoReflect.Descriptor instead. func (*AlterOutboundResponse) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{13} } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_command_command_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_command_command_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{14} } var File_app_proxyman_command_command_proto protoreflect.FileDescriptor var file_app_proxyman_command_command_proto_rawDesc = []byte{ 0x0a, 0x22, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x48, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2b, 0x0a, 0x13, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x4f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x13, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x44, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6e, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x44, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x90, 0x06, 0x0a, 0x0e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x35, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x0c, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x34, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x36, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x80, 0x01, 0x0a, 0x0d, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x35, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x6e, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x23, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1f, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_proxyman_command_command_proto_rawDescOnce sync.Once file_app_proxyman_command_command_proto_rawDescData = file_app_proxyman_command_command_proto_rawDesc ) func file_app_proxyman_command_command_proto_rawDescGZIP() []byte { file_app_proxyman_command_command_proto_rawDescOnce.Do(func() { file_app_proxyman_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_proxyman_command_command_proto_rawDescData) }) return file_app_proxyman_command_command_proto_rawDescData } var file_app_proxyman_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_app_proxyman_command_command_proto_goTypes = []interface{}{ (*AddUserOperation)(nil), // 0: v2ray.core.app.proxyman.command.AddUserOperation (*RemoveUserOperation)(nil), // 1: v2ray.core.app.proxyman.command.RemoveUserOperation (*AddInboundRequest)(nil), // 2: v2ray.core.app.proxyman.command.AddInboundRequest (*AddInboundResponse)(nil), // 3: v2ray.core.app.proxyman.command.AddInboundResponse (*RemoveInboundRequest)(nil), // 4: v2ray.core.app.proxyman.command.RemoveInboundRequest (*RemoveInboundResponse)(nil), // 5: v2ray.core.app.proxyman.command.RemoveInboundResponse (*AlterInboundRequest)(nil), // 6: v2ray.core.app.proxyman.command.AlterInboundRequest (*AlterInboundResponse)(nil), // 7: v2ray.core.app.proxyman.command.AlterInboundResponse (*AddOutboundRequest)(nil), // 8: v2ray.core.app.proxyman.command.AddOutboundRequest (*AddOutboundResponse)(nil), // 9: v2ray.core.app.proxyman.command.AddOutboundResponse (*RemoveOutboundRequest)(nil), // 10: v2ray.core.app.proxyman.command.RemoveOutboundRequest (*RemoveOutboundResponse)(nil), // 11: v2ray.core.app.proxyman.command.RemoveOutboundResponse (*AlterOutboundRequest)(nil), // 12: v2ray.core.app.proxyman.command.AlterOutboundRequest (*AlterOutboundResponse)(nil), // 13: v2ray.core.app.proxyman.command.AlterOutboundResponse (*Config)(nil), // 14: v2ray.core.app.proxyman.command.Config (*protocol.User)(nil), // 15: v2ray.core.common.protocol.User (*core.InboundHandlerConfig)(nil), // 16: v2ray.core.InboundHandlerConfig (*serial.TypedMessage)(nil), // 17: v2ray.core.common.serial.TypedMessage (*core.OutboundHandlerConfig)(nil), // 18: v2ray.core.OutboundHandlerConfig } var file_app_proxyman_command_command_proto_depIdxs = []int32{ 15, // 0: v2ray.core.app.proxyman.command.AddUserOperation.user:type_name -> v2ray.core.common.protocol.User 16, // 1: v2ray.core.app.proxyman.command.AddInboundRequest.inbound:type_name -> v2ray.core.InboundHandlerConfig 17, // 2: v2ray.core.app.proxyman.command.AlterInboundRequest.operation:type_name -> v2ray.core.common.serial.TypedMessage 18, // 3: v2ray.core.app.proxyman.command.AddOutboundRequest.outbound:type_name -> v2ray.core.OutboundHandlerConfig 17, // 4: v2ray.core.app.proxyman.command.AlterOutboundRequest.operation:type_name -> v2ray.core.common.serial.TypedMessage 2, // 5: v2ray.core.app.proxyman.command.HandlerService.AddInbound:input_type -> v2ray.core.app.proxyman.command.AddInboundRequest 4, // 6: v2ray.core.app.proxyman.command.HandlerService.RemoveInbound:input_type -> v2ray.core.app.proxyman.command.RemoveInboundRequest 6, // 7: v2ray.core.app.proxyman.command.HandlerService.AlterInbound:input_type -> v2ray.core.app.proxyman.command.AlterInboundRequest 8, // 8: v2ray.core.app.proxyman.command.HandlerService.AddOutbound:input_type -> v2ray.core.app.proxyman.command.AddOutboundRequest 10, // 9: v2ray.core.app.proxyman.command.HandlerService.RemoveOutbound:input_type -> v2ray.core.app.proxyman.command.RemoveOutboundRequest 12, // 10: v2ray.core.app.proxyman.command.HandlerService.AlterOutbound:input_type -> v2ray.core.app.proxyman.command.AlterOutboundRequest 3, // 11: v2ray.core.app.proxyman.command.HandlerService.AddInbound:output_type -> v2ray.core.app.proxyman.command.AddInboundResponse 5, // 12: v2ray.core.app.proxyman.command.HandlerService.RemoveInbound:output_type -> v2ray.core.app.proxyman.command.RemoveInboundResponse 7, // 13: v2ray.core.app.proxyman.command.HandlerService.AlterInbound:output_type -> v2ray.core.app.proxyman.command.AlterInboundResponse 9, // 14: v2ray.core.app.proxyman.command.HandlerService.AddOutbound:output_type -> v2ray.core.app.proxyman.command.AddOutboundResponse 11, // 15: v2ray.core.app.proxyman.command.HandlerService.RemoveOutbound:output_type -> v2ray.core.app.proxyman.command.RemoveOutboundResponse 13, // 16: v2ray.core.app.proxyman.command.HandlerService.AlterOutbound:output_type -> v2ray.core.app.proxyman.command.AlterOutboundResponse 11, // [11:17] is the sub-list for method output_type 5, // [5:11] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name } func init() { file_app_proxyman_command_command_proto_init() } func file_app_proxyman_command_command_proto_init() { if File_app_proxyman_command_command_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_proxyman_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddUserOperation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveUserOperation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddInboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddInboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveInboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveInboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlterInboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlterInboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddOutboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddOutboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveOutboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveOutboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlterOutboundRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AlterOutboundResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_command_command_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_proxyman_command_command_proto_rawDesc, NumEnums: 0, NumMessages: 15, NumExtensions: 0, NumServices: 1, }, GoTypes: file_app_proxyman_command_command_proto_goTypes, DependencyIndexes: file_app_proxyman_command_command_proto_depIdxs, MessageInfos: file_app_proxyman_command_command_proto_msgTypes, }.Build() File_app_proxyman_command_command_proto = out.File file_app_proxyman_command_command_proto_rawDesc = nil file_app_proxyman_command_command_proto_goTypes = nil file_app_proxyman_command_command_proto_depIdxs = nil } ================================================ FILE: app/proxyman/command/command.proto ================================================ syntax = "proto3"; package v2ray.core.app.proxyman.command; option csharp_namespace = "V2Ray.Core.App.Proxyman.Command"; option go_package = "v2ray.com/core/app/proxyman/command"; option java_package = "com.v2ray.core.app.proxyman.command"; option java_multiple_files = true; import "common/protocol/user.proto"; import "common/serial/typed_message.proto"; import "config.proto"; message AddUserOperation { v2ray.core.common.protocol.User user = 1; } message RemoveUserOperation { string email = 1; } message AddInboundRequest { core.InboundHandlerConfig inbound = 1; } message AddInboundResponse {} message RemoveInboundRequest { string tag = 1; } message RemoveInboundResponse {} message AlterInboundRequest { string tag = 1; v2ray.core.common.serial.TypedMessage operation = 2; } message AlterInboundResponse {} message AddOutboundRequest { core.OutboundHandlerConfig outbound = 1; } message AddOutboundResponse {} message RemoveOutboundRequest { string tag = 1; } message RemoveOutboundResponse {} message AlterOutboundRequest { string tag = 1; v2ray.core.common.serial.TypedMessage operation = 2; } message AlterOutboundResponse {} service HandlerService { rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {} rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {} rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {} rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {} rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {} rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {} } message Config {} ================================================ FILE: app/proxyman/command/command_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package command import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion7 // HandlerServiceClient is the client API for HandlerService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type HandlerServiceClient interface { AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) } type handlerServiceClient struct { cc grpc.ClientConnInterface } func NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient { return &handlerServiceClient{cc} } func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) { out := new(AddInboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AddInbound", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) { out := new(RemoveInboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/RemoveInbound", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) { out := new(AlterInboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AlterInbound", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) { out := new(AddOutboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AddOutbound", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) { out := new(RemoveOutboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/RemoveOutbound", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) { out := new(AlterOutboundResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AlterOutbound", in, out, opts...) if err != nil { return nil, err } return out, nil } // HandlerServiceServer is the server API for HandlerService service. // All implementations must embed UnimplementedHandlerServiceServer // for forward compatibility type HandlerServiceServer interface { AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) mustEmbedUnimplementedHandlerServiceServer() } // UnimplementedHandlerServiceServer must be embedded to have forward compatible implementations. type UnimplementedHandlerServiceServer struct { } func (UnimplementedHandlerServiceServer) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddInbound not implemented") } func (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveInbound not implemented") } func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented") } func (UnimplementedHandlerServiceServer) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddOutbound not implemented") } func (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveOutbound not implemented") } func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented") } func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {} // UnsafeHandlerServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to HandlerServiceServer will // result in compilation errors. type UnsafeHandlerServiceServer interface { mustEmbedUnimplementedHandlerServiceServer() } func RegisterHandlerServiceServer(s *grpc.Server, srv HandlerServiceServer) { s.RegisterService(&_HandlerService_serviceDesc, srv) } func _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddInboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).AddInbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AddInbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest)) } return interceptor(ctx, in, info, handler) } func _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RemoveInboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).RemoveInbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/RemoveInbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest)) } return interceptor(ctx, in, info, handler) } func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AlterInboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).AlterInbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AlterInbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest)) } return interceptor(ctx, in, info, handler) } func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddOutboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).AddOutbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AddOutbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest)) } return interceptor(ctx, in, info, handler) } func _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RemoveOutboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).RemoveOutbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/RemoveOutbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest)) } return interceptor(ctx, in, info, handler) } func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AlterOutboundRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HandlerServiceServer).AlterOutbound(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AlterOutbound", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest)) } return interceptor(ctx, in, info, handler) } var _HandlerService_serviceDesc = grpc.ServiceDesc{ ServiceName: "v2ray.core.app.proxyman.command.HandlerService", HandlerType: (*HandlerServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "AddInbound", Handler: _HandlerService_AddInbound_Handler, }, { MethodName: "RemoveInbound", Handler: _HandlerService_RemoveInbound_Handler, }, { MethodName: "AlterInbound", Handler: _HandlerService_AlterInbound_Handler, }, { MethodName: "AddOutbound", Handler: _HandlerService_AddOutbound_Handler, }, { MethodName: "RemoveOutbound", Handler: _HandlerService_RemoveOutbound_Handler, }, { MethodName: "AlterOutbound", Handler: _HandlerService_AlterOutbound_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "app/proxyman/command/command.proto", } ================================================ FILE: app/proxyman/command/doc.go ================================================ package command //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: app/proxyman/command/errors.generated.go ================================================ package command import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/proxyman/config.go ================================================ package proxyman func (s *AllocationStrategy) GetConcurrencyValue() uint32 { if s == nil || s.Concurrency == nil { return 3 } return s.Concurrency.Value } func (s *AllocationStrategy) GetRefreshValue() uint32 { if s == nil || s.Refresh == nil { return 5 } return s.Refresh.Value } func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig { if c.SniffingSettings != nil { return c.SniffingSettings } if len(c.DomainOverride) > 0 { var p []string for _, kd := range c.DomainOverride { switch kd { case KnownProtocols_HTTP: p = append(p, "http") case KnownProtocols_TLS: p = append(p, "tls") } } return &SniffingConfig{ Enabled: true, DestinationOverride: p, } } return nil } ================================================ FILE: app/proxyman/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/proxyman/config.proto package proxyman import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" serial "v2ray.com/core/common/serial" internet "v2ray.com/core/transport/internet" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type KnownProtocols int32 const ( KnownProtocols_HTTP KnownProtocols = 0 KnownProtocols_TLS KnownProtocols = 1 ) // Enum value maps for KnownProtocols. var ( KnownProtocols_name = map[int32]string{ 0: "HTTP", 1: "TLS", } KnownProtocols_value = map[string]int32{ "HTTP": 0, "TLS": 1, } ) func (x KnownProtocols) Enum() *KnownProtocols { p := new(KnownProtocols) *p = x return p } func (x KnownProtocols) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (KnownProtocols) Descriptor() protoreflect.EnumDescriptor { return file_app_proxyman_config_proto_enumTypes[0].Descriptor() } func (KnownProtocols) Type() protoreflect.EnumType { return &file_app_proxyman_config_proto_enumTypes[0] } func (x KnownProtocols) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use KnownProtocols.Descriptor instead. func (KnownProtocols) EnumDescriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{0} } type AllocationStrategy_Type int32 const ( // Always allocate all connection handlers. AllocationStrategy_Always AllocationStrategy_Type = 0 // Randomly allocate specific range of handlers. AllocationStrategy_Random AllocationStrategy_Type = 1 // External. Not supported yet. AllocationStrategy_External AllocationStrategy_Type = 2 ) // Enum value maps for AllocationStrategy_Type. var ( AllocationStrategy_Type_name = map[int32]string{ 0: "Always", 1: "Random", 2: "External", } AllocationStrategy_Type_value = map[string]int32{ "Always": 0, "Random": 1, "External": 2, } ) func (x AllocationStrategy_Type) Enum() *AllocationStrategy_Type { p := new(AllocationStrategy_Type) *p = x return p } func (x AllocationStrategy_Type) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AllocationStrategy_Type) Descriptor() protoreflect.EnumDescriptor { return file_app_proxyman_config_proto_enumTypes[1].Descriptor() } func (AllocationStrategy_Type) Type() protoreflect.EnumType { return &file_app_proxyman_config_proto_enumTypes[1] } func (x AllocationStrategy_Type) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AllocationStrategy_Type.Descriptor instead. func (AllocationStrategy_Type) EnumDescriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 0} } type InboundConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *InboundConfig) Reset() { *x = InboundConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InboundConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*InboundConfig) ProtoMessage() {} func (x *InboundConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InboundConfig.ProtoReflect.Descriptor instead. func (*InboundConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{0} } type AllocationStrategy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Type AllocationStrategy_Type `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.proxyman.AllocationStrategy_Type" json:"type,omitempty"` // Number of handlers (ports) running in parallel. // Default value is 3 if unset. Concurrency *AllocationStrategy_AllocationStrategyConcurrency `protobuf:"bytes,2,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // Number of minutes before a handler is regenerated. // Default value is 5 if unset. Refresh *AllocationStrategy_AllocationStrategyRefresh `protobuf:"bytes,3,opt,name=refresh,proto3" json:"refresh,omitempty"` } func (x *AllocationStrategy) Reset() { *x = AllocationStrategy{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AllocationStrategy) String() string { return protoimpl.X.MessageStringOf(x) } func (*AllocationStrategy) ProtoMessage() {} func (x *AllocationStrategy) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AllocationStrategy.ProtoReflect.Descriptor instead. func (*AllocationStrategy) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{1} } func (x *AllocationStrategy) GetType() AllocationStrategy_Type { if x != nil { return x.Type } return AllocationStrategy_Always } func (x *AllocationStrategy) GetConcurrency() *AllocationStrategy_AllocationStrategyConcurrency { if x != nil { return x.Concurrency } return nil } func (x *AllocationStrategy) GetRefresh() *AllocationStrategy_AllocationStrategyRefresh { if x != nil { return x.Refresh } return nil } type SniffingConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Whether or not to enable content sniffing on an inbound connection. Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` // Override target destination if sniff'ed protocol is in the given list. // Supported values are "http", "tls". DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` } func (x *SniffingConfig) Reset() { *x = SniffingConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SniffingConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SniffingConfig) ProtoMessage() {} func (x *SniffingConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SniffingConfig.ProtoReflect.Descriptor instead. func (*SniffingConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{2} } func (x *SniffingConfig) GetEnabled() bool { if x != nil { return x.Enabled } return false } func (x *SniffingConfig) GetDestinationOverride() []string { if x != nil { return x.DestinationOverride } return nil } type ReceiverConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // PortRange specifies the ports which the Receiver should listen on. PortRange *net.PortRange `protobuf:"bytes,1,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"` // Listen specifies the IP address that the Receiver should listen on. Listen *net.IPOrDomain `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"` AllocationStrategy *AllocationStrategy `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy,proto3" json:"allocation_strategy,omitempty"` StreamSettings *internet.StreamConfig `protobuf:"bytes,4,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"` ReceiveOriginalDestination bool `protobuf:"varint,5,opt,name=receive_original_destination,json=receiveOriginalDestination,proto3" json:"receive_original_destination,omitempty"` // Override domains for the given protocol. // Deprecated. Use sniffing_settings. // // Deprecated: Do not use. DomainOverride []KnownProtocols `protobuf:"varint,7,rep,packed,name=domain_override,json=domainOverride,proto3,enum=v2ray.core.app.proxyman.KnownProtocols" json:"domain_override,omitempty"` SniffingSettings *SniffingConfig `protobuf:"bytes,8,opt,name=sniffing_settings,json=sniffingSettings,proto3" json:"sniffing_settings,omitempty"` } func (x *ReceiverConfig) Reset() { *x = ReceiverConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ReceiverConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReceiverConfig) ProtoMessage() {} func (x *ReceiverConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReceiverConfig.ProtoReflect.Descriptor instead. func (*ReceiverConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{3} } func (x *ReceiverConfig) GetPortRange() *net.PortRange { if x != nil { return x.PortRange } return nil } func (x *ReceiverConfig) GetListen() *net.IPOrDomain { if x != nil { return x.Listen } return nil } func (x *ReceiverConfig) GetAllocationStrategy() *AllocationStrategy { if x != nil { return x.AllocationStrategy } return nil } func (x *ReceiverConfig) GetStreamSettings() *internet.StreamConfig { if x != nil { return x.StreamSettings } return nil } func (x *ReceiverConfig) GetReceiveOriginalDestination() bool { if x != nil { return x.ReceiveOriginalDestination } return false } // Deprecated: Do not use. func (x *ReceiverConfig) GetDomainOverride() []KnownProtocols { if x != nil { return x.DomainOverride } return nil } func (x *ReceiverConfig) GetSniffingSettings() *SniffingConfig { if x != nil { return x.SniffingSettings } return nil } type InboundHandlerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` ReceiverSettings *serial.TypedMessage `protobuf:"bytes,2,opt,name=receiver_settings,json=receiverSettings,proto3" json:"receiver_settings,omitempty"` ProxySettings *serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"` } func (x *InboundHandlerConfig) Reset() { *x = InboundHandlerConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InboundHandlerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*InboundHandlerConfig) ProtoMessage() {} func (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InboundHandlerConfig.ProtoReflect.Descriptor instead. func (*InboundHandlerConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{4} } func (x *InboundHandlerConfig) GetTag() string { if x != nil { return x.Tag } return "" } func (x *InboundHandlerConfig) GetReceiverSettings() *serial.TypedMessage { if x != nil { return x.ReceiverSettings } return nil } func (x *InboundHandlerConfig) GetProxySettings() *serial.TypedMessage { if x != nil { return x.ProxySettings } return nil } type OutboundConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *OutboundConfig) Reset() { *x = OutboundConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OutboundConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*OutboundConfig) ProtoMessage() {} func (x *OutboundConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OutboundConfig.ProtoReflect.Descriptor instead. func (*OutboundConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{5} } type SenderConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Send traffic through the given IP. Only IP is allowed. Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"` StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"` ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"` MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"` } func (x *SenderConfig) Reset() { *x = SenderConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SenderConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SenderConfig) ProtoMessage() {} func (x *SenderConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SenderConfig.ProtoReflect.Descriptor instead. func (*SenderConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{6} } func (x *SenderConfig) GetVia() *net.IPOrDomain { if x != nil { return x.Via } return nil } func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig { if x != nil { return x.StreamSettings } return nil } func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig { if x != nil { return x.ProxySettings } return nil } func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig { if x != nil { return x.MultiplexSettings } return nil } type MultiplexingConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Whether or not Mux is enabled. Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` // Max number of concurrent connections that one Mux connection can handle. Concurrency uint32 `protobuf:"varint,2,opt,name=concurrency,proto3" json:"concurrency,omitempty"` } func (x *MultiplexingConfig) Reset() { *x = MultiplexingConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MultiplexingConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*MultiplexingConfig) ProtoMessage() {} func (x *MultiplexingConfig) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MultiplexingConfig.ProtoReflect.Descriptor instead. func (*MultiplexingConfig) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{7} } func (x *MultiplexingConfig) GetEnabled() bool { if x != nil { return x.Enabled } return false } func (x *MultiplexingConfig) GetConcurrency() uint32 { if x != nil { return x.Concurrency } return 0 } type AllocationStrategy_AllocationStrategyConcurrency struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *AllocationStrategy_AllocationStrategyConcurrency) Reset() { *x = AllocationStrategy_AllocationStrategyConcurrency{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AllocationStrategy_AllocationStrategyConcurrency) String() string { return protoimpl.X.MessageStringOf(x) } func (*AllocationStrategy_AllocationStrategyConcurrency) ProtoMessage() {} func (x *AllocationStrategy_AllocationStrategyConcurrency) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AllocationStrategy_AllocationStrategyConcurrency.ProtoReflect.Descriptor instead. func (*AllocationStrategy_AllocationStrategyConcurrency) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 0} } func (x *AllocationStrategy_AllocationStrategyConcurrency) GetValue() uint32 { if x != nil { return x.Value } return 0 } type AllocationStrategy_AllocationStrategyRefresh struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *AllocationStrategy_AllocationStrategyRefresh) Reset() { *x = AllocationStrategy_AllocationStrategyRefresh{} if protoimpl.UnsafeEnabled { mi := &file_app_proxyman_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AllocationStrategy_AllocationStrategyRefresh) String() string { return protoimpl.X.MessageStringOf(x) } func (*AllocationStrategy_AllocationStrategyRefresh) ProtoMessage() {} func (x *AllocationStrategy_AllocationStrategyRefresh) ProtoReflect() protoreflect.Message { mi := &file_app_proxyman_config_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AllocationStrategy_AllocationStrategyRefresh.ProtoReflect.Descriptor instead. func (*AllocationStrategy_AllocationStrategyRefresh) Descriptor() ([]byte, []int) { return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 1} } func (x *AllocationStrategy_AllocationStrategyRefresh) GetValue() uint32 { if x != nil { return x.Value } return 0 } var File_app_proxyman_config_proto protoreflect.FileDescriptor var file_app_proxyman_config_proto_rawDesc = []byte{ 0x0a, 0x19, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc0, 0x03, 0x0a, 0x12, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x44, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x6b, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x5f, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a, 0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0x5d, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x22, 0xb4, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x54, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xcc, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x53, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc8, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x5a, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_proxyman_config_proto_rawDescOnce sync.Once file_app_proxyman_config_proto_rawDescData = file_app_proxyman_config_proto_rawDesc ) func file_app_proxyman_config_proto_rawDescGZIP() []byte { file_app_proxyman_config_proto_rawDescOnce.Do(func() { file_app_proxyman_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_proxyman_config_proto_rawDescData) }) return file_app_proxyman_config_proto_rawDescData } var file_app_proxyman_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_app_proxyman_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_app_proxyman_config_proto_goTypes = []interface{}{ (KnownProtocols)(0), // 0: v2ray.core.app.proxyman.KnownProtocols (AllocationStrategy_Type)(0), // 1: v2ray.core.app.proxyman.AllocationStrategy.Type (*InboundConfig)(nil), // 2: v2ray.core.app.proxyman.InboundConfig (*AllocationStrategy)(nil), // 3: v2ray.core.app.proxyman.AllocationStrategy (*SniffingConfig)(nil), // 4: v2ray.core.app.proxyman.SniffingConfig (*ReceiverConfig)(nil), // 5: v2ray.core.app.proxyman.ReceiverConfig (*InboundHandlerConfig)(nil), // 6: v2ray.core.app.proxyman.InboundHandlerConfig (*OutboundConfig)(nil), // 7: v2ray.core.app.proxyman.OutboundConfig (*SenderConfig)(nil), // 8: v2ray.core.app.proxyman.SenderConfig (*MultiplexingConfig)(nil), // 9: v2ray.core.app.proxyman.MultiplexingConfig (*AllocationStrategy_AllocationStrategyConcurrency)(nil), // 10: v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency (*AllocationStrategy_AllocationStrategyRefresh)(nil), // 11: v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh (*net.PortRange)(nil), // 12: v2ray.core.common.net.PortRange (*net.IPOrDomain)(nil), // 13: v2ray.core.common.net.IPOrDomain (*internet.StreamConfig)(nil), // 14: v2ray.core.transport.internet.StreamConfig (*serial.TypedMessage)(nil), // 15: v2ray.core.common.serial.TypedMessage (*internet.ProxyConfig)(nil), // 16: v2ray.core.transport.internet.ProxyConfig } var file_app_proxyman_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.app.proxyman.AllocationStrategy.type:type_name -> v2ray.core.app.proxyman.AllocationStrategy.Type 10, // 1: v2ray.core.app.proxyman.AllocationStrategy.concurrency:type_name -> v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency 11, // 2: v2ray.core.app.proxyman.AllocationStrategy.refresh:type_name -> v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh 12, // 3: v2ray.core.app.proxyman.ReceiverConfig.port_range:type_name -> v2ray.core.common.net.PortRange 13, // 4: v2ray.core.app.proxyman.ReceiverConfig.listen:type_name -> v2ray.core.common.net.IPOrDomain 3, // 5: v2ray.core.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> v2ray.core.app.proxyman.AllocationStrategy 14, // 6: v2ray.core.app.proxyman.ReceiverConfig.stream_settings:type_name -> v2ray.core.transport.internet.StreamConfig 0, // 7: v2ray.core.app.proxyman.ReceiverConfig.domain_override:type_name -> v2ray.core.app.proxyman.KnownProtocols 4, // 8: v2ray.core.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> v2ray.core.app.proxyman.SniffingConfig 15, // 9: v2ray.core.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> v2ray.core.common.serial.TypedMessage 15, // 10: v2ray.core.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> v2ray.core.common.serial.TypedMessage 13, // 11: v2ray.core.app.proxyman.SenderConfig.via:type_name -> v2ray.core.common.net.IPOrDomain 14, // 12: v2ray.core.app.proxyman.SenderConfig.stream_settings:type_name -> v2ray.core.transport.internet.StreamConfig 16, // 13: v2ray.core.app.proxyman.SenderConfig.proxy_settings:type_name -> v2ray.core.transport.internet.ProxyConfig 9, // 14: v2ray.core.app.proxyman.SenderConfig.multiplex_settings:type_name -> v2ray.core.app.proxyman.MultiplexingConfig 15, // [15:15] is the sub-list for method output_type 15, // [15:15] is the sub-list for method input_type 15, // [15:15] is the sub-list for extension type_name 15, // [15:15] is the sub-list for extension extendee 0, // [0:15] is the sub-list for field type_name } func init() { file_app_proxyman_config_proto_init() } func file_app_proxyman_config_proto_init() { if File_app_proxyman_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_proxyman_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InboundConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AllocationStrategy); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SniffingConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ReceiverConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InboundHandlerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OutboundConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SenderConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MultiplexingConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AllocationStrategy_AllocationStrategyConcurrency); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_proxyman_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AllocationStrategy_AllocationStrategyRefresh); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_proxyman_config_proto_rawDesc, NumEnums: 2, NumMessages: 10, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_proxyman_config_proto_goTypes, DependencyIndexes: file_app_proxyman_config_proto_depIdxs, EnumInfos: file_app_proxyman_config_proto_enumTypes, MessageInfos: file_app_proxyman_config_proto_msgTypes, }.Build() File_app_proxyman_config_proto = out.File file_app_proxyman_config_proto_rawDesc = nil file_app_proxyman_config_proto_goTypes = nil file_app_proxyman_config_proto_depIdxs = nil } ================================================ FILE: app/proxyman/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.proxyman; option csharp_namespace = "V2Ray.Core.App.Proxyman"; option go_package = "v2ray.com/core/app/proxyman"; option java_package = "com.v2ray.core.app.proxyman"; option java_multiple_files = true; import "common/net/address.proto"; import "common/net/port.proto"; import "transport/internet/config.proto"; import "common/serial/typed_message.proto"; message InboundConfig {} message AllocationStrategy { enum Type { // Always allocate all connection handlers. Always = 0; // Randomly allocate specific range of handlers. Random = 1; // External. Not supported yet. External = 2; } Type type = 1; message AllocationStrategyConcurrency { uint32 value = 1; } // Number of handlers (ports) running in parallel. // Default value is 3 if unset. AllocationStrategyConcurrency concurrency = 2; message AllocationStrategyRefresh { uint32 value = 1; } // Number of minutes before a handler is regenerated. // Default value is 5 if unset. AllocationStrategyRefresh refresh = 3; } enum KnownProtocols { HTTP = 0; TLS = 1; } message SniffingConfig { // Whether or not to enable content sniffing on an inbound connection. bool enabled = 1; // Override target destination if sniff'ed protocol is in the given list. // Supported values are "http", "tls". repeated string destination_override = 2; } message ReceiverConfig { // PortRange specifies the ports which the Receiver should listen on. v2ray.core.common.net.PortRange port_range = 1; // Listen specifies the IP address that the Receiver should listen on. v2ray.core.common.net.IPOrDomain listen = 2; AllocationStrategy allocation_strategy = 3; v2ray.core.transport.internet.StreamConfig stream_settings = 4; bool receive_original_destination = 5; reserved 6; // Override domains for the given protocol. // Deprecated. Use sniffing_settings. repeated KnownProtocols domain_override = 7 [deprecated = true]; SniffingConfig sniffing_settings = 8; } message InboundHandlerConfig { string tag = 1; v2ray.core.common.serial.TypedMessage receiver_settings = 2; v2ray.core.common.serial.TypedMessage proxy_settings = 3; } message OutboundConfig {} message SenderConfig { // Send traffic through the given IP. Only IP is allowed. v2ray.core.common.net.IPOrDomain via = 1; v2ray.core.transport.internet.StreamConfig stream_settings = 2; v2ray.core.transport.internet.ProxyConfig proxy_settings = 3; MultiplexingConfig multiplex_settings = 4; } message MultiplexingConfig { // Whether or not Mux is enabled. bool enabled = 1; // Max number of concurrent connections that one Mux connection can handle. uint32 concurrency = 2; } ================================================ FILE: app/proxyman/inbound/always.go ================================================ package inbound import ( "context" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/dice" "v2ray.com/core/common/errors" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/features/policy" "v2ray.com/core/features/stats" "v2ray.com/core/proxy" "v2ray.com/core/transport/internet" ) func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { var uplinkCounter stats.Counter var downlinkCounter stats.Counter policy := v.GetFeature(policy.ManagerType()).(policy.Manager) if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink { statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) name := "inbound>>>" + tag + ">>>traffic>>>uplink" c, _ := stats.GetOrRegisterCounter(statsManager, name) if c != nil { uplinkCounter = c } } if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink { statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) name := "inbound>>>" + tag + ">>>traffic>>>downlink" c, _ := stats.GetOrRegisterCounter(statsManager, name) if c != nil { downlinkCounter = c } } return uplinkCounter, downlinkCounter } type AlwaysOnInboundHandler struct { proxy proxy.Inbound workers []worker mux *mux.Server tag string } func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) { rawProxy, err := common.CreateObject(ctx, proxyConfig) if err != nil { return nil, err } p, ok := rawProxy.(proxy.Inbound) if !ok { return nil, newError("not an inbound proxy.") } h := &AlwaysOnInboundHandler{ proxy: p, mux: mux.NewServer(ctx), tag: tag, } uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag) nl := p.Network() pr := receiverConfig.PortRange address := receiverConfig.Listen.AsAddress() if address == nil { address = net.AnyIP } mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings) if err != nil { return nil, newError("failed to parse stream config").Base(err).AtWarning() } if receiverConfig.ReceiveOriginalDestination { if mss.SocketSettings == nil { mss.SocketSettings = &internet.SocketConfig{} } if mss.SocketSettings.Tproxy == internet.SocketConfig_Off { mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect } mss.SocketSettings.ReceiveOriginalDestAddress = true } for port := pr.From; port <= pr.To; port++ { if net.HasNetwork(nl, net.Network_TCP) { newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog() worker := &tcpWorker{ address: address, port: net.Port(port), proxy: p, stream: mss, recvOrigDest: receiverConfig.ReceiveOriginalDestination, tag: tag, dispatcher: h.mux, sniffingConfig: receiverConfig.GetEffectiveSniffingSettings(), uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, ctx: ctx, } h.workers = append(h.workers, worker) } if net.HasNetwork(nl, net.Network_UDP) { worker := &udpWorker{ tag: tag, proxy: p, address: address, port: net.Port(port), dispatcher: h.mux, uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, stream: mss, } h.workers = append(h.workers, worker) } } return h, nil } // Start implements common.Runnable. func (h *AlwaysOnInboundHandler) Start() error { for _, worker := range h.workers { if err := worker.Start(); err != nil { return err } } return nil } // Close implements common.Closable. func (h *AlwaysOnInboundHandler) Close() error { var errs []error for _, worker := range h.workers { errs = append(errs, worker.Close()) } errs = append(errs, h.mux.Close()) if err := errors.Combine(errs...); err != nil { return newError("failed to close all resources").Base(err) } return nil } func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) { if len(h.workers) == 0 { return nil, 0, 0 } w := h.workers[dice.Roll(len(h.workers))] return w.Proxy(), w.Port(), 9999 } func (h *AlwaysOnInboundHandler) Tag() string { return h.tag } func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound { return h.proxy } ================================================ FILE: app/proxyman/inbound/dynamic.go ================================================ package inbound import ( "context" "sync" "time" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common/dice" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/task" "v2ray.com/core/proxy" "v2ray.com/core/transport/internet" ) type DynamicInboundHandler struct { tag string v *core.Instance proxyConfig interface{} receiverConfig *proxyman.ReceiverConfig streamSettings *internet.MemoryStreamConfig portMutex sync.Mutex portsInUse map[net.Port]bool workerMutex sync.RWMutex worker []worker lastRefresh time.Time mux *mux.Server task *task.Periodic ctx context.Context } func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) { v := core.MustFromContext(ctx) h := &DynamicInboundHandler{ tag: tag, proxyConfig: proxyConfig, receiverConfig: receiverConfig, portsInUse: make(map[net.Port]bool), mux: mux.NewServer(ctx), v: v, ctx: ctx, } mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings) if err != nil { return nil, newError("failed to parse stream settings").Base(err).AtWarning() } if receiverConfig.ReceiveOriginalDestination { if mss.SocketSettings == nil { mss.SocketSettings = &internet.SocketConfig{} } if mss.SocketSettings.Tproxy == internet.SocketConfig_Off { mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect } mss.SocketSettings.ReceiveOriginalDestAddress = true } h.streamSettings = mss h.task = &task.Periodic{ Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()), Execute: h.refresh, } return h, nil } func (h *DynamicInboundHandler) allocatePort() net.Port { from := int(h.receiverConfig.PortRange.From) delta := int(h.receiverConfig.PortRange.To) - from + 1 h.portMutex.Lock() defer h.portMutex.Unlock() for { r := dice.Roll(delta) port := net.Port(from + r) _, used := h.portsInUse[port] if !used { h.portsInUse[port] = true return port } } } func (h *DynamicInboundHandler) closeWorkers(workers []worker) { ports2Del := make([]net.Port, len(workers)) for idx, worker := range workers { ports2Del[idx] = worker.Port() if err := worker.Close(); err != nil { newError("failed to close worker").Base(err).WriteToLog() } } h.portMutex.Lock() for _, port := range ports2Del { delete(h.portsInUse, port) } h.portMutex.Unlock() } func (h *DynamicInboundHandler) refresh() error { h.lastRefresh = time.Now() timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2 concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue() workers := make([]worker, 0, concurrency) address := h.receiverConfig.Listen.AsAddress() if address == nil { address = net.AnyIP } uplinkCounter, downlinkCounter := getStatCounter(h.v, h.tag) for i := uint32(0); i < concurrency; i++ { port := h.allocatePort() rawProxy, err := core.CreateObject(h.v, h.proxyConfig) if err != nil { newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog() continue } p := rawProxy.(proxy.Inbound) nl := p.Network() if net.HasNetwork(nl, net.Network_TCP) { worker := &tcpWorker{ tag: h.tag, address: address, port: port, proxy: p, stream: h.streamSettings, recvOrigDest: h.receiverConfig.ReceiveOriginalDestination, dispatcher: h.mux, sniffingConfig: h.receiverConfig.GetEffectiveSniffingSettings(), uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, ctx: h.ctx, } if err := worker.Start(); err != nil { newError("failed to create TCP worker").Base(err).AtWarning().WriteToLog() continue } workers = append(workers, worker) } if net.HasNetwork(nl, net.Network_UDP) { worker := &udpWorker{ tag: h.tag, proxy: p, address: address, port: port, dispatcher: h.mux, uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, stream: h.streamSettings, } if err := worker.Start(); err != nil { newError("failed to create UDP worker").Base(err).AtWarning().WriteToLog() continue } workers = append(workers, worker) } } h.workerMutex.Lock() h.worker = workers h.workerMutex.Unlock() time.AfterFunc(timeout, func() { h.closeWorkers(workers) }) return nil } func (h *DynamicInboundHandler) Start() error { return h.task.Start() } func (h *DynamicInboundHandler) Close() error { return h.task.Close() } func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) { h.workerMutex.RLock() defer h.workerMutex.RUnlock() if len(h.worker) == 0 { return nil, 0, 0 } w := h.worker[dice.Roll(len(h.worker))] expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute) return w.Proxy(), w.Port(), int(expire) } func (h *DynamicInboundHandler) Tag() string { return h.tag } ================================================ FILE: app/proxyman/inbound/errors.generated.go ================================================ package inbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/proxyman/inbound/inbound.go ================================================ package inbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "sync" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/serial" "v2ray.com/core/common/session" "v2ray.com/core/features/inbound" ) // Manager is to manage all inbound handlers. type Manager struct { access sync.RWMutex untaggedHandler []inbound.Handler taggedHandlers map[string]inbound.Handler running bool } // New returns a new Manager for inbound handlers. func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) { m := &Manager{ taggedHandlers: make(map[string]inbound.Handler), } return m, nil } // Type implements common.HasType. func (*Manager) Type() interface{} { return inbound.ManagerType() } // AddHandler implements inbound.Manager. func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error { m.access.Lock() defer m.access.Unlock() tag := handler.Tag() if len(tag) > 0 { m.taggedHandlers[tag] = handler } else { m.untaggedHandler = append(m.untaggedHandler, handler) } if m.running { return handler.Start() } return nil } // GetHandler implements inbound.Manager. func (m *Manager) GetHandler(ctx context.Context, tag string) (inbound.Handler, error) { m.access.RLock() defer m.access.RUnlock() handler, found := m.taggedHandlers[tag] if !found { return nil, newError("handler not found: ", tag) } return handler, nil } // RemoveHandler implements inbound.Manager. func (m *Manager) RemoveHandler(ctx context.Context, tag string) error { if tag == "" { return common.ErrNoClue } m.access.Lock() defer m.access.Unlock() if handler, found := m.taggedHandlers[tag]; found { if err := handler.Close(); err != nil { newError("failed to close handler ", tag).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } delete(m.taggedHandlers, tag) return nil } return common.ErrNoClue } // Start implements common.Runnable. func (m *Manager) Start() error { m.access.Lock() defer m.access.Unlock() m.running = true for _, handler := range m.taggedHandlers { if err := handler.Start(); err != nil { return err } } for _, handler := range m.untaggedHandler { if err := handler.Start(); err != nil { return err } } return nil } // Close implements common.Closable. func (m *Manager) Close() error { m.access.Lock() defer m.access.Unlock() m.running = false var errors []interface{} for _, handler := range m.taggedHandlers { if err := handler.Close(); err != nil { errors = append(errors, err) } } for _, handler := range m.untaggedHandler { if err := handler.Close(); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return newError("failed to close all handlers").Base(newError(serial.Concat(errors...))) } return nil } // NewHandler creates a new inbound.Handler based on the given config. func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound.Handler, error) { rawReceiverSettings, err := config.ReceiverSettings.GetInstance() if err != nil { return nil, err } proxySettings, err := config.ProxySettings.GetInstance() if err != nil { return nil, err } tag := config.Tag receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig) if !ok { return nil, newError("not a ReceiverConfig").AtError() } streamSettings := receiverSettings.StreamSettings if streamSettings != nil && streamSettings.SocketSettings != nil { ctx = session.ContextWithSockopt(ctx, &session.Sockopt{ Mark: streamSettings.SocketSettings.Mark, }) } allocStrategy := receiverSettings.AllocationStrategy if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always { return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings) } if allocStrategy.Type == proxyman.AllocationStrategy_Random { return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings) } return nil, newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError() } func init() { common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*proxyman.InboundConfig)) })) common.Must(common.RegisterConfig((*core.InboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewHandler(ctx, config.(*core.InboundHandlerConfig)) })) } ================================================ FILE: app/proxyman/inbound/worker.go ================================================ package inbound import ( "context" "sync" "sync/atomic" "time" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/common/session" "v2ray.com/core/common/signal/done" "v2ray.com/core/common/task" "v2ray.com/core/features/routing" "v2ray.com/core/features/stats" "v2ray.com/core/proxy" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tcp" "v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/pipe" ) type worker interface { Start() error Close() error Port() net.Port Proxy() proxy.Inbound } type tcpWorker struct { address net.Address port net.Port proxy proxy.Inbound stream *internet.MemoryStreamConfig recvOrigDest bool tag string dispatcher routing.Dispatcher sniffingConfig *proxyman.SniffingConfig uplinkCounter stats.Counter downlinkCounter stats.Counter hub internet.Listener ctx context.Context } func getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyMode { if s == nil || s.SocketSettings == nil { return internet.SocketConfig_Off } return s.SocketSettings.Tproxy } func (w *tcpWorker) callback(conn internet.Connection) { ctx, cancel := context.WithCancel(w.ctx) sid := session.NewID() ctx = session.ContextWithID(ctx, sid) if w.recvOrigDest { var dest net.Destination switch getTProxyType(w.stream) { case internet.SocketConfig_Redirect: d, err := tcp.GetOriginalDestination(conn) if err != nil { newError("failed to get original destination").Base(err).WriteToLog(session.ExportIDToError(ctx)) } else { dest = d } case internet.SocketConfig_TProxy: dest = net.DestinationFromAddr(conn.LocalAddr()) } if dest.IsValid() { ctx = session.ContextWithOutbound(ctx, &session.Outbound{ Target: dest, }) } } ctx = session.ContextWithInbound(ctx, &session.Inbound{ Source: net.DestinationFromAddr(conn.RemoteAddr()), Gateway: net.TCPDestination(w.address, w.port), Tag: w.tag, }) content := new(session.Content) if w.sniffingConfig != nil { content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride } ctx = session.ContextWithContent(ctx, content) if w.uplinkCounter != nil || w.downlinkCounter != nil { conn = &internet.StatCouterConnection{ Connection: conn, ReadCounter: w.uplinkCounter, WriteCounter: w.downlinkCounter, } } if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil { newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) } cancel() if err := conn.Close(); err != nil { newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } func (w *tcpWorker) Proxy() proxy.Inbound { return w.proxy } func (w *tcpWorker) Start() error { ctx := context.Background() hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn internet.Connection) { go w.callback(conn) }) if err != nil { return newError("failed to listen TCP on ", w.port).AtWarning().Base(err) } w.hub = hub return nil } func (w *tcpWorker) Close() error { var errors []interface{} if w.hub != nil { if err := common.Close(w.hub); err != nil { errors = append(errors, err) } if err := common.Close(w.proxy); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return newError("failed to close all resources").Base(newError(serial.Concat(errors...))) } return nil } func (w *tcpWorker) Port() net.Port { return w.port } type udpConn struct { lastActivityTime int64 // in seconds reader buf.Reader writer buf.Writer output func([]byte) (int, error) remote net.Addr local net.Addr done *done.Instance uplink stats.Counter downlink stats.Counter } func (c *udpConn) updateActivity() { atomic.StoreInt64(&c.lastActivityTime, time.Now().Unix()) } // ReadMultiBuffer implements buf.Reader func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { mb, err := c.reader.ReadMultiBuffer() if err != nil { return nil, err } c.updateActivity() if c.uplink != nil { c.uplink.Add(int64(mb.Len())) } return mb, nil } func (c *udpConn) Read(buf []byte) (int, error) { panic("not implemented") } // Write implements io.Writer. func (c *udpConn) Write(buf []byte) (int, error) { n, err := c.output(buf) if c.downlink != nil { c.downlink.Add(int64(n)) } if err == nil { c.updateActivity() } return n, err } func (c *udpConn) Close() error { common.Must(c.done.Close()) common.Must(common.Close(c.writer)) return nil } func (c *udpConn) RemoteAddr() net.Addr { return c.remote } func (c *udpConn) LocalAddr() net.Addr { return c.local } func (*udpConn) SetDeadline(time.Time) error { return nil } func (*udpConn) SetReadDeadline(time.Time) error { return nil } func (*udpConn) SetWriteDeadline(time.Time) error { return nil } type connID struct { src net.Destination dest net.Destination } type udpWorker struct { sync.RWMutex proxy proxy.Inbound hub *udp.Hub address net.Address port net.Port tag string stream *internet.MemoryStreamConfig dispatcher routing.Dispatcher uplinkCounter stats.Counter downlinkCounter stats.Counter checker *task.Periodic activeConn map[connID]*udpConn } func (w *udpWorker) getConnection(id connID) (*udpConn, bool) { w.Lock() defer w.Unlock() if conn, found := w.activeConn[id]; found && !conn.done.Done() { return conn, true } pReader, pWriter := pipe.New(pipe.DiscardOverflow(), pipe.WithSizeLimit(16*1024)) conn := &udpConn{ reader: pReader, writer: pWriter, output: func(b []byte) (int, error) { return w.hub.WriteTo(b, id.src) }, remote: &net.UDPAddr{ IP: id.src.Address.IP(), Port: int(id.src.Port), }, local: &net.UDPAddr{ IP: w.address.IP(), Port: int(w.port), }, done: done.New(), uplink: w.uplinkCounter, downlink: w.downlinkCounter, } w.activeConn[id] = conn conn.updateActivity() return conn, false } func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) { id := connID{ src: source, } if originalDest.IsValid() { id.dest = originalDest } conn, existing := w.getConnection(id) // payload will be discarded in pipe is full. conn.writer.WriteMultiBuffer(buf.MultiBuffer{b}) // nolint: errcheck if !existing { common.Must(w.checker.Start()) go func() { ctx := context.Background() sid := session.NewID() ctx = session.ContextWithID(ctx, sid) if originalDest.IsValid() { ctx = session.ContextWithOutbound(ctx, &session.Outbound{ Target: originalDest, }) } ctx = session.ContextWithInbound(ctx, &session.Inbound{ Source: source, Gateway: net.UDPDestination(w.address, w.port), Tag: w.tag, }) if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil { newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) } conn.Close() // nolint: errcheck w.removeConn(id) }() } } func (w *udpWorker) removeConn(id connID) { w.Lock() delete(w.activeConn, id) w.Unlock() } func (w *udpWorker) handlePackets() { receive := w.hub.Receive() for payload := range receive { w.callback(payload.Payload, payload.Source, payload.Target) } } func (w *udpWorker) clean() error { nowSec := time.Now().Unix() w.Lock() defer w.Unlock() if len(w.activeConn) == 0 { return newError("no more connections. stopping...") } for addr, conn := range w.activeConn { if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 8 { //TODO Timeout too small delete(w.activeConn, addr) conn.Close() // nolint: errcheck } } if len(w.activeConn) == 0 { w.activeConn = make(map[connID]*udpConn, 16) } return nil } func (w *udpWorker) Start() error { w.activeConn = make(map[connID]*udpConn, 16) ctx := context.Background() h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256)) if err != nil { return err } w.checker = &task.Periodic{ Interval: time.Second * 16, Execute: w.clean, } w.hub = h go w.handlePackets() return nil } func (w *udpWorker) Close() error { w.Lock() defer w.Unlock() var errors []interface{} if w.hub != nil { if err := w.hub.Close(); err != nil { errors = append(errors, err) } } if w.checker != nil { if err := w.checker.Close(); err != nil { errors = append(errors, err) } } if err := common.Close(w.proxy); err != nil { errors = append(errors, err) } if len(errors) > 0 { return newError("failed to close all resources").Base(newError(serial.Concat(errors...))) } return nil } func (w *udpWorker) Port() net.Port { return w.port } func (w *udpWorker) Proxy() proxy.Inbound { return w.proxy } ================================================ FILE: app/proxyman/outbound/errors.generated.go ================================================ package outbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/proxyman/outbound/handler.go ================================================ package outbound import ( "context" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/features/outbound" "v2ray.com/core/features/policy" "v2ray.com/core/features/stats" "v2ray.com/core/proxy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/pipe" ) func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { var uplinkCounter stats.Counter var downlinkCounter stats.Counter policy := v.GetFeature(policy.ManagerType()).(policy.Manager) if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink { statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) name := "outbound>>>" + tag + ">>>traffic>>>uplink" c, _ := stats.GetOrRegisterCounter(statsManager, name) if c != nil { uplinkCounter = c } } if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink { statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) name := "outbound>>>" + tag + ">>>traffic>>>downlink" c, _ := stats.GetOrRegisterCounter(statsManager, name) if c != nil { downlinkCounter = c } } return uplinkCounter, downlinkCounter } // Handler is an implements of outbound.Handler. type Handler struct { tag string senderSettings *proxyman.SenderConfig streamSettings *internet.MemoryStreamConfig proxy proxy.Outbound outboundManager outbound.Manager mux *mux.ClientManager uplinkCounter stats.Counter downlinkCounter stats.Counter } // NewHandler create a new Handler based on the given configuration. func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) { v := core.MustFromContext(ctx) uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag) h := &Handler{ tag: config.Tag, outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager), uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter, } if config.SenderSettings != nil { senderSettings, err := config.SenderSettings.GetInstance() if err != nil { return nil, err } switch s := senderSettings.(type) { case *proxyman.SenderConfig: h.senderSettings = s mss, err := internet.ToMemoryStreamConfig(s.StreamSettings) if err != nil { return nil, newError("failed to parse stream settings").Base(err).AtWarning() } h.streamSettings = mss default: return nil, newError("settings is not SenderConfig") } } proxyConfig, err := config.ProxySettings.GetInstance() if err != nil { return nil, err } rawProxyHandler, err := common.CreateObject(ctx, proxyConfig) if err != nil { return nil, err } proxyHandler, ok := rawProxyHandler.(proxy.Outbound) if !ok { return nil, newError("not an outbound handler") } if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil { config := h.senderSettings.MultiplexSettings if config.Concurrency < 1 || config.Concurrency > 1024 { return nil, newError("invalid mux concurrency: ", config.Concurrency).AtWarning() } h.mux = &mux.ClientManager{ Enabled: h.senderSettings.MultiplexSettings.Enabled, Picker: &mux.IncrementalWorkerPicker{ Factory: &mux.DialingWorkerFactory{ Proxy: proxyHandler, Dialer: h, Strategy: mux.ClientStrategy{ MaxConcurrency: config.Concurrency, MaxConnection: 128, }, }, }, } } h.proxy = proxyHandler return h, nil } // Tag implements outbound.Handler. func (h *Handler) Tag() string { return h.tag } // Dispatch implements proxy.Outbound.Dispatch. func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) { if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) { if err := h.mux.Dispatch(ctx, link); err != nil { newError("failed to process mux outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(link.Writer) } } else { if err := h.proxy.Process(ctx, link, h); err != nil { // Ensure outbound ray is properly closed. newError("failed to process outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(link.Writer) } else { common.Must(common.Close(link.Writer)) } common.Interrupt(link.Reader) } } // Address implements internet.Dialer. func (h *Handler) Address() net.Address { if h.senderSettings == nil || h.senderSettings.Via == nil { return nil } return h.senderSettings.Via.AsAddress() } // Dial implements internet.Dialer. func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { if h.senderSettings != nil { if h.senderSettings.ProxySettings.HasTag() { tag := h.senderSettings.ProxySettings.Tag handler := h.outboundManager.GetHandler(tag) if handler != nil { newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx)) ctx = session.ContextWithOutbound(ctx, &session.Outbound{ Target: dest, }) opts := pipe.OptionsFromContext(ctx) uplinkReader, uplinkWriter := pipe.New(opts...) downlinkReader, downlinkWriter := pipe.New(opts...) go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}) conn := net.NewConnection(net.ConnectionInputMulti(uplinkWriter), net.ConnectionOutputMulti(downlinkReader)) if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) conn = tls.Client(conn, tlsConfig) } return h.getStatCouterConnection(conn), nil } newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } if h.senderSettings.Via != nil { outbound := session.OutboundFromContext(ctx) if outbound == nil { outbound = new(session.Outbound) ctx = session.ContextWithOutbound(ctx, outbound) } outbound.Gateway = h.senderSettings.Via.AsAddress() } } conn, err := internet.Dial(ctx, dest, h.streamSettings) return h.getStatCouterConnection(conn), err } func (h *Handler) getStatCouterConnection(conn internet.Connection) internet.Connection { if h.uplinkCounter != nil || h.downlinkCounter != nil { return &internet.StatCouterConnection{ Connection: conn, ReadCounter: h.downlinkCounter, WriteCounter: h.uplinkCounter, } } return conn } // GetOutbound implements proxy.GetOutbound. func (h *Handler) GetOutbound() proxy.Outbound { return h.proxy } // Start implements common.Runnable. func (h *Handler) Start() error { return nil } // Close implements common.Closable. func (h *Handler) Close() error { common.Close(h.mux) return nil } ================================================ FILE: app/proxyman/outbound/handler_test.go ================================================ package outbound_test import ( "context" "testing" "v2ray.com/core" "v2ray.com/core/app/policy" . "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/app/stats" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/features/outbound" "v2ray.com/core/proxy/freedom" "v2ray.com/core/transport/internet" ) func TestInterfaces(t *testing.T) { _ = (outbound.Handler)(new(Handler)) _ = (outbound.Manager)(new(Manager)) } const v2rayKey core.V2rayKey = 1 func TestOutboundWithoutStatCounter(t *testing.T) { config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&stats.Config{}), serial.ToTypedMessage(&policy.Config{ System: &policy.SystemPolicy{ Stats: &policy.SystemPolicy_Stats{ InboundUplink: true, }, }, }), }, } v, _ := core.New(config) v.AddFeature((outbound.Manager)(new(Manager))) ctx := context.WithValue(context.Background(), v2rayKey, v) h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ Tag: "tag", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }) conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146)) _, ok := conn.(*internet.StatCouterConnection) if ok { t.Errorf("Expected conn to not be StatCouterConnection") } } func TestOutboundWithStatCounter(t *testing.T) { config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&stats.Config{}), serial.ToTypedMessage(&policy.Config{ System: &policy.SystemPolicy{ Stats: &policy.SystemPolicy_Stats{ OutboundUplink: true, OutboundDownlink: true, }, }, }), }, } v, _ := core.New(config) v.AddFeature((outbound.Manager)(new(Manager))) ctx := context.WithValue(context.Background(), v2rayKey, v) h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ Tag: "tag", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }) conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146)) _, ok := conn.(*internet.StatCouterConnection) if !ok { t.Errorf("Expected conn to be StatCouterConnection") } } ================================================ FILE: app/proxyman/outbound/outbound.go ================================================ package outbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "strings" "sync" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/features/outbound" ) // Manager is to manage all outbound handlers. type Manager struct { access sync.RWMutex defaultHandler outbound.Handler taggedHandler map[string]outbound.Handler untaggedHandlers []outbound.Handler running bool } // New creates a new Manager. func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) { m := &Manager{ taggedHandler: make(map[string]outbound.Handler), } return m, nil } // Type implements common.HasType. func (m *Manager) Type() interface{} { return outbound.ManagerType() } // Start implements core.Feature func (m *Manager) Start() error { m.access.Lock() defer m.access.Unlock() m.running = true for _, h := range m.taggedHandler { if err := h.Start(); err != nil { return err } } for _, h := range m.untaggedHandlers { if err := h.Start(); err != nil { return err } } return nil } // Close implements core.Feature func (m *Manager) Close() error { m.access.Lock() defer m.access.Unlock() m.running = false var errs []error for _, h := range m.taggedHandler { errs = append(errs, h.Close()) } for _, h := range m.untaggedHandlers { errs = append(errs, h.Close()) } return errors.Combine(errs...) } // GetDefaultHandler implements outbound.Manager. func (m *Manager) GetDefaultHandler() outbound.Handler { m.access.RLock() defer m.access.RUnlock() if m.defaultHandler == nil { return nil } return m.defaultHandler } // GetHandler implements outbound.Manager. func (m *Manager) GetHandler(tag string) outbound.Handler { m.access.RLock() defer m.access.RUnlock() if handler, found := m.taggedHandler[tag]; found { return handler } return nil } // AddHandler implements outbound.Manager. func (m *Manager) AddHandler(ctx context.Context, handler outbound.Handler) error { m.access.Lock() defer m.access.Unlock() if m.defaultHandler == nil { m.defaultHandler = handler } tag := handler.Tag() if len(tag) > 0 { m.taggedHandler[tag] = handler } else { m.untaggedHandlers = append(m.untaggedHandlers, handler) } if m.running { return handler.Start() } return nil } // RemoveHandler implements outbound.Manager. func (m *Manager) RemoveHandler(ctx context.Context, tag string) error { if tag == "" { return common.ErrNoClue } m.access.Lock() defer m.access.Unlock() delete(m.taggedHandler, tag) if m.defaultHandler != nil && m.defaultHandler.Tag() == tag { m.defaultHandler = nil } return nil } // Select implements outbound.HandlerSelector. func (m *Manager) Select(selectors []string) []string { m.access.RLock() defer m.access.RUnlock() tags := make([]string, 0, len(selectors)) for tag := range m.taggedHandler { match := false for _, selector := range selectors { if strings.HasPrefix(tag, selector) { match = true break } } if match { tags = append(tags, tag) } } return tags } func init() { common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*proxyman.OutboundConfig)) })) common.Must(common.RegisterConfig((*core.OutboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewHandler(ctx, config.(*core.OutboundHandlerConfig)) })) } ================================================ FILE: app/proxyman/proxyman.go ================================================ // Package proxyman defines applications for managing inbound and outbound proxies. package proxyman import ( "context" "v2ray.com/core/common/session" ) // ContextWithSniffingConfig is a wrapper of session.ContextWithContent. // Deprecated. Use session.ContextWithContent directly. func ContextWithSniffingConfig(ctx context.Context, c *SniffingConfig) context.Context { content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } content.SniffingRequest.Enabled = c.Enabled content.SniffingRequest.OverrideDestinationForProtocol = c.DestinationOverride return ctx } ================================================ FILE: app/reverse/bridge.go ================================================ // +build !confonly package reverse import ( "context" "time" "github.com/golang/protobuf/proto" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/common/task" "v2ray.com/core/features/routing" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) // Bridge is a component in reverse proxy, that relays connections from Portal to local address. type Bridge struct { dispatcher routing.Dispatcher tag string domain string workers []*BridgeWorker monitorTask *task.Periodic } // NewBridge creates a new Bridge instance. func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) { if config.Tag == "" { return nil, newError("bridge tag is empty") } if config.Domain == "" { return nil, newError("bridge domain is empty") } b := &Bridge{ dispatcher: dispatcher, tag: config.Tag, domain: config.Domain, } b.monitorTask = &task.Periodic{ Execute: b.monitor, Interval: time.Second * 2, } return b, nil } func (b *Bridge) cleanup() { var activeWorkers []*BridgeWorker for _, w := range b.workers { if w.IsActive() { activeWorkers = append(activeWorkers, w) } } if len(activeWorkers) != len(b.workers) { b.workers = activeWorkers } } func (b *Bridge) monitor() error { b.cleanup() var numConnections uint32 var numWorker uint32 for _, w := range b.workers { if w.IsActive() { numConnections += w.Connections() numWorker++ } } if numWorker == 0 || numConnections/numWorker > 16 { worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher) if err != nil { newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog() return nil } b.workers = append(b.workers, worker) } return nil } func (b *Bridge) Start() error { return b.monitorTask.Start() } func (b *Bridge) Close() error { return b.monitorTask.Close() } type BridgeWorker struct { tag string worker *mux.ServerWorker dispatcher routing.Dispatcher state Control_State } func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) { ctx := context.Background() ctx = session.ContextWithInbound(ctx, &session.Inbound{ Tag: tag, }) link, err := d.Dispatch(ctx, net.Destination{ Network: net.Network_TCP, Address: net.DomainAddress(domain), Port: 0, }) if err != nil { return nil, err } w := &BridgeWorker{ dispatcher: d, tag: tag, } worker, err := mux.NewServerWorker(context.Background(), w, link) if err != nil { return nil, err } w.worker = worker return w, nil } func (w *BridgeWorker) Type() interface{} { return routing.DispatcherType() } func (w *BridgeWorker) Start() error { return nil } func (w *BridgeWorker) Close() error { return nil } func (w *BridgeWorker) IsActive() bool { return w.state == Control_ACTIVE && !w.worker.Closed() } func (w *BridgeWorker) Connections() uint32 { return w.worker.ActiveConnections() } func (w *BridgeWorker) handleInternalConn(link transport.Link) { go func() { reader := link.Reader for { mb, err := reader.ReadMultiBuffer() if err != nil { break } for _, b := range mb { var ctl Control if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil { newError("failed to parse proto message").Base(err).WriteToLog() break } if ctl.State != w.state { w.state = ctl.State } } } }() } func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { if !isInternalDomain(dest) { ctx = session.ContextWithInbound(ctx, &session.Inbound{ Tag: w.tag, }) return w.dispatcher.Dispatch(ctx, dest) } opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} uplinkReader, uplinkWriter := pipe.New(opt...) downlinkReader, downlinkWriter := pipe.New(opt...) w.handleInternalConn(transport.Link{ Reader: downlinkReader, Writer: uplinkWriter, }) return &transport.Link{ Reader: uplinkReader, Writer: downlinkWriter, }, nil } ================================================ FILE: app/reverse/config.go ================================================ // +build !confonly package reverse import ( "crypto/rand" "io" "v2ray.com/core/common/dice" ) func (c *Control) FillInRandom() { randomLength := dice.Roll(64) c.Random = make([]byte, randomLength) io.ReadFull(rand.Reader, c.Random) } ================================================ FILE: app/reverse/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/reverse/config.proto package reverse import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Control_State int32 const ( Control_ACTIVE Control_State = 0 Control_DRAIN Control_State = 1 ) // Enum value maps for Control_State. var ( Control_State_name = map[int32]string{ 0: "ACTIVE", 1: "DRAIN", } Control_State_value = map[string]int32{ "ACTIVE": 0, "DRAIN": 1, } ) func (x Control_State) Enum() *Control_State { p := new(Control_State) *p = x return p } func (x Control_State) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Control_State) Descriptor() protoreflect.EnumDescriptor { return file_app_reverse_config_proto_enumTypes[0].Descriptor() } func (Control_State) Type() protoreflect.EnumType { return &file_app_reverse_config_proto_enumTypes[0] } func (x Control_State) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Control_State.Descriptor instead. func (Control_State) EnumDescriptor() ([]byte, []int) { return file_app_reverse_config_proto_rawDescGZIP(), []int{0, 0} } type Control struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields State Control_State `protobuf:"varint,1,opt,name=state,proto3,enum=v2ray.core.app.reverse.Control_State" json:"state,omitempty"` Random []byte `protobuf:"bytes,99,opt,name=random,proto3" json:"random,omitempty"` } func (x *Control) Reset() { *x = Control{} if protoimpl.UnsafeEnabled { mi := &file_app_reverse_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Control) String() string { return protoimpl.X.MessageStringOf(x) } func (*Control) ProtoMessage() {} func (x *Control) ProtoReflect() protoreflect.Message { mi := &file_app_reverse_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Control.ProtoReflect.Descriptor instead. func (*Control) Descriptor() ([]byte, []int) { return file_app_reverse_config_proto_rawDescGZIP(), []int{0} } func (x *Control) GetState() Control_State { if x != nil { return x.State } return Control_ACTIVE } func (x *Control) GetRandom() []byte { if x != nil { return x.Random } return nil } type BridgeConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` } func (x *BridgeConfig) Reset() { *x = BridgeConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_reverse_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *BridgeConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*BridgeConfig) ProtoMessage() {} func (x *BridgeConfig) ProtoReflect() protoreflect.Message { mi := &file_app_reverse_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BridgeConfig.ProtoReflect.Descriptor instead. func (*BridgeConfig) Descriptor() ([]byte, []int) { return file_app_reverse_config_proto_rawDescGZIP(), []int{1} } func (x *BridgeConfig) GetTag() string { if x != nil { return x.Tag } return "" } func (x *BridgeConfig) GetDomain() string { if x != nil { return x.Domain } return "" } type PortalConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` } func (x *PortalConfig) Reset() { *x = PortalConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_reverse_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PortalConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*PortalConfig) ProtoMessage() {} func (x *PortalConfig) ProtoReflect() protoreflect.Message { mi := &file_app_reverse_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PortalConfig.ProtoReflect.Descriptor instead. func (*PortalConfig) Descriptor() ([]byte, []int) { return file_app_reverse_config_proto_rawDescGZIP(), []int{2} } func (x *PortalConfig) GetTag() string { if x != nil { return x.Tag } return "" } func (x *PortalConfig) GetDomain() string { if x != nil { return x.Domain } return "" } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields BridgeConfig []*BridgeConfig `protobuf:"bytes,1,rep,name=bridge_config,json=bridgeConfig,proto3" json:"bridge_config,omitempty"` PortalConfig []*PortalConfig `protobuf:"bytes,2,rep,name=portal_config,json=portalConfig,proto3" json:"portal_config,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_reverse_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_reverse_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_reverse_config_proto_rawDescGZIP(), []int{3} } func (x *Config) GetBridgeConfig() []*BridgeConfig { if x != nil { return x.BridgeConfig } return nil } func (x *Config) GetPortalConfig() []*PortalConfig { if x != nil { return x.PortalConfig } return nil } var File_app_reverse_config_proto protoreflect.FileDescriptor var file_app_reverse_config_proto_rawDesc = []byte{ 0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22, 0x7e, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x18, 0x63, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x52, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x22, 0x38, 0x0a, 0x0c, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x38, 0x0a, 0x0c, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x9e, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x49, 0x0a, 0x0d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x49, 0x0a, 0x0d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x57, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x50, 0x01, 0x5a, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_reverse_config_proto_rawDescOnce sync.Once file_app_reverse_config_proto_rawDescData = file_app_reverse_config_proto_rawDesc ) func file_app_reverse_config_proto_rawDescGZIP() []byte { file_app_reverse_config_proto_rawDescOnce.Do(func() { file_app_reverse_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_reverse_config_proto_rawDescData) }) return file_app_reverse_config_proto_rawDescData } var file_app_reverse_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_app_reverse_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_app_reverse_config_proto_goTypes = []interface{}{ (Control_State)(0), // 0: v2ray.core.app.reverse.Control.State (*Control)(nil), // 1: v2ray.core.app.reverse.Control (*BridgeConfig)(nil), // 2: v2ray.core.app.reverse.BridgeConfig (*PortalConfig)(nil), // 3: v2ray.core.app.reverse.PortalConfig (*Config)(nil), // 4: v2ray.core.app.reverse.Config } var file_app_reverse_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.app.reverse.Control.state:type_name -> v2ray.core.app.reverse.Control.State 2, // 1: v2ray.core.app.reverse.Config.bridge_config:type_name -> v2ray.core.app.reverse.BridgeConfig 3, // 2: v2ray.core.app.reverse.Config.portal_config:type_name -> v2ray.core.app.reverse.PortalConfig 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_app_reverse_config_proto_init() } func file_app_reverse_config_proto_init() { if File_app_reverse_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_reverse_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Control); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_reverse_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BridgeConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_reverse_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PortalConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_reverse_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_reverse_config_proto_rawDesc, NumEnums: 1, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_reverse_config_proto_goTypes, DependencyIndexes: file_app_reverse_config_proto_depIdxs, EnumInfos: file_app_reverse_config_proto_enumTypes, MessageInfos: file_app_reverse_config_proto_msgTypes, }.Build() File_app_reverse_config_proto = out.File file_app_reverse_config_proto_rawDesc = nil file_app_reverse_config_proto_goTypes = nil file_app_reverse_config_proto_depIdxs = nil } ================================================ FILE: app/reverse/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.reverse; option csharp_namespace = "V2Ray.Core.Proxy.Reverse"; option go_package = "v2ray.com/core/app/reverse"; option java_package = "com.v2ray.core.proxy.reverse"; option java_multiple_files = true; message Control { enum State { ACTIVE = 0; DRAIN = 1; } State state = 1; bytes random = 99; } message BridgeConfig { string tag = 1; string domain = 2; } message PortalConfig { string tag = 1; string domain = 2; } message Config { repeated BridgeConfig bridge_config = 1; repeated PortalConfig portal_config = 2; } ================================================ FILE: app/reverse/errors.generated.go ================================================ package reverse import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/reverse/portal.go ================================================ // +build !confonly package reverse import ( "context" "sync" "time" "github.com/golang/protobuf/proto" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/common/task" "v2ray.com/core/features/outbound" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) type Portal struct { ohm outbound.Manager tag string domain string picker *StaticMuxPicker client *mux.ClientManager } func NewPortal(config *PortalConfig, ohm outbound.Manager) (*Portal, error) { if config.Tag == "" { return nil, newError("portal tag is empty") } if config.Domain == "" { return nil, newError("portal domain is empty") } picker, err := NewStaticMuxPicker() if err != nil { return nil, err } return &Portal{ ohm: ohm, tag: config.Tag, domain: config.Domain, picker: picker, client: &mux.ClientManager{ Picker: picker, }, }, nil } func (p *Portal) Start() error { return p.ohm.AddHandler(context.Background(), &Outbound{ portal: p, tag: p.tag, }) } func (p *Portal) Close() error { return p.ohm.RemoveHandler(context.Background(), p.tag) } func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error { outboundMeta := session.OutboundFromContext(ctx) if outboundMeta == nil { return newError("outbound metadata not found").AtError() } if isDomain(outboundMeta.Target, p.domain) { muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) if err != nil { return newError("failed to create mux client worker").Base(err).AtWarning() } worker, err := NewPortalWorker(muxClient) if err != nil { return newError("failed to create portal worker").Base(err) } p.picker.AddWorker(worker) return nil } return p.client.Dispatch(ctx, link) } type Outbound struct { portal *Portal tag string } func (o *Outbound) Tag() string { return o.tag } func (o *Outbound) Dispatch(ctx context.Context, link *transport.Link) { if err := o.portal.HandleConnection(ctx, link); err != nil { newError("failed to process reverse connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(link.Writer) } } func (o *Outbound) Start() error { return nil } func (o *Outbound) Close() error { return nil } type StaticMuxPicker struct { access sync.Mutex workers []*PortalWorker cTask *task.Periodic } func NewStaticMuxPicker() (*StaticMuxPicker, error) { p := &StaticMuxPicker{} p.cTask = &task.Periodic{ Execute: p.cleanup, Interval: time.Second * 30, } p.cTask.Start() return p, nil } func (p *StaticMuxPicker) cleanup() error { p.access.Lock() defer p.access.Unlock() var activeWorkers []*PortalWorker for _, w := range p.workers { if !w.Closed() { activeWorkers = append(activeWorkers, w) } } if len(activeWorkers) != len(p.workers) { p.workers = activeWorkers } return nil } func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) { p.access.Lock() defer p.access.Unlock() if len(p.workers) == 0 { return nil, newError("empty worker list") } var minIdx int = -1 var minConn uint32 = 9999 for i, w := range p.workers { if w.draining { continue } if w.client.ActiveConnections() < minConn { minConn = w.client.ActiveConnections() minIdx = i } } if minIdx == -1 { for i, w := range p.workers { if w.IsFull() { continue } if w.client.ActiveConnections() < minConn { minConn = w.client.ActiveConnections() minIdx = i } } } if minIdx != -1 { return p.workers[minIdx].client, nil } return nil, newError("no mux client worker available") } func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) { p.access.Lock() defer p.access.Unlock() p.workers = append(p.workers, worker) } type PortalWorker struct { client *mux.ClientWorker control *task.Periodic writer buf.Writer reader buf.Reader draining bool } func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) { opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} uplinkReader, uplinkWriter := pipe.New(opt...) downlinkReader, downlinkWriter := pipe.New(opt...) ctx := context.Background() ctx = session.ContextWithOutbound(ctx, &session.Outbound{ Target: net.UDPDestination(net.DomainAddress(internalDomain), 0), }) f := client.Dispatch(ctx, &transport.Link{ Reader: uplinkReader, Writer: downlinkWriter, }) if !f { return nil, newError("unable to dispatch control connection") } w := &PortalWorker{ client: client, reader: downlinkReader, writer: uplinkWriter, } w.control = &task.Periodic{ Execute: w.heartbeat, Interval: time.Second * 2, } w.control.Start() return w, nil } func (w *PortalWorker) heartbeat() error { if w.client.Closed() { return newError("client worker stopped") } if w.draining || w.writer == nil { return newError("already disposed") } msg := &Control{} msg.FillInRandom() if w.client.TotalConnections() > 256 { w.draining = true msg.State = Control_DRAIN defer func() { common.Close(w.writer) common.Interrupt(w.reader) w.writer = nil }() } b, err := proto.Marshal(msg) common.Must(err) mb := buf.MergeBytes(nil, b) return w.writer.WriteMultiBuffer(mb) } func (w *PortalWorker) IsFull() bool { return w.client.IsFull() } func (w *PortalWorker) Closed() bool { return w.client.Closed() } ================================================ FILE: app/reverse/portal_test.go ================================================ package reverse_test import ( "testing" "v2ray.com/core/app/reverse" "v2ray.com/core/common" ) func TestStaticPickerEmpty(t *testing.T) { picker, err := reverse.NewStaticMuxPicker() common.Must(err) worker, err := picker.PickAvailable() if err == nil { t.Error("expected error, but nil") } if worker != nil { t.Error("expected nil worker, but not nil") } } ================================================ FILE: app/reverse/reverse.go ================================================ // +build !confonly package reverse //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/features/outbound" "v2ray.com/core/features/routing" ) const ( internalDomain = "reverse.internal.v2ray.com" ) func isDomain(dest net.Destination, domain string) bool { return dest.Address.Family().IsDomain() && dest.Address.Domain() == domain } func isInternalDomain(dest net.Destination) bool { return isDomain(dest, internalDomain) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { r := new(Reverse) if err := core.RequireFeatures(ctx, func(d routing.Dispatcher, om outbound.Manager) error { return r.Init(config.(*Config), d, om) }); err != nil { return nil, err } return r, nil })) } type Reverse struct { bridges []*Bridge portals []*Portal } func (r *Reverse) Init(config *Config, d routing.Dispatcher, ohm outbound.Manager) error { for _, bConfig := range config.BridgeConfig { b, err := NewBridge(bConfig, d) if err != nil { return err } r.bridges = append(r.bridges, b) } for _, pConfig := range config.PortalConfig { p, err := NewPortal(pConfig, ohm) if err != nil { return err } r.portals = append(r.portals, p) } return nil } func (r *Reverse) Type() interface{} { return (*Reverse)(nil) } func (r *Reverse) Start() error { for _, b := range r.bridges { if err := b.Start(); err != nil { return err } } for _, p := range r.portals { if err := p.Start(); err != nil { return err } } return nil } func (r *Reverse) Close() error { var errs []error for _, b := range r.bridges { errs = append(errs, b.Close()) } for _, p := range r.portals { errs = append(errs, p.Close()) } return errors.Combine(errs...) } ================================================ FILE: app/router/balancing.go ================================================ // +build !confonly package router import ( "v2ray.com/core/common/dice" "v2ray.com/core/features/outbound" ) type BalancingStrategy interface { PickOutbound([]string) string } type RandomStrategy struct { } func (s *RandomStrategy) PickOutbound(tags []string) string { n := len(tags) if n == 0 { panic("0 tags") } return tags[dice.Roll(n)] } type Balancer struct { selectors []string strategy BalancingStrategy ohm outbound.Manager } func (b *Balancer) PickOutbound() (string, error) { hs, ok := b.ohm.(outbound.HandlerSelector) if !ok { return "", newError("outbound.Manager is not a HandlerSelector") } tags := hs.Select(b.selectors) if len(tags) == 0 { return "", newError("no available outbounds selected") } tag := b.strategy.PickOutbound(tags) if tag == "" { return "", newError("balancing strategy returns empty tag") } return tag, nil } ================================================ FILE: app/router/command/command.go ================================================ // +build !confonly package command //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "time" "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/features/routing" "v2ray.com/core/features/stats" ) // routingServer is an implementation of RoutingService. type routingServer struct { router routing.Router routingStats stats.Channel } // NewRoutingServer creates a statistics service with statistics manager. func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer { return &routingServer{ router: router, routingStats: routingStats, } } func (s *routingServer) TestRoute(ctx context.Context, request *TestRouteRequest) (*RoutingContext, error) { if request.RoutingContext == nil { return nil, newError("Invalid routing request.") } route, err := s.router.PickRoute(AsRoutingContext(request.RoutingContext)) if err != nil { return nil, err } if request.PublishResult && s.routingStats != nil { ctx, _ := context.WithTimeout(context.Background(), 4*time.Second) // nolint: govet s.routingStats.Publish(ctx, route) } return AsProtobufMessage(request.FieldSelectors)(route), nil } func (s *routingServer) SubscribeRoutingStats(request *SubscribeRoutingStatsRequest, stream RoutingService_SubscribeRoutingStatsServer) error { if s.routingStats == nil { return newError("Routing statistics not enabled.") } genMessage := AsProtobufMessage(request.FieldSelectors) subscriber, err := stats.SubscribeRunnableChannel(s.routingStats) if err != nil { return err } defer stats.UnsubscribeClosableChannel(s.routingStats, subscriber) // nolint: errcheck for { select { case value, ok := <-subscriber: if !ok { return newError("Upstream closed the subscriber channel.") } route, ok := value.(routing.Route) if !ok { return newError("Upstream sent malformed statistics.") } err := stream.Send(genMessage(route)) if err != nil { return err } case <-stream.Context().Done(): return stream.Context().Err() } } } func (s *routingServer) mustEmbedUnimplementedRoutingServiceServer() {} type service struct { v *core.Instance } func (s *service) Register(server *grpc.Server) { common.Must(s.v.RequireFeatures(func(router routing.Router, stats stats.Manager) { RegisterRoutingServiceServer(server, NewRoutingServer(router, nil)) })) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { s := core.MustFromContext(ctx) return &service{v: s}, nil })) } ================================================ FILE: app/router/command/command.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/router/command/command.proto package command import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // RoutingContext is the context with information relative to routing process. // It conforms to the structure of v2ray.core.features.routing.Context and // v2ray.core.features.routing.Route. type RoutingContext struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields InboundTag string `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"` Network net.Network `protobuf:"varint,2,opt,name=Network,proto3,enum=v2ray.core.common.net.Network" json:"Network,omitempty"` SourceIPs [][]byte `protobuf:"bytes,3,rep,name=SourceIPs,proto3" json:"SourceIPs,omitempty"` TargetIPs [][]byte `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"` SourcePort uint32 `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"` TargetPort uint32 `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"` TargetDomain string `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,omitempty"` Protocol string `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"` User string `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"` Attributes map[string]string `protobuf:"bytes,10,rep,name=Attributes,proto3" json:"Attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` OutboundGroupTags []string `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"` OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"` } func (x *RoutingContext) Reset() { *x = RoutingContext{} if protoimpl.UnsafeEnabled { mi := &file_app_router_command_command_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RoutingContext) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoutingContext) ProtoMessage() {} func (x *RoutingContext) ProtoReflect() protoreflect.Message { mi := &file_app_router_command_command_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoutingContext.ProtoReflect.Descriptor instead. func (*RoutingContext) Descriptor() ([]byte, []int) { return file_app_router_command_command_proto_rawDescGZIP(), []int{0} } func (x *RoutingContext) GetInboundTag() string { if x != nil { return x.InboundTag } return "" } func (x *RoutingContext) GetNetwork() net.Network { if x != nil { return x.Network } return net.Network_Unknown } func (x *RoutingContext) GetSourceIPs() [][]byte { if x != nil { return x.SourceIPs } return nil } func (x *RoutingContext) GetTargetIPs() [][]byte { if x != nil { return x.TargetIPs } return nil } func (x *RoutingContext) GetSourcePort() uint32 { if x != nil { return x.SourcePort } return 0 } func (x *RoutingContext) GetTargetPort() uint32 { if x != nil { return x.TargetPort } return 0 } func (x *RoutingContext) GetTargetDomain() string { if x != nil { return x.TargetDomain } return "" } func (x *RoutingContext) GetProtocol() string { if x != nil { return x.Protocol } return "" } func (x *RoutingContext) GetUser() string { if x != nil { return x.User } return "" } func (x *RoutingContext) GetAttributes() map[string]string { if x != nil { return x.Attributes } return nil } func (x *RoutingContext) GetOutboundGroupTags() []string { if x != nil { return x.OutboundGroupTags } return nil } func (x *RoutingContext) GetOutboundTag() string { if x != nil { return x.OutboundTag } return "" } // SubscribeRoutingStatsRequest subscribes to routing statistics channel if // opened by v2ray-core. // * FieldSelectors selects a subset of fields in routing statistics to return. // Valid selectors: // - inbound: Selects connection's inbound tag. // - network: Selects connection's network. // - ip: Equivalent as "ip_source" and "ip_target", selects both source and // target IP. // - port: Equivalent as "port_source" and "port_target", selects both source // and target port. // - domain: Selects target domain. // - protocol: Select connection's protocol. // - user: Select connection's inbound user email. // - attributes: Select connection's additional attributes. // - outbound: Equivalent as "outbound" and "outbound_group", select both // outbound tag and outbound group tags. // * If FieldSelectors is left empty, all fields will be returned. type SubscribeRoutingStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"` } func (x *SubscribeRoutingStatsRequest) Reset() { *x = SubscribeRoutingStatsRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_router_command_command_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SubscribeRoutingStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SubscribeRoutingStatsRequest) ProtoMessage() {} func (x *SubscribeRoutingStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_app_router_command_command_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SubscribeRoutingStatsRequest.ProtoReflect.Descriptor instead. func (*SubscribeRoutingStatsRequest) Descriptor() ([]byte, []int) { return file_app_router_command_command_proto_rawDescGZIP(), []int{1} } func (x *SubscribeRoutingStatsRequest) GetFieldSelectors() []string { if x != nil { return x.FieldSelectors } return nil } // TestRouteRequest manually tests a routing result according to the routing // context message. // * RoutingContext is the routing message without outbound information. // * FieldSelectors selects the fields to return in the routing result. All // fields are returned if left empty. // * PublishResult broadcasts the routing result to routing statistics channel // if set true. type TestRouteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"` FieldSelectors []string `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"` PublishResult bool `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"` } func (x *TestRouteRequest) Reset() { *x = TestRouteRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_router_command_command_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestRouteRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestRouteRequest) ProtoMessage() {} func (x *TestRouteRequest) ProtoReflect() protoreflect.Message { mi := &file_app_router_command_command_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TestRouteRequest.ProtoReflect.Descriptor instead. func (*TestRouteRequest) Descriptor() ([]byte, []int) { return file_app_router_command_command_proto_rawDescGZIP(), []int{2} } func (x *TestRouteRequest) GetRoutingContext() *RoutingContext { if x != nil { return x.RoutingContext } return nil } func (x *TestRouteRequest) GetFieldSelectors() []string { if x != nil { return x.FieldSelectors } return nil } func (x *TestRouteRequest) GetPublishResult() bool { if x != nil { return x.PublishResult } return false } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_router_command_command_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_router_command_command_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_router_command_command_proto_rawDescGZIP(), []int{3} } var File_app_router_command_command_proto protoreflect.FileDescriptor var file_app_router_command_command_proto_rawDesc = []byte{ 0x0a, 0x20, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa8, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x38, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x5d, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x46, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x89, 0x02, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x3b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x6d, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x42, 0x68, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1d, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_router_command_command_proto_rawDescOnce sync.Once file_app_router_command_command_proto_rawDescData = file_app_router_command_command_proto_rawDesc ) func file_app_router_command_command_proto_rawDescGZIP() []byte { file_app_router_command_command_proto_rawDescOnce.Do(func() { file_app_router_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_router_command_command_proto_rawDescData) }) return file_app_router_command_command_proto_rawDescData } var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_app_router_command_command_proto_goTypes = []interface{}{ (*RoutingContext)(nil), // 0: v2ray.core.app.router.command.RoutingContext (*SubscribeRoutingStatsRequest)(nil), // 1: v2ray.core.app.router.command.SubscribeRoutingStatsRequest (*TestRouteRequest)(nil), // 2: v2ray.core.app.router.command.TestRouteRequest (*Config)(nil), // 3: v2ray.core.app.router.command.Config nil, // 4: v2ray.core.app.router.command.RoutingContext.AttributesEntry (net.Network)(0), // 5: v2ray.core.common.net.Network } var file_app_router_command_command_proto_depIdxs = []int32{ 5, // 0: v2ray.core.app.router.command.RoutingContext.Network:type_name -> v2ray.core.common.net.Network 4, // 1: v2ray.core.app.router.command.RoutingContext.Attributes:type_name -> v2ray.core.app.router.command.RoutingContext.AttributesEntry 0, // 2: v2ray.core.app.router.command.TestRouteRequest.RoutingContext:type_name -> v2ray.core.app.router.command.RoutingContext 1, // 3: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> v2ray.core.app.router.command.SubscribeRoutingStatsRequest 2, // 4: v2ray.core.app.router.command.RoutingService.TestRoute:input_type -> v2ray.core.app.router.command.TestRouteRequest 0, // 5: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> v2ray.core.app.router.command.RoutingContext 0, // 6: v2ray.core.app.router.command.RoutingService.TestRoute:output_type -> v2ray.core.app.router.command.RoutingContext 5, // [5:7] is the sub-list for method output_type 3, // [3:5] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_app_router_command_command_proto_init() } func file_app_router_command_command_proto_init() { if File_app_router_command_command_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_router_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RoutingContext); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SubscribeRoutingStatsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestRouteRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_command_command_proto_rawDesc, NumEnums: 0, NumMessages: 5, NumExtensions: 0, NumServices: 1, }, GoTypes: file_app_router_command_command_proto_goTypes, DependencyIndexes: file_app_router_command_command_proto_depIdxs, MessageInfos: file_app_router_command_command_proto_msgTypes, }.Build() File_app_router_command_command_proto = out.File file_app_router_command_command_proto_rawDesc = nil file_app_router_command_command_proto_goTypes = nil file_app_router_command_command_proto_depIdxs = nil } ================================================ FILE: app/router/command/command.proto ================================================ syntax = "proto3"; package v2ray.core.app.router.command; option csharp_namespace = "V2Ray.Core.App.Router.Command"; option go_package = "v2ray.com/core/app/router/command"; option java_package = "com.v2ray.core.app.router.command"; option java_multiple_files = true; import "common/net/network.proto"; // RoutingContext is the context with information relative to routing process. // It conforms to the structure of v2ray.core.features.routing.Context and // v2ray.core.features.routing.Route. message RoutingContext { string InboundTag = 1; v2ray.core.common.net.Network Network = 2; repeated bytes SourceIPs = 3; repeated bytes TargetIPs = 4; uint32 SourcePort = 5; uint32 TargetPort = 6; string TargetDomain = 7; string Protocol = 8; string User = 9; map Attributes = 10; repeated string OutboundGroupTags = 11; string OutboundTag = 12; } // SubscribeRoutingStatsRequest subscribes to routing statistics channel if // opened by v2ray-core. // * FieldSelectors selects a subset of fields in routing statistics to return. // Valid selectors: // - inbound: Selects connection's inbound tag. // - network: Selects connection's network. // - ip: Equivalent as "ip_source" and "ip_target", selects both source and // target IP. // - port: Equivalent as "port_source" and "port_target", selects both source // and target port. // - domain: Selects target domain. // - protocol: Select connection's protocol. // - user: Select connection's inbound user email. // - attributes: Select connection's additional attributes. // - outbound: Equivalent as "outbound" and "outbound_group", select both // outbound tag and outbound group tags. // * If FieldSelectors is left empty, all fields will be returned. message SubscribeRoutingStatsRequest { repeated string FieldSelectors = 1; } // TestRouteRequest manually tests a routing result according to the routing // context message. // * RoutingContext is the routing message without outbound information. // * FieldSelectors selects the fields to return in the routing result. All // fields are returned if left empty. // * PublishResult broadcasts the routing result to routing statistics channel // if set true. message TestRouteRequest { RoutingContext RoutingContext = 1; repeated string FieldSelectors = 2; bool PublishResult = 3; } service RoutingService { rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest) returns (stream RoutingContext) {} rpc TestRoute(TestRouteRequest) returns (RoutingContext) {} } message Config {} ================================================ FILE: app/router/command/command_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package command import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion7 // RoutingServiceClient is the client API for RoutingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RoutingServiceClient interface { SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) } type routingServiceClient struct { cc grpc.ClientConnInterface } func NewRoutingServiceClient(cc grpc.ClientConnInterface) RoutingServiceClient { return &routingServiceClient{cc} } func (c *routingServiceClient) SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) { stream, err := c.cc.NewStream(ctx, &_RoutingService_serviceDesc.Streams[0], "/v2ray.core.app.router.command.RoutingService/SubscribeRoutingStats", opts...) if err != nil { return nil, err } x := &routingServiceSubscribeRoutingStatsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type RoutingService_SubscribeRoutingStatsClient interface { Recv() (*RoutingContext, error) grpc.ClientStream } type routingServiceSubscribeRoutingStatsClient struct { grpc.ClientStream } func (x *routingServiceSubscribeRoutingStatsClient) Recv() (*RoutingContext, error) { m := new(RoutingContext) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) { out := new(RoutingContext) err := c.cc.Invoke(ctx, "/v2ray.core.app.router.command.RoutingService/TestRoute", in, out, opts...) if err != nil { return nil, err } return out, nil } // RoutingServiceServer is the server API for RoutingService service. // All implementations must embed UnimplementedRoutingServiceServer // for forward compatibility type RoutingServiceServer interface { SubscribeRoutingStats(*SubscribeRoutingStatsRequest, RoutingService_SubscribeRoutingStatsServer) error TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) mustEmbedUnimplementedRoutingServiceServer() } // UnimplementedRoutingServiceServer must be embedded to have forward compatible implementations. type UnimplementedRoutingServiceServer struct { } func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRoutingStatsRequest, RoutingService_SubscribeRoutingStatsServer) error { return status.Errorf(codes.Unimplemented, "method SubscribeRoutingStats not implemented") } func (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) { return nil, status.Errorf(codes.Unimplemented, "method TestRoute not implemented") } func (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {} // UnsafeRoutingServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RoutingServiceServer will // result in compilation errors. type UnsafeRoutingServiceServer interface { mustEmbedUnimplementedRoutingServiceServer() } func RegisterRoutingServiceServer(s *grpc.Server, srv RoutingServiceServer) { s.RegisterService(&_RoutingService_serviceDesc, srv) } func _RoutingService_SubscribeRoutingStats_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(SubscribeRoutingStatsRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(RoutingServiceServer).SubscribeRoutingStats(m, &routingServiceSubscribeRoutingStatsServer{stream}) } type RoutingService_SubscribeRoutingStatsServer interface { Send(*RoutingContext) error grpc.ServerStream } type routingServiceSubscribeRoutingStatsServer struct { grpc.ServerStream } func (x *routingServiceSubscribeRoutingStatsServer) Send(m *RoutingContext) error { return x.ServerStream.SendMsg(m) } func _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(TestRouteRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(RoutingServiceServer).TestRoute(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.router.command.RoutingService/TestRoute", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RoutingServiceServer).TestRoute(ctx, req.(*TestRouteRequest)) } return interceptor(ctx, in, info, handler) } var _RoutingService_serviceDesc = grpc.ServiceDesc{ ServiceName: "v2ray.core.app.router.command.RoutingService", HandlerType: (*RoutingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "TestRoute", Handler: _RoutingService_TestRoute_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SubscribeRoutingStats", Handler: _RoutingService_SubscribeRoutingStats_Handler, ServerStreams: true, }, }, Metadata: "app/router/command/command.proto", } ================================================ FILE: app/router/command/command_test.go ================================================ package command_test import ( "context" "testing" "time" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" "v2ray.com/core/app/router" . "v2ray.com/core/app/router/command" "v2ray.com/core/app/stats" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/features/routing" "v2ray.com/core/testing/mocks" ) func TestServiceSubscribeRoutingStats(t *testing.T) { c := stats.NewChannel(&stats.ChannelConfig{ SubscriberLimit: 1, BufferSize: 0, Blocking: true, }) common.Must(c.Start()) defer c.Close() lis := bufconn.Listen(1024 * 1024) bufDialer := func(context.Context, string) (net.Conn, error) { return lis.Dial() } testCases := []*RoutingContext{ {InboundTag: "in", OutboundTag: "out"}, {TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, {TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, {SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, {Network: net.Network_UDP, OutboundGroupTags: []string{"outergroup", "innergroup"}, OutboundTag: "out"}, {Protocol: "bittorrent", OutboundTag: "blocked"}, {User: "example@v2fly.org", OutboundTag: "out"}, {SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, } errCh := make(chan error) nextPub := make(chan struct{}) // Server goroutine go func() { server := grpc.NewServer() RegisterRoutingServiceServer(server, NewRoutingServer(nil, c)) errCh <- server.Serve(lis) }() // Publisher goroutine go func() { publishTestCases := func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() for { // Wait until there's one subscriber in routing stats channel if len(c.Subscribers()) > 0 { break } if ctx.Err() != nil { return ctx.Err() } } for _, tc := range testCases { c.Publish(context.Background(), AsRoutingRoute(tc)) time.Sleep(time.Millisecond) } return nil } if err := publishTestCases(); err != nil { errCh <- err } // Wait for next round of publishing <-nextPub if err := publishTestCases(); err != nil { errCh <- err } }() // Client goroutine go func() { defer lis.Close() conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) if err != nil { errCh <- err return } defer conn.Close() client := NewRoutingServiceClient(conn) // Test retrieving all fields testRetrievingAllFields := func() error { streamCtx, streamClose := context.WithCancel(context.Background()) // Test the unsubscription of stream works well defer func() { streamClose() timeOutCtx, timeout := context.WithTimeout(context.Background(), time.Second) defer timeout() for { // Wait until there's no subscriber in routing stats channel if len(c.Subscribers()) == 0 { break } if timeOutCtx.Err() != nil { t.Error("unexpected subscribers not decreased in channel", timeOutCtx.Err()) } } }() stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{}) if err != nil { return err } for _, tc := range testCases { msg, err := stream.Recv() if err != nil { return err } if r := cmp.Diff(msg, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { t.Error(r) } } // Test that double subscription will fail errStream, err := client.SubscribeRoutingStats(context.Background(), &SubscribeRoutingStatsRequest{ FieldSelectors: []string{"ip", "port", "domain", "outbound"}, }) if err != nil { return err } if _, err := errStream.Recv(); err == nil { t.Error("unexpected successful subscription") } return nil } // Test retrieving only a subset of fields testRetrievingSubsetOfFields := func() error { streamCtx, streamClose := context.WithCancel(context.Background()) defer streamClose() stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{ FieldSelectors: []string{"ip", "port", "domain", "outbound"}, }) if err != nil { return err } // Send nextPub signal to start next round of publishing close(nextPub) for _, tc := range testCases { msg, err := stream.Recv() if err != nil { return err } stat := &RoutingContext{ // Only a subset of stats is retrieved SourceIPs: tc.SourceIPs, TargetIPs: tc.TargetIPs, SourcePort: tc.SourcePort, TargetPort: tc.TargetPort, TargetDomain: tc.TargetDomain, OutboundGroupTags: tc.OutboundGroupTags, OutboundTag: tc.OutboundTag, } if r := cmp.Diff(msg, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { t.Error(r) } } return nil } if err := testRetrievingAllFields(); err != nil { errCh <- err } if err := testRetrievingSubsetOfFields(); err != nil { errCh <- err } errCh <- nil // Client passed all tests successfully }() // Wait for goroutines to complete select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case err := <-errCh: if err != nil { t.Fatal(err) } } } func TestSerivceTestRoute(t *testing.T) { c := stats.NewChannel(&stats.ChannelConfig{ SubscriberLimit: 1, BufferSize: 16, Blocking: true, }) common.Must(c.Start()) defer c.Close() r := new(router.Router) mockCtl := gomock.NewController(t) defer mockCtl.Finish() common.Must(r.Init(&router.Config{ Rule: []*router.RoutingRule{ { InboundTag: []string{"in"}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { Protocol: []string{"bittorrent"}, TargetTag: &router.RoutingRule_Tag{Tag: "blocked"}, }, { PortList: &net.PortList{Range: []*net.PortRange{{From: 8080, To: 8080}}}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 9999, To: 9999}}}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { UserEmail: []string{"example@v2fly.org"}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, { Networks: []net.Network{net.Network_UDP, net.Network_TCP}, TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, }, }, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl))) lis := bufconn.Listen(1024 * 1024) bufDialer := func(context.Context, string) (net.Conn, error) { return lis.Dial() } errCh := make(chan error) // Server goroutine go func() { server := grpc.NewServer() RegisterRoutingServiceServer(server, NewRoutingServer(r, c)) errCh <- server.Serve(lis) }() // Client goroutine go func() { defer lis.Close() conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) if err != nil { errCh <- err } defer conn.Close() client := NewRoutingServiceClient(conn) testCases := []*RoutingContext{ {InboundTag: "in", OutboundTag: "out"}, {TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, {TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, {SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, {Network: net.Network_UDP, Protocol: "bittorrent", OutboundTag: "blocked"}, {User: "example@v2fly.org", OutboundTag: "out"}, {SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, } // Test simple TestRoute testSimple := func() error { for _, tc := range testCases { route, err := client.TestRoute(context.Background(), &TestRouteRequest{RoutingContext: tc}) if err != nil { return err } if r := cmp.Diff(route, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { t.Error(r) } } return nil } // Test TestRoute with special options testOptions := func() error { sub, err := c.Subscribe() if err != nil { return err } for _, tc := range testCases { route, err := client.TestRoute(context.Background(), &TestRouteRequest{ RoutingContext: tc, FieldSelectors: []string{"ip", "port", "domain", "outbound"}, PublishResult: true, }) if err != nil { return err } stat := &RoutingContext{ // Only a subset of stats is retrieved SourceIPs: tc.SourceIPs, TargetIPs: tc.TargetIPs, SourcePort: tc.SourcePort, TargetPort: tc.TargetPort, TargetDomain: tc.TargetDomain, OutboundGroupTags: tc.OutboundGroupTags, OutboundTag: tc.OutboundTag, } if r := cmp.Diff(route, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { t.Error(r) } select { // Check that routing result has been published to statistics channel case msg, received := <-sub: if route, ok := msg.(routing.Route); received && ok { if r := cmp.Diff(AsProtobufMessage(nil)(route), tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { t.Error(r) } } else { t.Error("unexpected failure in receiving published routing result for testcase", tc) } case <-time.After(100 * time.Millisecond): t.Error("unexpected failure in receiving published routing result", tc) } } return nil } if err := testSimple(); err != nil { errCh <- err } if err := testOptions(); err != nil { errCh <- err } errCh <- nil // Client passed all tests successfully }() // Wait for goroutines to complete select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case err := <-errCh: if err != nil { t.Fatal(err) } } } ================================================ FILE: app/router/command/config.go ================================================ package command import ( "strings" "v2ray.com/core/common/net" "v2ray.com/core/features/routing" ) // routingContext is an wrapper of protobuf RoutingContext as implementation of routing.Context and routing.Route. type routingContext struct { *RoutingContext } func (c routingContext) GetSourceIPs() []net.IP { return mapBytesToIPs(c.RoutingContext.GetSourceIPs()) } func (c routingContext) GetSourcePort() net.Port { return net.Port(c.RoutingContext.GetSourcePort()) } func (c routingContext) GetTargetIPs() []net.IP { return mapBytesToIPs(c.RoutingContext.GetTargetIPs()) } func (c routingContext) GetTargetPort() net.Port { return net.Port(c.RoutingContext.GetTargetPort()) } // AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context. func AsRoutingContext(r *RoutingContext) routing.Context { return routingContext{r} } // AsRoutingRoute converts a protobuf RoutingContext into an implementation of routing.Route. func AsRoutingRoute(r *RoutingContext) routing.Route { return routingContext{r} } var fieldMap = map[string]func(*RoutingContext, routing.Route){ "inbound": func(s *RoutingContext, r routing.Route) { s.InboundTag = r.GetInboundTag() }, "network": func(s *RoutingContext, r routing.Route) { s.Network = r.GetNetwork() }, "ip_source": func(s *RoutingContext, r routing.Route) { s.SourceIPs = mapIPsToBytes(r.GetSourceIPs()) }, "ip_target": func(s *RoutingContext, r routing.Route) { s.TargetIPs = mapIPsToBytes(r.GetTargetIPs()) }, "port_source": func(s *RoutingContext, r routing.Route) { s.SourcePort = uint32(r.GetSourcePort()) }, "port_target": func(s *RoutingContext, r routing.Route) { s.TargetPort = uint32(r.GetTargetPort()) }, "domain": func(s *RoutingContext, r routing.Route) { s.TargetDomain = r.GetTargetDomain() }, "protocol": func(s *RoutingContext, r routing.Route) { s.Protocol = r.GetProtocol() }, "user": func(s *RoutingContext, r routing.Route) { s.User = r.GetUser() }, "attributes": func(s *RoutingContext, r routing.Route) { s.Attributes = r.GetAttributes() }, "outbound_group": func(s *RoutingContext, r routing.Route) { s.OutboundGroupTags = r.GetOutboundGroupTags() }, "outbound": func(s *RoutingContext, r routing.Route) { s.OutboundTag = r.GetOutboundTag() }, } // AsProtobufMessage takes selectors of fields and returns a function to convert routing.Route to protobuf RoutingContext. func AsProtobufMessage(fieldSelectors []string) func(routing.Route) *RoutingContext { initializers := []func(*RoutingContext, routing.Route){} for field, init := range fieldMap { if len(fieldSelectors) == 0 { // If selectors not set, retrieve all fields initializers = append(initializers, init) continue } for _, selector := range fieldSelectors { if strings.HasPrefix(field, selector) { initializers = append(initializers, init) break } } } return func(ctx routing.Route) *RoutingContext { message := new(RoutingContext) for _, init := range initializers { init(message, ctx) } return message } } func mapBytesToIPs(bytes [][]byte) []net.IP { var ips []net.IP for _, rawIP := range bytes { ips = append(ips, net.IP(rawIP)) } return ips } func mapIPsToBytes(ips []net.IP) [][]byte { var bytes [][]byte for _, ip := range ips { bytes = append(bytes, []byte(ip)) } return bytes } ================================================ FILE: app/router/command/errors.generated.go ================================================ package command import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/router/condition.go ================================================ // +build !confonly package router import ( "strings" "go.starlark.net/starlark" "go.starlark.net/syntax" "v2ray.com/core/common/net" "v2ray.com/core/common/strmatcher" "v2ray.com/core/features/routing" ) type Condition interface { Apply(ctx routing.Context) bool } type ConditionChan []Condition func NewConditionChan() *ConditionChan { var condChan ConditionChan = make([]Condition, 0, 8) return &condChan } func (v *ConditionChan) Add(cond Condition) *ConditionChan { *v = append(*v, cond) return v } // Apply applies all conditions registered in this chan. func (v *ConditionChan) Apply(ctx routing.Context) bool { for _, cond := range *v { if !cond.Apply(ctx) { return false } } return true } func (v *ConditionChan) Len() int { return len(*v) } var matcherTypeMap = map[Domain_Type]strmatcher.Type{ Domain_Plain: strmatcher.Substr, Domain_Regex: strmatcher.Regex, Domain_Domain: strmatcher.Domain, Domain_Full: strmatcher.Full, } func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) { matcherType, f := matcherTypeMap[domain.Type] if !f { return nil, newError("unsupported domain type", domain.Type) } matcher, err := matcherType.New(domain.Value) if err != nil { return nil, newError("failed to create domain matcher").Base(err) } return matcher, nil } type DomainMatcher struct { matchers strmatcher.IndexMatcher } func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) { g := new(strmatcher.MatcherGroup) for _, d := range domains { m, err := domainToMatcher(d) if err != nil { return nil, err } g.Add(m) } return &DomainMatcher{ matchers: g, }, nil } func (m *DomainMatcher) ApplyDomain(domain string) bool { return len(m.matchers.Match(domain)) > 0 } // Apply implements Condition. func (m *DomainMatcher) Apply(ctx routing.Context) bool { domain := ctx.GetTargetDomain() if len(domain) == 0 { return false } return m.ApplyDomain(domain) } type MultiGeoIPMatcher struct { matchers []*GeoIPMatcher onSource bool } func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) { var matchers []*GeoIPMatcher for _, geoip := range geoips { matcher, err := globalGeoIPContainer.Add(geoip) if err != nil { return nil, err } matchers = append(matchers, matcher) } matcher := &MultiGeoIPMatcher{ matchers: matchers, onSource: onSource, } return matcher, nil } // Apply implements Condition. func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool { var ips []net.IP if m.onSource { ips = ctx.GetSourceIPs() } else { ips = ctx.GetTargetIPs() } for _, ip := range ips { for _, matcher := range m.matchers { if matcher.Match(ip) { return true } } } return false } type PortMatcher struct { port net.MemoryPortList onSource bool } // NewPortMatcher create a new port matcher that can match source or destination port func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher { return &PortMatcher{ port: net.PortListFromProto(list), onSource: onSource, } } // Apply implements Condition. func (v *PortMatcher) Apply(ctx routing.Context) bool { if v.onSource { return v.port.Contains(ctx.GetSourcePort()) } else { return v.port.Contains(ctx.GetTargetPort()) } } type NetworkMatcher struct { list [8]bool } func NewNetworkMatcher(network []net.Network) NetworkMatcher { var matcher NetworkMatcher for _, n := range network { matcher.list[int(n)] = true } return matcher } // Apply implements Condition. func (v NetworkMatcher) Apply(ctx routing.Context) bool { return v.list[int(ctx.GetNetwork())] } type UserMatcher struct { user []string } func NewUserMatcher(users []string) *UserMatcher { usersCopy := make([]string, 0, len(users)) for _, user := range users { if len(user) > 0 { usersCopy = append(usersCopy, user) } } return &UserMatcher{ user: usersCopy, } } // Apply implements Condition. func (v *UserMatcher) Apply(ctx routing.Context) bool { user := ctx.GetUser() if len(user) == 0 { return false } for _, u := range v.user { if u == user { return true } } return false } type InboundTagMatcher struct { tags []string } func NewInboundTagMatcher(tags []string) *InboundTagMatcher { tagsCopy := make([]string, 0, len(tags)) for _, tag := range tags { if len(tag) > 0 { tagsCopy = append(tagsCopy, tag) } } return &InboundTagMatcher{ tags: tagsCopy, } } // Apply implements Condition. func (v *InboundTagMatcher) Apply(ctx routing.Context) bool { tag := ctx.GetInboundTag() if len(tag) == 0 { return false } for _, t := range v.tags { if t == tag { return true } } return false } type ProtocolMatcher struct { protocols []string } func NewProtocolMatcher(protocols []string) *ProtocolMatcher { pCopy := make([]string, 0, len(protocols)) for _, p := range protocols { if len(p) > 0 { pCopy = append(pCopy, p) } } return &ProtocolMatcher{ protocols: pCopy, } } // Apply implements Condition. func (m *ProtocolMatcher) Apply(ctx routing.Context) bool { protocol := ctx.GetProtocol() if len(protocol) == 0 { return false } for _, p := range m.protocols { if strings.HasPrefix(protocol, p) { return true } } return false } type AttributeMatcher struct { program *starlark.Program } func NewAttributeMatcher(code string) (*AttributeMatcher, error) { starFile, err := syntax.Parse("attr.star", "satisfied=("+code+")", 0) if err != nil { return nil, newError("attr rule").Base(err) } p, err := starlark.FileProgram(starFile, func(name string) bool { return name == "attrs" }) if err != nil { return nil, err } return &AttributeMatcher{ program: p, }, nil } // Match implements attributes matching. func (m *AttributeMatcher) Match(attrs map[string]string) bool { attrsDict := new(starlark.Dict) for key, value := range attrs { attrsDict.SetKey(starlark.String(key), starlark.String(value)) } predefined := make(starlark.StringDict) predefined["attrs"] = attrsDict thread := &starlark.Thread{ Name: "matcher", } results, err := m.program.Init(thread, predefined) if err != nil { newError("attr matcher").Base(err).WriteToLog() } satisfied := results["satisfied"] return satisfied != nil && bool(satisfied.Truth()) } // Apply implements Condition. func (m *AttributeMatcher) Apply(ctx routing.Context) bool { attributes := ctx.GetAttributes() if attributes == nil { return false } return m.Match(attributes) } ================================================ FILE: app/router/condition_geoip.go ================================================ // +build !confonly package router import ( "encoding/binary" "sort" "v2ray.com/core/common/net" ) type ipv6 struct { a uint64 b uint64 } type GeoIPMatcher struct { countryCode string ip4 []uint32 prefix4 []uint8 ip6 []ipv6 prefix6 []uint8 } func normalize4(ip uint32, prefix uint8) uint32 { return (ip >> (32 - prefix)) << (32 - prefix) } func normalize6(ip ipv6, prefix uint8) ipv6 { if prefix <= 64 { ip.a = (ip.a >> (64 - prefix)) << (64 - prefix) ip.b = 0 } else { ip.b = (ip.b >> (128 - prefix)) << (128 - prefix) } return ip } func (m *GeoIPMatcher) Init(cidrs []*CIDR) error { ip4Count := 0 ip6Count := 0 for _, cidr := range cidrs { ip := cidr.Ip switch len(ip) { case 4: ip4Count++ case 16: ip6Count++ default: return newError("unexpect ip length: ", len(ip)) } } cidrList := CIDRList(cidrs) sort.Sort(&cidrList) m.ip4 = make([]uint32, 0, ip4Count) m.prefix4 = make([]uint8, 0, ip4Count) m.ip6 = make([]ipv6, 0, ip6Count) m.prefix6 = make([]uint8, 0, ip6Count) for _, cidr := range cidrs { ip := cidr.Ip prefix := uint8(cidr.Prefix) switch len(ip) { case 4: m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix)) m.prefix4 = append(m.prefix4, prefix) case 16: ip6 := ipv6{ a: binary.BigEndian.Uint64(ip[0:8]), b: binary.BigEndian.Uint64(ip[8:16]), } ip6 = normalize6(ip6, prefix) m.ip6 = append(m.ip6, ip6) m.prefix6 = append(m.prefix6, prefix) } } return nil } func (m *GeoIPMatcher) match4(ip uint32) bool { if len(m.ip4) == 0 { return false } if ip < m.ip4[0] { return false } size := uint32(len(m.ip4)) l := uint32(0) r := size for l < r { x := ((l + r) >> 1) if ip < m.ip4[x] { r = x continue } nip := normalize4(ip, m.prefix4[x]) if nip == m.ip4[x] { return true } l = x + 1 } return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1] } func less6(a ipv6, b ipv6) bool { return a.a < b.a || (a.a == b.a && a.b < b.b) } func (m *GeoIPMatcher) match6(ip ipv6) bool { if len(m.ip6) == 0 { return false } if less6(ip, m.ip6[0]) { return false } size := uint32(len(m.ip6)) l := uint32(0) r := size for l < r { x := (l + r) / 2 if less6(ip, m.ip6[x]) { r = x continue } if normalize6(ip, m.prefix6[x]) == m.ip6[x] { return true } l = x + 1 } return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1] } // Match returns true if the given ip is included by the GeoIP. func (m *GeoIPMatcher) Match(ip net.IP) bool { switch len(ip) { case 4: return m.match4(binary.BigEndian.Uint32(ip)) case 16: return m.match6(ipv6{ a: binary.BigEndian.Uint64(ip[0:8]), b: binary.BigEndian.Uint64(ip[8:16]), }) default: return false } } // GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code. type GeoIPMatcherContainer struct { matchers []*GeoIPMatcher } // Add adds a new GeoIP set into the container. // If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one. func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) { if len(geoip.CountryCode) > 0 { for _, m := range c.matchers { if m.countryCode == geoip.CountryCode { return m, nil } } } m := &GeoIPMatcher{ countryCode: geoip.CountryCode, } if err := m.Init(geoip.Cidr); err != nil { return nil, err } if len(geoip.CountryCode) > 0 { c.matchers = append(c.matchers, m) } return m, nil } var ( globalGeoIPContainer GeoIPMatcherContainer ) ================================================ FILE: app/router/condition_geoip_test.go ================================================ package router_test import ( "os" "path/filepath" "testing" proto "github.com/golang/protobuf/proto" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/platform/filesystem" ) func init() { wd, err := os.Getwd() common.Must(err) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) } if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) } } func TestGeoIPMatcherContainer(t *testing.T) { container := &router.GeoIPMatcherContainer{} m1, err := container.Add(&router.GeoIP{ CountryCode: "CN", }) common.Must(err) m2, err := container.Add(&router.GeoIP{ CountryCode: "US", }) common.Must(err) m3, err := container.Add(&router.GeoIP{ CountryCode: "CN", }) common.Must(err) if m1 != m3 { t.Error("expect same matcher for same geoip, but not") } if m1 == m2 { t.Error("expect different matcher for different geoip, but actually same") } } func TestGeoIPMatcher(t *testing.T) { cidrList := router.CIDRList{ {Ip: []byte{0, 0, 0, 0}, Prefix: 8}, {Ip: []byte{10, 0, 0, 0}, Prefix: 8}, {Ip: []byte{100, 64, 0, 0}, Prefix: 10}, {Ip: []byte{127, 0, 0, 0}, Prefix: 8}, {Ip: []byte{169, 254, 0, 0}, Prefix: 16}, {Ip: []byte{172, 16, 0, 0}, Prefix: 12}, {Ip: []byte{192, 0, 0, 0}, Prefix: 24}, {Ip: []byte{192, 0, 2, 0}, Prefix: 24}, {Ip: []byte{192, 168, 0, 0}, Prefix: 16}, {Ip: []byte{192, 18, 0, 0}, Prefix: 15}, {Ip: []byte{198, 51, 100, 0}, Prefix: 24}, {Ip: []byte{203, 0, 113, 0}, Prefix: 24}, {Ip: []byte{8, 8, 8, 8}, Prefix: 32}, {Ip: []byte{91, 108, 4, 0}, Prefix: 16}, } matcher := &router.GeoIPMatcher{} common.Must(matcher.Init(cidrList)) testCases := []struct { Input string Output bool }{ { Input: "192.168.1.1", Output: true, }, { Input: "192.0.0.0", Output: true, }, { Input: "192.0.1.0", Output: false, }, { Input: "0.1.0.0", Output: true, }, { Input: "1.0.0.1", Output: false, }, { Input: "8.8.8.7", Output: false, }, { Input: "8.8.8.8", Output: true, }, { Input: "2001:cdba::3257:9652", Output: false, }, { Input: "91.108.255.254", Output: true, }, } for _, testCase := range testCases { ip := net.ParseAddress(testCase.Input).IP() actual := matcher.Match(ip) if actual != testCase.Output { t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual) } } } func TestGeoIPMatcher4CN(t *testing.T) { ips, err := loadGeoIP("CN") common.Must(err) matcher := &router.GeoIPMatcher{} common.Must(matcher.Init(ips)) if matcher.Match([]byte{8, 8, 8, 8}) { t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does") } } func TestGeoIPMatcher6US(t *testing.T) { ips, err := loadGeoIP("US") common.Must(err) matcher := &router.GeoIPMatcher{} common.Must(matcher.Init(ips)) if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) { t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not") } } func loadGeoIP(country string) ([]*router.CIDR, error) { geoipBytes, err := filesystem.ReadAsset("geoip.dat") if err != nil { return nil, err } var geoipList router.GeoIPList if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { return nil, err } for _, geoip := range geoipList.Entry { if geoip.CountryCode == country { return geoip.Cidr, nil } } panic("country not found: " + country) } func BenchmarkGeoIPMatcher4CN(b *testing.B) { ips, err := loadGeoIP("CN") common.Must(err) matcher := &router.GeoIPMatcher{} common.Must(matcher.Init(ips)) b.ResetTimer() for i := 0; i < b.N; i++ { _ = matcher.Match([]byte{8, 8, 8, 8}) } } func BenchmarkGeoIPMatcher6US(b *testing.B) { ips, err := loadGeoIP("US") common.Must(err) matcher := &router.GeoIPMatcher{} common.Must(matcher.Init(ips)) b.ResetTimer() for i := 0; i < b.N; i++ { _ = matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) } } ================================================ FILE: app/router/condition_test.go ================================================ package router_test import ( "os" "path/filepath" "strconv" "testing" proto "github.com/golang/protobuf/proto" . "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/platform/filesystem" "v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol/http" "v2ray.com/core/common/session" "v2ray.com/core/features/routing" routing_session "v2ray.com/core/features/routing/session" ) func init() { wd, err := os.Getwd() common.Must(err) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) } if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) } } func withBackground() routing.Context { return &routing_session.Context{} } func withOutbound(outbound *session.Outbound) routing.Context { return &routing_session.Context{Outbound: outbound} } func withInbound(inbound *session.Inbound) routing.Context { return &routing_session.Context{Inbound: inbound} } func withContent(content *session.Content) routing.Context { return &routing_session.Context{Content: content} } func TestRoutingRule(t *testing.T) { type ruleTest struct { input routing.Context output bool } cases := []struct { rule *RoutingRule test []ruleTest }{ { rule: &RoutingRule{ Domain: []*Domain{ { Value: "v2ray.com", Type: Domain_Plain, }, { Value: "google.com", Type: Domain_Domain, }, { Value: "^facebook\\.com$", Type: Domain_Regex, }, }, }, test: []ruleTest{ { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.v2ray.com.www"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.co"), 80)}), output: false, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.google.com"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("facebook.com"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.facebook.com"), 80)}), output: false, }, { input: withBackground(), output: false, }, }, }, { rule: &RoutingRule{ Cidr: []*CIDR{ { Ip: []byte{8, 8, 8, 8}, Prefix: 32, }, { Ip: []byte{8, 8, 8, 8}, Prefix: 32, }, { Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128, }, }, }, test: []ruleTest{ { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}), output: false, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}), output: true, }, { input: withBackground(), output: false, }, }, }, { rule: &RoutingRule{ Geoip: []*GeoIP{ { Cidr: []*CIDR{ { Ip: []byte{8, 8, 8, 8}, Prefix: 32, }, { Ip: []byte{8, 8, 8, 8}, Prefix: 32, }, { Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128, }, }, }, }, }, test: []ruleTest{ { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}), output: false, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}), output: true, }, { input: withBackground(), output: false, }, }, }, { rule: &RoutingRule{ SourceCidr: []*CIDR{ { Ip: []byte{192, 168, 0, 0}, Prefix: 16, }, }, }, test: []ruleTest{ { input: withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("192.168.0.1"), 80)}), output: true, }, { input: withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("10.0.0.1"), 80)}), output: false, }, }, }, { rule: &RoutingRule{ UserEmail: []string{ "admin@v2ray.com", }, }, test: []ruleTest{ { input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "admin@v2ray.com"}}), output: true, }, { input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "love@v2ray.com"}}), output: false, }, { input: withBackground(), output: false, }, }, }, { rule: &RoutingRule{ Protocol: []string{"http"}, }, test: []ruleTest{ { input: withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}), output: true, }, }, }, { rule: &RoutingRule{ InboundTag: []string{"test", "test1"}, }, test: []ruleTest{ { input: withInbound(&session.Inbound{Tag: "test"}), output: true, }, { input: withInbound(&session.Inbound{Tag: "test2"}), output: false, }, }, }, { rule: &RoutingRule{ PortList: &net.PortList{ Range: []*net.PortRange{ {From: 443, To: 443}, {From: 1000, To: 1100}, }, }, }, test: []ruleTest{ { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}), output: true, }, { input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}), output: false, }, }, }, { rule: &RoutingRule{ SourcePortList: &net.PortList{ Range: []*net.PortRange{ {From: 123, To: 123}, {From: 9993, To: 9999}, }, }, }, test: []ruleTest{ { input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}), output: true, }, { input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}), output: true, }, { input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}), output: true, }, { input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}), output: false, }, }, }, { rule: &RoutingRule{ Protocol: []string{"http"}, Attributes: "attrs[':path'].startswith('/test')", }, test: []ruleTest{ { input: withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]string{":path": "/test/1"}}), output: true, }, }, }, } for _, test := range cases { cond, err := test.rule.BuildCondition() common.Must(err) for _, subtest := range test.test { actual := cond.Apply(subtest.input) if actual != subtest.output { t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual) } } } } func loadGeoSite(country string) ([]*Domain, error) { geositeBytes, err := filesystem.ReadAsset("geosite.dat") if err != nil { return nil, err } var geositeList GeoSiteList if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { return nil, err } for _, site := range geositeList.Entry { if site.CountryCode == country { return site.Domain, nil } } return nil, errors.New("country not found: " + country) } func TestChinaSites(t *testing.T) { domains, err := loadGeoSite("CN") common.Must(err) matcher, err := NewDomainMatcher(domains) common.Must(err) type TestCase struct { Domain string Output bool } testCases := []TestCase{ { Domain: "163.com", Output: true, }, { Domain: "163.com", Output: true, }, { Domain: "164.com", Output: false, }, { Domain: "164.com", Output: false, }, } for i := 0; i < 1024; i++ { testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false}) } for _, testCase := range testCases { r := matcher.ApplyDomain(testCase.Domain) if r != testCase.Output { t.Error("expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r) } } } func BenchmarkMultiGeoIPMatcher(b *testing.B) { var geoips []*GeoIP { ips, err := loadGeoIP("CN") common.Must(err) geoips = append(geoips, &GeoIP{ CountryCode: "CN", Cidr: ips, }) } { ips, err := loadGeoIP("JP") common.Must(err) geoips = append(geoips, &GeoIP{ CountryCode: "JP", Cidr: ips, }) } { ips, err := loadGeoIP("CA") common.Must(err) geoips = append(geoips, &GeoIP{ CountryCode: "CA", Cidr: ips, }) } { ips, err := loadGeoIP("US") common.Must(err) geoips = append(geoips, &GeoIP{ CountryCode: "US", Cidr: ips, }) } matcher, err := NewMultiGeoIPMatcher(geoips, false) common.Must(err) ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}) b.ResetTimer() for i := 0; i < b.N; i++ { _ = matcher.Apply(ctx) } } ================================================ FILE: app/router/config.go ================================================ // +build !confonly package router import ( "v2ray.com/core/common/net" "v2ray.com/core/features/outbound" "v2ray.com/core/features/routing" ) // CIDRList is an alias of []*CIDR to provide sort.Interface. type CIDRList []*CIDR // Len implements sort.Interface. func (l *CIDRList) Len() int { return len(*l) } // Less implements sort.Interface. func (l *CIDRList) Less(i int, j int) bool { ci := (*l)[i] cj := (*l)[j] if len(ci.Ip) < len(cj.Ip) { return true } if len(ci.Ip) > len(cj.Ip) { return false } for k := 0; k < len(ci.Ip); k++ { if ci.Ip[k] < cj.Ip[k] { return true } if ci.Ip[k] > cj.Ip[k] { return false } } return ci.Prefix < cj.Prefix } // Swap implements sort.Interface. func (l *CIDRList) Swap(i int, j int) { (*l)[i], (*l)[j] = (*l)[j], (*l)[i] } type Rule struct { Tag string Balancer *Balancer Condition Condition } func (r *Rule) GetTag() (string, error) { if r.Balancer != nil { return r.Balancer.PickOutbound() } return r.Tag, nil } // Apply checks rule matching of current routing context. func (r *Rule) Apply(ctx routing.Context) bool { return r.Condition.Apply(ctx) } func (rr *RoutingRule) BuildCondition() (Condition, error) { conds := NewConditionChan() if len(rr.Domain) > 0 { matcher, err := NewDomainMatcher(rr.Domain) if err != nil { return nil, newError("failed to build domain condition").Base(err) } conds.Add(matcher) } if len(rr.UserEmail) > 0 { conds.Add(NewUserMatcher(rr.UserEmail)) } if len(rr.InboundTag) > 0 { conds.Add(NewInboundTagMatcher(rr.InboundTag)) } if rr.PortList != nil { conds.Add(NewPortMatcher(rr.PortList, false)) } else if rr.PortRange != nil { conds.Add(NewPortMatcher(&net.PortList{Range: []*net.PortRange{rr.PortRange}}, false)) } if rr.SourcePortList != nil { conds.Add(NewPortMatcher(rr.SourcePortList, true)) } if len(rr.Networks) > 0 { conds.Add(NewNetworkMatcher(rr.Networks)) } else if rr.NetworkList != nil { conds.Add(NewNetworkMatcher(rr.NetworkList.Network)) } if len(rr.Geoip) > 0 { cond, err := NewMultiGeoIPMatcher(rr.Geoip, false) if err != nil { return nil, err } conds.Add(cond) } else if len(rr.Cidr) > 0 { cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.Cidr}}, false) if err != nil { return nil, err } conds.Add(cond) } if len(rr.SourceGeoip) > 0 { cond, err := NewMultiGeoIPMatcher(rr.SourceGeoip, true) if err != nil { return nil, err } conds.Add(cond) } else if len(rr.SourceCidr) > 0 { cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.SourceCidr}}, true) if err != nil { return nil, err } conds.Add(cond) } if len(rr.Protocol) > 0 { conds.Add(NewProtocolMatcher(rr.Protocol)) } if len(rr.Attributes) > 0 { cond, err := NewAttributeMatcher(rr.Attributes) if err != nil { return nil, err } conds.Add(cond) } if conds.Len() == 0 { return nil, newError("this rule has no effective fields").AtWarning() } return conds, nil } func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) { return &Balancer{ selectors: br.OutboundSelector, strategy: &RandomStrategy{}, ohm: ohm, }, nil } ================================================ FILE: app/router/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/router/config.proto package router import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Type of domain value. type Domain_Type int32 const ( // The value is used as is. Domain_Plain Domain_Type = 0 // The value is used as a regular expression. Domain_Regex Domain_Type = 1 // The value is a root domain. Domain_Domain Domain_Type = 2 // The value is a domain. Domain_Full Domain_Type = 3 ) // Enum value maps for Domain_Type. var ( Domain_Type_name = map[int32]string{ 0: "Plain", 1: "Regex", 2: "Domain", 3: "Full", } Domain_Type_value = map[string]int32{ "Plain": 0, "Regex": 1, "Domain": 2, "Full": 3, } ) func (x Domain_Type) Enum() *Domain_Type { p := new(Domain_Type) *p = x return p } func (x Domain_Type) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Domain_Type) Descriptor() protoreflect.EnumDescriptor { return file_app_router_config_proto_enumTypes[0].Descriptor() } func (Domain_Type) Type() protoreflect.EnumType { return &file_app_router_config_proto_enumTypes[0] } func (x Domain_Type) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Domain_Type.Descriptor instead. func (Domain_Type) EnumDescriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{0, 0} } type Config_DomainStrategy int32 const ( // Use domain as is. Config_AsIs Config_DomainStrategy = 0 // Always resolve IP for domains. Config_UseIp Config_DomainStrategy = 1 // Resolve to IP if the domain doesn't match any rules. Config_IpIfNonMatch Config_DomainStrategy = 2 // Resolve to IP if any rule requires IP matching. Config_IpOnDemand Config_DomainStrategy = 3 ) // Enum value maps for Config_DomainStrategy. var ( Config_DomainStrategy_name = map[int32]string{ 0: "AsIs", 1: "UseIp", 2: "IpIfNonMatch", 3: "IpOnDemand", } Config_DomainStrategy_value = map[string]int32{ "AsIs": 0, "UseIp": 1, "IpIfNonMatch": 2, "IpOnDemand": 3, } ) func (x Config_DomainStrategy) Enum() *Config_DomainStrategy { p := new(Config_DomainStrategy) *p = x return p } func (x Config_DomainStrategy) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor { return file_app_router_config_proto_enumTypes[1].Descriptor() } func (Config_DomainStrategy) Type() protoreflect.EnumType { return &file_app_router_config_proto_enumTypes[1] } func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Config_DomainStrategy.Descriptor instead. func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{8, 0} } // Domain for routing decision. type Domain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Domain matching type. Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.app.router.Domain_Type" json:"type,omitempty"` // Domain value. Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // Attributes of this domain. May be used for filtering. Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"` } func (x *Domain) Reset() { *x = Domain{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Domain) String() string { return protoimpl.X.MessageStringOf(x) } func (*Domain) ProtoMessage() {} func (x *Domain) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Domain.ProtoReflect.Descriptor instead. func (*Domain) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{0} } func (x *Domain) GetType() Domain_Type { if x != nil { return x.Type } return Domain_Plain } func (x *Domain) GetValue() string { if x != nil { return x.Value } return "" } func (x *Domain) GetAttribute() []*Domain_Attribute { if x != nil { return x.Attribute } return nil } // IP for routing decision, in CIDR form. type CIDR struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // IP address, should be either 4 or 16 bytes. Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` // Number of leading ones in the network mask. Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"` } func (x *CIDR) Reset() { *x = CIDR{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CIDR) String() string { return protoimpl.X.MessageStringOf(x) } func (*CIDR) ProtoMessage() {} func (x *CIDR) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CIDR.ProtoReflect.Descriptor instead. func (*CIDR) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{1} } func (x *CIDR) GetIp() []byte { if x != nil { return x.Ip } return nil } func (x *CIDR) GetPrefix() uint32 { if x != nil { return x.Prefix } return 0 } type GeoIP struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"` } func (x *GeoIP) Reset() { *x = GeoIP{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GeoIP) String() string { return protoimpl.X.MessageStringOf(x) } func (*GeoIP) ProtoMessage() {} func (x *GeoIP) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GeoIP.ProtoReflect.Descriptor instead. func (*GeoIP) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{2} } func (x *GeoIP) GetCountryCode() string { if x != nil { return x.CountryCode } return "" } func (x *GeoIP) GetCidr() []*CIDR { if x != nil { return x.Cidr } return nil } type GeoIPList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` } func (x *GeoIPList) Reset() { *x = GeoIPList{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GeoIPList) String() string { return protoimpl.X.MessageStringOf(x) } func (*GeoIPList) ProtoMessage() {} func (x *GeoIPList) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead. func (*GeoIPList) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{3} } func (x *GeoIPList) GetEntry() []*GeoIP { if x != nil { return x.Entry } return nil } type GeoSite struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"` } func (x *GeoSite) Reset() { *x = GeoSite{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GeoSite) String() string { return protoimpl.X.MessageStringOf(x) } func (*GeoSite) ProtoMessage() {} func (x *GeoSite) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GeoSite.ProtoReflect.Descriptor instead. func (*GeoSite) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{4} } func (x *GeoSite) GetCountryCode() string { if x != nil { return x.CountryCode } return "" } func (x *GeoSite) GetDomain() []*Domain { if x != nil { return x.Domain } return nil } type GeoSiteList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` } func (x *GeoSiteList) Reset() { *x = GeoSiteList{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GeoSiteList) String() string { return protoimpl.X.MessageStringOf(x) } func (*GeoSiteList) ProtoMessage() {} func (x *GeoSiteList) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead. func (*GeoSiteList) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{5} } func (x *GeoSiteList) GetEntry() []*GeoSite { if x != nil { return x.Entry } return nil } type RoutingRule struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to TargetTag: // *RoutingRule_Tag // *RoutingRule_BalancingTag TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"` // List of domains for target domain matching. Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"` // List of CIDRs for target IP address matching. // Deprecated. Use geoip below. // // Deprecated: Do not use. Cidr []*CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"` // List of GeoIPs for target IP address matching. If this entry exists, the // cidr above will have no effect. GeoIP fields with the same country code are // supposed to contain exactly same content. They will be merged during // runtime. For customized GeoIPs, please leave country code empty. Geoip []*GeoIP `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"` // A range of port [from, to]. If the destination port is in this range, this // rule takes effect. Deprecated. Use port_list. // // Deprecated: Do not use. PortRange *net.PortRange `protobuf:"bytes,4,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"` // List of ports. PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"` // List of networks. Deprecated. Use networks. // // Deprecated: Do not use. NetworkList *net.NetworkList `protobuf:"bytes,5,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"` // List of networks for matching. Networks []net.Network `protobuf:"varint,13,rep,packed,name=networks,proto3,enum=v2ray.core.common.net.Network" json:"networks,omitempty"` // List of CIDRs for source IP address matching. // // Deprecated: Do not use. SourceCidr []*CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"` // List of GeoIPs for source IP address matching. If this entry exists, the // source_cidr above will have no effect. SourceGeoip []*GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"` // List of ports for source port matching. SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"` UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"` InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"` Attributes string `protobuf:"bytes,15,opt,name=attributes,proto3" json:"attributes,omitempty"` } func (x *RoutingRule) Reset() { *x = RoutingRule{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RoutingRule) String() string { return protoimpl.X.MessageStringOf(x) } func (*RoutingRule) ProtoMessage() {} func (x *RoutingRule) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RoutingRule.ProtoReflect.Descriptor instead. func (*RoutingRule) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{6} } func (m *RoutingRule) GetTargetTag() isRoutingRule_TargetTag { if m != nil { return m.TargetTag } return nil } func (x *RoutingRule) GetTag() string { if x, ok := x.GetTargetTag().(*RoutingRule_Tag); ok { return x.Tag } return "" } func (x *RoutingRule) GetBalancingTag() string { if x, ok := x.GetTargetTag().(*RoutingRule_BalancingTag); ok { return x.BalancingTag } return "" } func (x *RoutingRule) GetDomain() []*Domain { if x != nil { return x.Domain } return nil } // Deprecated: Do not use. func (x *RoutingRule) GetCidr() []*CIDR { if x != nil { return x.Cidr } return nil } func (x *RoutingRule) GetGeoip() []*GeoIP { if x != nil { return x.Geoip } return nil } // Deprecated: Do not use. func (x *RoutingRule) GetPortRange() *net.PortRange { if x != nil { return x.PortRange } return nil } func (x *RoutingRule) GetPortList() *net.PortList { if x != nil { return x.PortList } return nil } // Deprecated: Do not use. func (x *RoutingRule) GetNetworkList() *net.NetworkList { if x != nil { return x.NetworkList } return nil } func (x *RoutingRule) GetNetworks() []net.Network { if x != nil { return x.Networks } return nil } // Deprecated: Do not use. func (x *RoutingRule) GetSourceCidr() []*CIDR { if x != nil { return x.SourceCidr } return nil } func (x *RoutingRule) GetSourceGeoip() []*GeoIP { if x != nil { return x.SourceGeoip } return nil } func (x *RoutingRule) GetSourcePortList() *net.PortList { if x != nil { return x.SourcePortList } return nil } func (x *RoutingRule) GetUserEmail() []string { if x != nil { return x.UserEmail } return nil } func (x *RoutingRule) GetInboundTag() []string { if x != nil { return x.InboundTag } return nil } func (x *RoutingRule) GetProtocol() []string { if x != nil { return x.Protocol } return nil } func (x *RoutingRule) GetAttributes() string { if x != nil { return x.Attributes } return "" } type isRoutingRule_TargetTag interface { isRoutingRule_TargetTag() } type RoutingRule_Tag struct { // Tag of outbound that this rule is pointing to. Tag string `protobuf:"bytes,1,opt,name=tag,proto3,oneof"` } type RoutingRule_BalancingTag struct { // Tag of routing balancer. BalancingTag string `protobuf:"bytes,12,opt,name=balancing_tag,json=balancingTag,proto3,oneof"` } func (*RoutingRule_Tag) isRoutingRule_TargetTag() {} func (*RoutingRule_BalancingTag) isRoutingRule_TargetTag() {} type BalancingRule struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` OutboundSelector []string `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"` } func (x *BalancingRule) Reset() { *x = BalancingRule{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *BalancingRule) String() string { return protoimpl.X.MessageStringOf(x) } func (*BalancingRule) ProtoMessage() {} func (x *BalancingRule) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead. func (*BalancingRule) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{7} } func (x *BalancingRule) GetTag() string { if x != nil { return x.Tag } return "" } func (x *BalancingRule) GetOutboundSelector() []string { if x != nil { return x.OutboundSelector } return nil } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields DomainStrategy Config_DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=v2ray.core.app.router.Config_DomainStrategy" json:"domain_strategy,omitempty"` Rule []*RoutingRule `protobuf:"bytes,2,rep,name=rule,proto3" json:"rule,omitempty"` BalancingRule []*BalancingRule `protobuf:"bytes,3,rep,name=balancing_rule,json=balancingRule,proto3" json:"balancing_rule,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{8} } func (x *Config) GetDomainStrategy() Config_DomainStrategy { if x != nil { return x.DomainStrategy } return Config_AsIs } func (x *Config) GetRule() []*RoutingRule { if x != nil { return x.Rule } return nil } func (x *Config) GetBalancingRule() []*BalancingRule { if x != nil { return x.BalancingRule } return nil } type Domain_Attribute struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Types that are assignable to TypedValue: // *Domain_Attribute_BoolValue // *Domain_Attribute_IntValue TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"` } func (x *Domain_Attribute) Reset() { *x = Domain_Attribute{} if protoimpl.UnsafeEnabled { mi := &file_app_router_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Domain_Attribute) String() string { return protoimpl.X.MessageStringOf(x) } func (*Domain_Attribute) ProtoMessage() {} func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { mi := &file_app_router_config_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead. func (*Domain_Attribute) Descriptor() ([]byte, []int) { return file_app_router_config_proto_rawDescGZIP(), []int{0, 0} } func (x *Domain_Attribute) GetKey() string { if x != nil { return x.Key } return "" } func (m *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue { if m != nil { return m.TypedValue } return nil } func (x *Domain_Attribute) GetBoolValue() bool { if x, ok := x.GetTypedValue().(*Domain_Attribute_BoolValue); ok { return x.BoolValue } return false } func (x *Domain_Attribute) GetIntValue() int64 { if x, ok := x.GetTypedValue().(*Domain_Attribute_IntValue); ok { return x.IntValue } return 0 } type isDomain_Attribute_TypedValue interface { isDomain_Attribute_TypedValue() } type Domain_Attribute_BoolValue struct { BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` } type Domain_Attribute_IntValue struct { IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` } func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {} func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {} var File_app_router_config_proto protoreflect.FileDescriptor var file_app_router_config_proto_rawDesc = []byte{ 0x0a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x1a, 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x32, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x03, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x5b, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x22, 0x3f, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x63, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x43, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xca, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x12, 0x35, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, 0x32, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x43, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x40, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x69, 0x64, 0x72, 0x12, 0x3f, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xad, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_router_config_proto_rawDescOnce sync.Once file_app_router_config_proto_rawDescData = file_app_router_config_proto_rawDesc ) func file_app_router_config_proto_rawDescGZIP() []byte { file_app_router_config_proto_rawDescOnce.Do(func() { file_app_router_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_router_config_proto_rawDescData) }) return file_app_router_config_proto_rawDescData } var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_app_router_config_proto_goTypes = []interface{}{ (Domain_Type)(0), // 0: v2ray.core.app.router.Domain.Type (Config_DomainStrategy)(0), // 1: v2ray.core.app.router.Config.DomainStrategy (*Domain)(nil), // 2: v2ray.core.app.router.Domain (*CIDR)(nil), // 3: v2ray.core.app.router.CIDR (*GeoIP)(nil), // 4: v2ray.core.app.router.GeoIP (*GeoIPList)(nil), // 5: v2ray.core.app.router.GeoIPList (*GeoSite)(nil), // 6: v2ray.core.app.router.GeoSite (*GeoSiteList)(nil), // 7: v2ray.core.app.router.GeoSiteList (*RoutingRule)(nil), // 8: v2ray.core.app.router.RoutingRule (*BalancingRule)(nil), // 9: v2ray.core.app.router.BalancingRule (*Config)(nil), // 10: v2ray.core.app.router.Config (*Domain_Attribute)(nil), // 11: v2ray.core.app.router.Domain.Attribute (*net.PortRange)(nil), // 12: v2ray.core.common.net.PortRange (*net.PortList)(nil), // 13: v2ray.core.common.net.PortList (*net.NetworkList)(nil), // 14: v2ray.core.common.net.NetworkList (net.Network)(0), // 15: v2ray.core.common.net.Network } var file_app_router_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.app.router.Domain.type:type_name -> v2ray.core.app.router.Domain.Type 11, // 1: v2ray.core.app.router.Domain.attribute:type_name -> v2ray.core.app.router.Domain.Attribute 3, // 2: v2ray.core.app.router.GeoIP.cidr:type_name -> v2ray.core.app.router.CIDR 4, // 3: v2ray.core.app.router.GeoIPList.entry:type_name -> v2ray.core.app.router.GeoIP 2, // 4: v2ray.core.app.router.GeoSite.domain:type_name -> v2ray.core.app.router.Domain 6, // 5: v2ray.core.app.router.GeoSiteList.entry:type_name -> v2ray.core.app.router.GeoSite 2, // 6: v2ray.core.app.router.RoutingRule.domain:type_name -> v2ray.core.app.router.Domain 3, // 7: v2ray.core.app.router.RoutingRule.cidr:type_name -> v2ray.core.app.router.CIDR 4, // 8: v2ray.core.app.router.RoutingRule.geoip:type_name -> v2ray.core.app.router.GeoIP 12, // 9: v2ray.core.app.router.RoutingRule.port_range:type_name -> v2ray.core.common.net.PortRange 13, // 10: v2ray.core.app.router.RoutingRule.port_list:type_name -> v2ray.core.common.net.PortList 14, // 11: v2ray.core.app.router.RoutingRule.network_list:type_name -> v2ray.core.common.net.NetworkList 15, // 12: v2ray.core.app.router.RoutingRule.networks:type_name -> v2ray.core.common.net.Network 3, // 13: v2ray.core.app.router.RoutingRule.source_cidr:type_name -> v2ray.core.app.router.CIDR 4, // 14: v2ray.core.app.router.RoutingRule.source_geoip:type_name -> v2ray.core.app.router.GeoIP 13, // 15: v2ray.core.app.router.RoutingRule.source_port_list:type_name -> v2ray.core.common.net.PortList 1, // 16: v2ray.core.app.router.Config.domain_strategy:type_name -> v2ray.core.app.router.Config.DomainStrategy 8, // 17: v2ray.core.app.router.Config.rule:type_name -> v2ray.core.app.router.RoutingRule 9, // 18: v2ray.core.app.router.Config.balancing_rule:type_name -> v2ray.core.app.router.BalancingRule 19, // [19:19] is the sub-list for method output_type 19, // [19:19] is the sub-list for method input_type 19, // [19:19] is the sub-list for extension type_name 19, // [19:19] is the sub-list for extension extendee 0, // [0:19] is the sub-list for field type_name } func init() { file_app_router_config_proto_init() } func file_app_router_config_proto_init() { if File_app_router_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_router_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Domain); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CIDR); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GeoIP); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GeoIPList); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GeoSite); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GeoSiteList); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RoutingRule); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BalancingRule); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_router_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Domain_Attribute); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_app_router_config_proto_msgTypes[6].OneofWrappers = []interface{}{ (*RoutingRule_Tag)(nil), (*RoutingRule_BalancingTag)(nil), } file_app_router_config_proto_msgTypes[9].OneofWrappers = []interface{}{ (*Domain_Attribute_BoolValue)(nil), (*Domain_Attribute_IntValue)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_config_proto_rawDesc, NumEnums: 2, NumMessages: 10, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_router_config_proto_goTypes, DependencyIndexes: file_app_router_config_proto_depIdxs, EnumInfos: file_app_router_config_proto_enumTypes, MessageInfos: file_app_router_config_proto_msgTypes, }.Build() File_app_router_config_proto = out.File file_app_router_config_proto_rawDesc = nil file_app_router_config_proto_goTypes = nil file_app_router_config_proto_depIdxs = nil } ================================================ FILE: app/router/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.router; option csharp_namespace = "V2Ray.Core.App.Router"; option go_package = "v2ray.com/core/app/router"; option java_package = "com.v2ray.core.app.router"; option java_multiple_files = true; import "common/net/port.proto"; import "common/net/network.proto"; // Domain for routing decision. message Domain { // Type of domain value. enum Type { // The value is used as is. Plain = 0; // The value is used as a regular expression. Regex = 1; // The value is a root domain. Domain = 2; // The value is a domain. Full = 3; } // Domain matching type. Type type = 1; // Domain value. string value = 2; message Attribute { string key = 1; oneof typed_value { bool bool_value = 2; int64 int_value = 3; } } // Attributes of this domain. May be used for filtering. repeated Attribute attribute = 3; } // IP for routing decision, in CIDR form. message CIDR { // IP address, should be either 4 or 16 bytes. bytes ip = 1; // Number of leading ones in the network mask. uint32 prefix = 2; } message GeoIP { string country_code = 1; repeated CIDR cidr = 2; } message GeoIPList { repeated GeoIP entry = 1; } message GeoSite { string country_code = 1; repeated Domain domain = 2; } message GeoSiteList { repeated GeoSite entry = 1; } message RoutingRule { oneof target_tag { // Tag of outbound that this rule is pointing to. string tag = 1; // Tag of routing balancer. string balancing_tag = 12; } // List of domains for target domain matching. repeated Domain domain = 2; // List of CIDRs for target IP address matching. // Deprecated. Use geoip below. repeated CIDR cidr = 3 [deprecated = true]; // List of GeoIPs for target IP address matching. If this entry exists, the // cidr above will have no effect. GeoIP fields with the same country code are // supposed to contain exactly same content. They will be merged during // runtime. For customized GeoIPs, please leave country code empty. repeated GeoIP geoip = 10; // A range of port [from, to]. If the destination port is in this range, this // rule takes effect. Deprecated. Use port_list. v2ray.core.common.net.PortRange port_range = 4 [deprecated = true]; // List of ports. v2ray.core.common.net.PortList port_list = 14; // List of networks. Deprecated. Use networks. v2ray.core.common.net.NetworkList network_list = 5 [deprecated = true]; // List of networks for matching. repeated v2ray.core.common.net.Network networks = 13; // List of CIDRs for source IP address matching. repeated CIDR source_cidr = 6 [deprecated = true]; // List of GeoIPs for source IP address matching. If this entry exists, the // source_cidr above will have no effect. repeated GeoIP source_geoip = 11; // List of ports for source port matching. v2ray.core.common.net.PortList source_port_list = 16; repeated string user_email = 7; repeated string inbound_tag = 8; repeated string protocol = 9; string attributes = 15; } message BalancingRule { string tag = 1; repeated string outbound_selector = 2; } message Config { enum DomainStrategy { // Use domain as is. AsIs = 0; // Always resolve IP for domains. UseIp = 1; // Resolve to IP if the domain doesn't match any rules. IpIfNonMatch = 2; // Resolve to IP if any rule requires IP matching. IpOnDemand = 3; } DomainStrategy domain_strategy = 1; repeated RoutingRule rule = 2; repeated BalancingRule balancing_rule = 3; } ================================================ FILE: app/router/errors.generated.go ================================================ package router import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/router/router.go ================================================ // +build !confonly package router //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/features/dns" "v2ray.com/core/features/outbound" "v2ray.com/core/features/routing" routing_dns "v2ray.com/core/features/routing/dns" ) // Router is an implementation of routing.Router. type Router struct { domainStrategy Config_DomainStrategy rules []*Rule balancers map[string]*Balancer dns dns.Client } // Route is an implementation of routing.Route. type Route struct { routing.Context outboundGroupTags []string outboundTag string } // Init initializes the Router. func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error { r.domainStrategy = config.DomainStrategy r.dns = d r.balancers = make(map[string]*Balancer, len(config.BalancingRule)) for _, rule := range config.BalancingRule { balancer, err := rule.Build(ohm) if err != nil { return err } r.balancers[rule.Tag] = balancer } r.rules = make([]*Rule, 0, len(config.Rule)) for _, rule := range config.Rule { cond, err := rule.BuildCondition() if err != nil { return err } rr := &Rule{ Condition: cond, Tag: rule.GetTag(), } btag := rule.GetBalancingTag() if len(btag) > 0 { brule, found := r.balancers[btag] if !found { return newError("balancer ", btag, " not found") } rr.Balancer = brule } r.rules = append(r.rules, rr) } return nil } // PickRoute implements routing.Router. func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) { rule, ctx, err := r.pickRouteInternal(ctx) if err != nil { return nil, err } tag, err := rule.GetTag() if err != nil { return nil, err } return &Route{Context: ctx, outboundTag: tag}, nil } func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) { if r.domainStrategy == Config_IpOnDemand { ctx = routing_dns.ContextWithDNSClient(ctx, r.dns) } for _, rule := range r.rules { if rule.Apply(ctx) { return rule, ctx, nil } } if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 { return nil, ctx, common.ErrNoClue } ctx = routing_dns.ContextWithDNSClient(ctx, r.dns) // Try applying rules again if we have IPs. for _, rule := range r.rules { if rule.Apply(ctx) { return rule, ctx, nil } } return nil, ctx, common.ErrNoClue } // Start implements common.Runnable. func (*Router) Start() error { return nil } // Close implements common.Closable. func (*Router) Close() error { return nil } // Type implement common.HasType. func (*Router) Type() interface{} { return routing.RouterType() } // GetOutboundGroupTags implements routing.Route. func (r *Route) GetOutboundGroupTags() []string { return r.outboundGroupTags } // GetOutboundTag implements routing.Route. func (r *Route) GetOutboundTag() string { return r.outboundTag } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { r := new(Router) if err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager) error { return r.Init(config.(*Config), d, ohm) }); err != nil { return nil, err } return r, nil })) } ================================================ FILE: app/router/router_test.go ================================================ package router_test import ( "context" "testing" "github.com/golang/mock/gomock" . "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/features/outbound" routing_session "v2ray.com/core/features/routing/session" "v2ray.com/core/testing/mocks" ) type mockOutboundManager struct { outbound.Manager outbound.HandlerSelector } func TestSimpleRouter(t *testing.T) { config := &Config{ Rule: []*RoutingRule{ { TargetTag: &RoutingRule_Tag{ Tag: "test", }, Networks: []net.Network{net.Network_TCP}, }, }, } mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockDns := mocks.NewDNSClient(mockCtl) mockOhm := mocks.NewOutboundManager(mockCtl) mockHs := mocks.NewOutboundHandlerSelector(mockCtl) r := new(Router) common.Must(r.Init(config, mockDns, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, })) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) common.Must(err) if tag := route.GetOutboundTag(); tag != "test" { t.Error("expect tag 'test', bug actually ", tag) } } func TestSimpleBalancer(t *testing.T) { config := &Config{ Rule: []*RoutingRule{ { TargetTag: &RoutingRule_BalancingTag{ BalancingTag: "balance", }, Networks: []net.Network{net.Network_TCP}, }, }, BalancingRule: []*BalancingRule{ { Tag: "balance", OutboundSelector: []string{"test-"}, }, }, } mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockDns := mocks.NewDNSClient(mockCtl) mockOhm := mocks.NewOutboundManager(mockCtl) mockHs := mocks.NewOutboundHandlerSelector(mockCtl) mockHs.EXPECT().Select(gomock.Eq([]string{"test-"})).Return([]string{"test"}) r := new(Router) common.Must(r.Init(config, mockDns, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, })) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) common.Must(err) if tag := route.GetOutboundTag(); tag != "test" { t.Error("expect tag 'test', bug actually ", tag) } } func TestIPOnDemand(t *testing.T) { config := &Config{ DomainStrategy: Config_IpOnDemand, Rule: []*RoutingRule{ { TargetTag: &RoutingRule_Tag{ Tag: "test", }, Cidr: []*CIDR{ { Ip: []byte{192, 168, 0, 0}, Prefix: 16, }, }, }, }, } mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockDns := mocks.NewDNSClient(mockCtl) mockDns.EXPECT().LookupIP(gomock.Eq("v2ray.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDns, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) common.Must(err) if tag := route.GetOutboundTag(); tag != "test" { t.Error("expect tag 'test', bug actually ", tag) } } func TestIPIfNonMatchDomain(t *testing.T) { config := &Config{ DomainStrategy: Config_IpIfNonMatch, Rule: []*RoutingRule{ { TargetTag: &RoutingRule_Tag{ Tag: "test", }, Cidr: []*CIDR{ { Ip: []byte{192, 168, 0, 0}, Prefix: 16, }, }, }, }, } mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockDns := mocks.NewDNSClient(mockCtl) mockDns.EXPECT().LookupIP(gomock.Eq("v2ray.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) common.Must(r.Init(config, mockDns, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) common.Must(err) if tag := route.GetOutboundTag(); tag != "test" { t.Error("expect tag 'test', bug actually ", tag) } } func TestIPIfNonMatchIP(t *testing.T) { config := &Config{ DomainStrategy: Config_IpIfNonMatch, Rule: []*RoutingRule{ { TargetTag: &RoutingRule_Tag{ Tag: "test", }, Cidr: []*CIDR{ { Ip: []byte{127, 0, 0, 0}, Prefix: 8, }, }, }, }, } mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockDns := mocks.NewDNSClient(mockCtl) r := new(Router) common.Must(r.Init(config, mockDns, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) common.Must(err) if tag := route.GetOutboundTag(); tag != "test" { t.Error("expect tag 'test', bug actually ", tag) } } ================================================ FILE: app/stats/channel.go ================================================ // +build !confonly package stats import ( "context" "sync" "v2ray.com/core/common" ) // Channel is an implementation of stats.Channel. type Channel struct { channel chan channelMessage subscribers []chan interface{} // Synchronization components access sync.RWMutex closed chan struct{} // Channel options blocking bool // Set blocking state if channel buffer reaches limit bufferSize int // Set to 0 as no buffering subsLimit int // Set to 0 as no subscriber limit } // NewChannel creates an instance of Statistics Channel. func NewChannel(config *ChannelConfig) *Channel { return &Channel{ channel: make(chan channelMessage, config.BufferSize), subsLimit: int(config.SubscriberLimit), bufferSize: int(config.BufferSize), blocking: config.Blocking, } } // Subscribers implements stats.Channel. func (c *Channel) Subscribers() []chan interface{} { c.access.RLock() defer c.access.RUnlock() return c.subscribers } // Subscribe implements stats.Channel. func (c *Channel) Subscribe() (chan interface{}, error) { c.access.Lock() defer c.access.Unlock() if c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit { return nil, newError("Number of subscribers has reached limit") } subscriber := make(chan interface{}, c.bufferSize) c.subscribers = append(c.subscribers, subscriber) return subscriber, nil } // Unsubscribe implements stats.Channel. func (c *Channel) Unsubscribe(subscriber chan interface{}) error { c.access.Lock() defer c.access.Unlock() for i, s := range c.subscribers { if s == subscriber { // Copy to new memory block to prevent modifying original data subscribers := make([]chan interface{}, len(c.subscribers)-1) copy(subscribers[:i], c.subscribers[:i]) copy(subscribers[i:], c.subscribers[i+1:]) c.subscribers = subscribers } } return nil } // Publish implements stats.Channel. func (c *Channel) Publish(ctx context.Context, msg interface{}) { select { // Early exit if channel closed case <-c.closed: return default: pub := channelMessage{context: ctx, message: msg} if c.blocking { pub.publish(c.channel) } else { pub.publishNonBlocking(c.channel) } } } // Running returns whether the channel is running. func (c *Channel) Running() bool { select { case <-c.closed: // Channel closed default: // Channel running or not initialized if c.closed != nil { // Channel initialized return true } } return false } // Start implements common.Runnable. func (c *Channel) Start() error { c.access.Lock() defer c.access.Unlock() if !c.Running() { c.closed = make(chan struct{}) // Reset close signal go func() { for { select { case pub := <-c.channel: // Published message received for _, sub := range c.Subscribers() { // Concurrency-safe subscribers retrievement if c.blocking { pub.broadcast(sub) } else { pub.broadcastNonBlocking(sub) } } case <-c.closed: // Channel closed for _, sub := range c.Subscribers() { // Remove all subscribers common.Must(c.Unsubscribe(sub)) close(sub) } return } } }() } return nil } // Close implements common.Closable. func (c *Channel) Close() error { c.access.Lock() defer c.access.Unlock() if c.Running() { close(c.closed) // Send closed signal } return nil } // channelMessage is the published message with guaranteed delivery. // message is discarded only when the context is early cancelled. type channelMessage struct { context context.Context message interface{} } func (c channelMessage) publish(publisher chan channelMessage) { select { case publisher <- c: case <-c.context.Done(): } } func (c channelMessage) publishNonBlocking(publisher chan channelMessage) { select { case publisher <- c: default: // Create another goroutine to keep sending message go c.publish(publisher) } } func (c channelMessage) broadcast(subscriber chan interface{}) { select { case subscriber <- c.message: case <-c.context.Done(): } } func (c channelMessage) broadcastNonBlocking(subscriber chan interface{}) { select { case subscriber <- c.message: default: // Create another goroutine to keep sending message go c.broadcast(subscriber) } } ================================================ FILE: app/stats/channel_test.go ================================================ package stats_test import ( "context" "fmt" "testing" "time" . "v2ray.com/core/app/stats" "v2ray.com/core/common" "v2ray.com/core/features/stats" ) func TestStatsChannel(t *testing.T) { // At most 2 subscribers could be registered c := NewChannel(&ChannelConfig{SubscriberLimit: 2, Blocking: true}) a, err := stats.SubscribeRunnableChannel(c) common.Must(err) if !c.Running() { t.Fatal("unexpected failure in running channel after first subscription") } b, err := c.Subscribe() common.Must(err) // Test that third subscriber is forbidden _, err = c.Subscribe() if err == nil { t.Fatal("unexpected successful subscription") } t.Log("expected error: ", err) stopCh := make(chan struct{}) errCh := make(chan string) go func() { c.Publish(context.Background(), 1) c.Publish(context.Background(), 2) c.Publish(context.Background(), "3") c.Publish(context.Background(), []int{4}) stopCh <- struct{}{} }() go func() { if v, ok := (<-a).(int); !ok || v != 1 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) } if v, ok := (<-a).(int); !ok || v != 2 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) } if v, ok := (<-a).(string); !ok || v != "3" { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") } if v, ok := (<-a).([]int); !ok || v[0] != 4 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) } stopCh <- struct{}{} }() go func() { if v, ok := (<-b).(int); !ok || v != 1 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) } if v, ok := (<-b).(int); !ok || v != 2 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) } if v, ok := (<-b).(string); !ok || v != "3" { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") } if v, ok := (<-b).([]int); !ok || v[0] != 4 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) } stopCh <- struct{}{} }() timeout := time.After(2 * time.Second) for i := 0; i < 3; i++ { select { case <-timeout: t.Fatal("Test timeout after 2s") case e := <-errCh: t.Fatal(e) case <-stopCh: } } // Test the unsubscription of channel common.Must(c.Unsubscribe(b)) // Test the last subscriber will close channel with `UnsubscribeClosableChannel` common.Must(stats.UnsubscribeClosableChannel(c, a)) if c.Running() { t.Fatal("unexpected running channel after unsubscribing the last subscriber") } } func TestStatsChannelUnsubcribe(t *testing.T) { c := NewChannel(&ChannelConfig{Blocking: true}) common.Must(c.Start()) defer c.Close() a, err := c.Subscribe() common.Must(err) defer c.Unsubscribe(a) b, err := c.Subscribe() common.Must(err) pauseCh := make(chan struct{}) stopCh := make(chan struct{}) errCh := make(chan string) { var aSet, bSet bool for _, s := range c.Subscribers() { if s == a { aSet = true } if s == b { bSet = true } } if !(aSet && bSet) { t.Fatal("unexpected subscribers: ", c.Subscribers()) } } go func() { // Blocking publish c.Publish(context.Background(), 1) <-pauseCh // Wait for `b` goroutine to resume sending message c.Publish(context.Background(), 2) }() go func() { if v, ok := (<-a).(int); !ok || v != 1 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) } if v, ok := (<-a).(int); !ok || v != 2 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) } }() go func() { if v, ok := (<-b).(int); !ok || v != 1 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) } // Unsubscribe `b` while publishing is paused c.Unsubscribe(b) { // Test `b` is not in subscribers var aSet, bSet bool for _, s := range c.Subscribers() { if s == a { aSet = true } if s == b { bSet = true } } if !(aSet && !bSet) { errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers()) } } // Resume publishing progress close(pauseCh) // Test `b` is neither closed nor able to receive any data select { case v, ok := <-b: if ok { errCh <- fmt.Sprint("unexpected data received: ", v) } else { errCh <- fmt.Sprint("unexpected closed channel: ", b) } default: } close(stopCh) }() select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case e := <-errCh: t.Fatal(e) case <-stopCh: } } func TestStatsChannelBlocking(t *testing.T) { // Do not use buffer so as to create blocking scenario c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) common.Must(c.Start()) defer c.Close() a, err := c.Subscribe() common.Must(err) defer c.Unsubscribe(a) pauseCh := make(chan struct{}) stopCh := make(chan struct{}) errCh := make(chan string) ctx, cancel := context.WithCancel(context.Background()) // Test blocking channel publishing go func() { // Dummy messsage with no subscriber receiving, will block broadcasting goroutine c.Publish(context.Background(), nil) <-pauseCh // Publishing should be blocked here, for last message was not cleared and buffer was full c.Publish(context.Background(), nil) pauseCh <- struct{}{} // Publishing should still be blocked here c.Publish(ctx, nil) // Check publishing is done because context is canceled select { case <-ctx.Done(): if ctx.Err() != context.Canceled { errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) } default: errCh <- "unexpected non-blocked publishing" } close(stopCh) }() go func() { pauseCh <- struct{}{} select { case <-pauseCh: errCh <- "unexpected non-blocked publishing" case <-time.After(100 * time.Millisecond): } // Receive first published message <-a select { case <-pauseCh: case <-time.After(100 * time.Millisecond): errCh <- "unexpected blocking publishing" } // Manually cancel the context to end publishing cancel() }() select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case e := <-errCh: t.Fatal(e) case <-stopCh: } } func TestStatsChannelNonBlocking(t *testing.T) { // Do not use buffer so as to create blocking scenario c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: false}) common.Must(c.Start()) defer c.Close() a, err := c.Subscribe() common.Must(err) defer c.Unsubscribe(a) pauseCh := make(chan struct{}) stopCh := make(chan struct{}) errCh := make(chan string) ctx, cancel := context.WithCancel(context.Background()) // Test blocking channel publishing go func() { c.Publish(context.Background(), nil) c.Publish(context.Background(), nil) pauseCh <- struct{}{} <-pauseCh c.Publish(ctx, nil) c.Publish(ctx, nil) // Check publishing is done because context is canceled select { case <-ctx.Done(): if ctx.Err() != context.Canceled { errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) } case <-time.After(100 * time.Millisecond): errCh <- "unexpected non-cancelled publishing" } }() go func() { // Check publishing won't block even if there is no subscriber receiving message select { case <-pauseCh: case <-time.After(100 * time.Millisecond): errCh <- "unexpected blocking publishing" } // Receive first and second published message <-a <-a pauseCh <- struct{}{} // Manually cancel the context to end publishing cancel() // Check third and forth published message is cancelled and cannot receive <-time.After(100 * time.Millisecond) select { case <-a: errCh <- "unexpected non-cancelled publishing" default: } select { case <-a: errCh <- "unexpected non-cancelled publishing" default: } close(stopCh) }() select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case e := <-errCh: t.Fatal(e) case <-stopCh: } } func TestStatsChannelConcurrency(t *testing.T) { // Do not use buffer so as to create blocking scenario c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) common.Must(c.Start()) defer c.Close() a, err := c.Subscribe() common.Must(err) defer c.Unsubscribe(a) b, err := c.Subscribe() common.Must(err) defer c.Unsubscribe(b) stopCh := make(chan struct{}) errCh := make(chan string) go func() { // Blocking publish c.Publish(context.Background(), 1) c.Publish(context.Background(), 2) }() go func() { if v, ok := (<-a).(int); !ok || v != 1 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) } if v, ok := (<-a).(int); !ok || v != 2 { errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) } }() go func() { // Block `b` for a time so as to ensure source channel is trying to send message to `b`. <-time.After(25 * time.Millisecond) // This causes concurrency scenario: unsubscribe `b` while trying to send message to it c.Unsubscribe(b) // Test `b` is not closed and can still receive data 1: // Because unsubscribe won't affect the ongoing process of sending message. select { case v, ok := <-b: if v1, ok1 := v.(int); !(ok && ok1 && v1 == 1) { errCh <- fmt.Sprint("unexpected failure in receiving data: ", 1) } default: errCh <- fmt.Sprint("unexpected block from receiving data: ", 1) } // Test `b` is not closed but cannot receive data 2: // Because in a new round of messaging, `b` has been unsubscribed. select { case v, ok := <-b: if ok { errCh <- fmt.Sprint("unexpected receiving: ", v) } else { errCh <- fmt.Sprint("unexpected closing of channel") } default: } close(stopCh) }() select { case <-time.After(2 * time.Second): t.Fatal("Test timeout after 2s") case e := <-errCh: t.Fatal(e) case <-stopCh: } } ================================================ FILE: app/stats/command/command.go ================================================ // +build !confonly package command //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "runtime" "time" grpc "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/app/stats" "v2ray.com/core/common" "v2ray.com/core/common/strmatcher" feature_stats "v2ray.com/core/features/stats" ) // statsServer is an implementation of StatsService. type statsServer struct { stats feature_stats.Manager startTime time.Time } func NewStatsServer(manager feature_stats.Manager) StatsServiceServer { return &statsServer{ stats: manager, startTime: time.Now(), } } func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { c := s.stats.GetCounter(request.Name) if c == nil { return nil, newError(request.Name, " not found.") } var value int64 if request.Reset_ { value = c.Set(0) } else { value = c.Value() } return &GetStatsResponse{ Stat: &Stat{ Name: request.Name, Value: value, }, }, nil } func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { matcher, err := strmatcher.Substr.New(request.Pattern) if err != nil { return nil, err } response := &QueryStatsResponse{} manager, ok := s.stats.(*stats.Manager) if !ok { return nil, newError("QueryStats only works its own stats.Manager.") } manager.VisitCounters(func(name string, c feature_stats.Counter) bool { if matcher.Match(name) { var value int64 if request.Reset_ { value = c.Set(0) } else { value = c.Value() } response.Stat = append(response.Stat, &Stat{ Name: name, Value: value, }) } return true }) return response, nil } func (s *statsServer) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) { var rtm runtime.MemStats runtime.ReadMemStats(&rtm) uptime := time.Since(s.startTime) response := &SysStatsResponse{ Uptime: uint32(uptime.Seconds()), NumGoroutine: uint32(runtime.NumGoroutine()), Alloc: rtm.Alloc, TotalAlloc: rtm.TotalAlloc, Sys: rtm.Sys, Mallocs: rtm.Mallocs, Frees: rtm.Frees, LiveObjects: rtm.Mallocs - rtm.Frees, NumGC: rtm.NumGC, PauseTotalNs: rtm.PauseTotalNs, } return response, nil } func (s *statsServer) mustEmbedUnimplementedStatsServiceServer() {} type service struct { statsManager feature_stats.Manager } func (s *service) Register(server *grpc.Server) { RegisterStatsServiceServer(server, NewStatsServer(s.statsManager)) } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { s := new(service) core.RequireFeatures(ctx, func(sm feature_stats.Manager) { s.statsManager = sm }) return s, nil })) } ================================================ FILE: app/stats/command/command.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/stats/command/command.proto package command import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type GetStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Name of the stat counter. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Whether or not to reset the counter to fetching its value. Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` } func (x *GetStatsRequest) Reset() { *x = GetStatsRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetStatsRequest) ProtoMessage() {} func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead. func (*GetStatsRequest) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{0} } func (x *GetStatsRequest) GetName() string { if x != nil { return x.Name } return "" } func (x *GetStatsRequest) GetReset_() bool { if x != nil { return x.Reset_ } return false } type Stat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *Stat) Reset() { *x = Stat{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Stat) String() string { return protoimpl.X.MessageStringOf(x) } func (*Stat) ProtoMessage() {} func (x *Stat) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Stat.ProtoReflect.Descriptor instead. func (*Stat) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{1} } func (x *Stat) GetName() string { if x != nil { return x.Name } return "" } func (x *Stat) GetValue() int64 { if x != nil { return x.Value } return 0 } type GetStatsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` } func (x *GetStatsResponse) Reset() { *x = GetStatsResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetStatsResponse) ProtoMessage() {} func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead. func (*GetStatsResponse) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{2} } func (x *GetStatsResponse) GetStat() *Stat { if x != nil { return x.Stat } return nil } type QueryStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` } func (x *QueryStatsRequest) Reset() { *x = QueryStatsRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QueryStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*QueryStatsRequest) ProtoMessage() {} func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead. func (*QueryStatsRequest) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{3} } func (x *QueryStatsRequest) GetPattern() string { if x != nil { return x.Pattern } return "" } func (x *QueryStatsRequest) GetReset_() bool { if x != nil { return x.Reset_ } return false } type QueryStatsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` } func (x *QueryStatsResponse) Reset() { *x = QueryStatsResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QueryStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*QueryStatsResponse) ProtoMessage() {} func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead. func (*QueryStatsResponse) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{4} } func (x *QueryStatsResponse) GetStat() []*Stat { if x != nil { return x.Stat } return nil } type SysStatsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *SysStatsRequest) Reset() { *x = SysStatsRequest{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SysStatsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SysStatsRequest) ProtoMessage() {} func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead. func (*SysStatsRequest) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{5} } type SysStatsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` } func (x *SysStatsResponse) Reset() { *x = SysStatsResponse{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SysStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SysStatsResponse) ProtoMessage() {} func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead. func (*SysStatsResponse) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{6} } func (x *SysStatsResponse) GetNumGoroutine() uint32 { if x != nil { return x.NumGoroutine } return 0 } func (x *SysStatsResponse) GetNumGC() uint32 { if x != nil { return x.NumGC } return 0 } func (x *SysStatsResponse) GetAlloc() uint64 { if x != nil { return x.Alloc } return 0 } func (x *SysStatsResponse) GetTotalAlloc() uint64 { if x != nil { return x.TotalAlloc } return 0 } func (x *SysStatsResponse) GetSys() uint64 { if x != nil { return x.Sys } return 0 } func (x *SysStatsResponse) GetMallocs() uint64 { if x != nil { return x.Mallocs } return 0 } func (x *SysStatsResponse) GetFrees() uint64 { if x != nil { return x.Frees } return 0 } func (x *SysStatsResponse) GetLiveObjects() uint64 { if x != nil { return x.LiveObjects } return 0 } func (x *SysStatsResponse) GetPauseTotalNs() uint64 { if x != nil { return x.PauseTotalNs } return 0 } func (x *SysStatsResponse) GetUptime() uint32 { if x != nil { return x.Uptime } return 0 } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_command_command_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_stats_command_command_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_stats_command_command_proto_rawDescGZIP(), []int{7} } var File_app_stats_command_command_proto protoreflect.FileDescriptor var file_app_stats_command_command_proto_rawDesc = []byte{ 0x0a, 0x1f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x43, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x4c, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xde, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x65, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x20, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1c, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_stats_command_command_proto_rawDescOnce sync.Once file_app_stats_command_command_proto_rawDescData = file_app_stats_command_command_proto_rawDesc ) func file_app_stats_command_command_proto_rawDescGZIP() []byte { file_app_stats_command_command_proto_rawDescOnce.Do(func() { file_app_stats_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_stats_command_command_proto_rawDescData) }) return file_app_stats_command_command_proto_rawDescData } var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_app_stats_command_command_proto_goTypes = []interface{}{ (*GetStatsRequest)(nil), // 0: v2ray.core.app.stats.command.GetStatsRequest (*Stat)(nil), // 1: v2ray.core.app.stats.command.Stat (*GetStatsResponse)(nil), // 2: v2ray.core.app.stats.command.GetStatsResponse (*QueryStatsRequest)(nil), // 3: v2ray.core.app.stats.command.QueryStatsRequest (*QueryStatsResponse)(nil), // 4: v2ray.core.app.stats.command.QueryStatsResponse (*SysStatsRequest)(nil), // 5: v2ray.core.app.stats.command.SysStatsRequest (*SysStatsResponse)(nil), // 6: v2ray.core.app.stats.command.SysStatsResponse (*Config)(nil), // 7: v2ray.core.app.stats.command.Config } var file_app_stats_command_command_proto_depIdxs = []int32{ 1, // 0: v2ray.core.app.stats.command.GetStatsResponse.stat:type_name -> v2ray.core.app.stats.command.Stat 1, // 1: v2ray.core.app.stats.command.QueryStatsResponse.stat:type_name -> v2ray.core.app.stats.command.Stat 0, // 2: v2ray.core.app.stats.command.StatsService.GetStats:input_type -> v2ray.core.app.stats.command.GetStatsRequest 3, // 3: v2ray.core.app.stats.command.StatsService.QueryStats:input_type -> v2ray.core.app.stats.command.QueryStatsRequest 5, // 4: v2ray.core.app.stats.command.StatsService.GetSysStats:input_type -> v2ray.core.app.stats.command.SysStatsRequest 2, // 5: v2ray.core.app.stats.command.StatsService.GetStats:output_type -> v2ray.core.app.stats.command.GetStatsResponse 4, // 6: v2ray.core.app.stats.command.StatsService.QueryStats:output_type -> v2ray.core.app.stats.command.QueryStatsResponse 6, // 7: v2ray.core.app.stats.command.StatsService.GetSysStats:output_type -> v2ray.core.app.stats.command.SysStatsResponse 5, // [5:8] is the sub-list for method output_type 2, // [2:5] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_app_stats_command_command_proto_init() } func file_app_stats_command_command_proto_init() { if File_app_stats_command_command_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_stats_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStatsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stat); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStatsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryStatsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryStatsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SysStatsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SysStatsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_command_command_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_stats_command_command_proto_rawDesc, NumEnums: 0, NumMessages: 8, NumExtensions: 0, NumServices: 1, }, GoTypes: file_app_stats_command_command_proto_goTypes, DependencyIndexes: file_app_stats_command_command_proto_depIdxs, MessageInfos: file_app_stats_command_command_proto_msgTypes, }.Build() File_app_stats_command_command_proto = out.File file_app_stats_command_command_proto_rawDesc = nil file_app_stats_command_command_proto_goTypes = nil file_app_stats_command_command_proto_depIdxs = nil } ================================================ FILE: app/stats/command/command.proto ================================================ syntax = "proto3"; package v2ray.core.app.stats.command; option csharp_namespace = "V2Ray.Core.App.Stats.Command"; option go_package = "v2ray.com/core/app/stats/command"; option java_package = "com.v2ray.core.app.stats.command"; option java_multiple_files = true; message GetStatsRequest { // Name of the stat counter. string name = 1; // Whether or not to reset the counter to fetching its value. bool reset = 2; } message Stat { string name = 1; int64 value = 2; } message GetStatsResponse { Stat stat = 1; } message QueryStatsRequest { string pattern = 1; bool reset = 2; } message QueryStatsResponse { repeated Stat stat = 1; } message SysStatsRequest {} message SysStatsResponse { uint32 NumGoroutine = 1; uint32 NumGC = 2; uint64 Alloc = 3; uint64 TotalAlloc = 4; uint64 Sys = 5; uint64 Mallocs = 6; uint64 Frees = 7; uint64 LiveObjects = 8; uint64 PauseTotalNs = 9; uint32 Uptime = 10; } service StatsService { rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} } message Config {} ================================================ FILE: app/stats/command/command_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package command import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion7 // StatsServiceClient is the client API for StatsService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type StatsServiceClient interface { GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) } type statsServiceClient struct { cc grpc.ClientConnInterface } func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { return &statsServiceClient{cc} } func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { out := new(GetStatsResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.stats.command.StatsService/GetStats", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { out := new(QueryStatsResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.stats.command.StatsService/QueryStats", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { out := new(SysStatsResponse) err := c.cc.Invoke(ctx, "/v2ray.core.app.stats.command.StatsService/GetSysStats", in, out, opts...) if err != nil { return nil, err } return out, nil } // StatsServiceServer is the server API for StatsService service. // All implementations must embed UnimplementedStatsServiceServer // for forward compatibility type StatsServiceServer interface { GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) mustEmbedUnimplementedStatsServiceServer() } // UnimplementedStatsServiceServer must be embedded to have forward compatible implementations. type UnimplementedStatsServiceServer struct { } func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented") } func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") } func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") } func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} // UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to StatsServiceServer will // result in compilation errors. type UnsafeStatsServiceServer interface { mustEmbedUnimplementedStatsServiceServer() } func RegisterStatsServiceServer(s *grpc.Server, srv StatsServiceServer) { s.RegisterService(&_StatsService_serviceDesc, srv) } func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetStatsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(StatsServiceServer).GetStats(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.stats.command.StatsService/GetStats", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest)) } return interceptor(ctx, in, info, handler) } func _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryStatsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(StatsServiceServer).QueryStats(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.stats.command.StatsService/QueryStats", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest)) } return interceptor(ctx, in, info, handler) } func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SysStatsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(StatsServiceServer).GetSysStats(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/v2ray.core.app.stats.command.StatsService/GetSysStats", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest)) } return interceptor(ctx, in, info, handler) } var _StatsService_serviceDesc = grpc.ServiceDesc{ ServiceName: "v2ray.core.app.stats.command.StatsService", HandlerType: (*StatsServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetStats", Handler: _StatsService_GetStats_Handler, }, { MethodName: "QueryStats", Handler: _StatsService_QueryStats_Handler, }, { MethodName: "GetSysStats", Handler: _StatsService_GetSysStats_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "app/stats/command/command.proto", } ================================================ FILE: app/stats/command/command_test.go ================================================ package command_test import ( "context" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "v2ray.com/core/app/stats" . "v2ray.com/core/app/stats/command" "v2ray.com/core/common" ) func TestGetStats(t *testing.T) { m, err := stats.NewManager(context.Background(), &stats.Config{}) common.Must(err) sc, err := m.RegisterCounter("test_counter") common.Must(err) sc.Set(1) s := NewStatsServer(m) testCases := []struct { name string reset bool value int64 err bool }{ { name: "counterNotExist", err: true, }, { name: "test_counter", reset: true, value: 1, }, { name: "test_counter", value: 0, }, } for _, tc := range testCases { resp, err := s.GetStats(context.Background(), &GetStatsRequest{ Name: tc.name, Reset_: tc.reset, }) if tc.err { if err == nil { t.Error("nil error: ", tc.name) } } else { common.Must(err) if r := cmp.Diff(resp.Stat, &Stat{Name: tc.name, Value: tc.value}, cmpopts.IgnoreUnexported(Stat{})); r != "" { t.Error(r) } } } } func TestQueryStats(t *testing.T) { m, err := stats.NewManager(context.Background(), &stats.Config{}) common.Must(err) sc1, err := m.RegisterCounter("test_counter") common.Must(err) sc1.Set(1) sc2, err := m.RegisterCounter("test_counter_2") common.Must(err) sc2.Set(2) sc3, err := m.RegisterCounter("test_counter_3") common.Must(err) sc3.Set(3) s := NewStatsServer(m) resp, err := s.QueryStats(context.Background(), &QueryStatsRequest{ Pattern: "counter_", }) common.Must(err) if r := cmp.Diff(resp.Stat, []*Stat{ {Name: "test_counter_2", Value: 2}, {Name: "test_counter_3", Value: 3}, }, cmpopts.SortSlices(func(s1, s2 *Stat) bool { return s1.Name < s2.Name }), cmpopts.IgnoreUnexported(Stat{})); r != "" { t.Error(r) } } ================================================ FILE: app/stats/command/errors.generated.go ================================================ package command import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/stats/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: app/stats/config.proto package stats import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_app_stats_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_app_stats_config_proto_rawDescGZIP(), []int{0} } type ChannelConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Blocking bool `protobuf:"varint,1,opt,name=Blocking,proto3" json:"Blocking,omitempty"` SubscriberLimit int32 `protobuf:"varint,2,opt,name=SubscriberLimit,proto3" json:"SubscriberLimit,omitempty"` BufferSize int32 `protobuf:"varint,3,opt,name=BufferSize,proto3" json:"BufferSize,omitempty"` } func (x *ChannelConfig) Reset() { *x = ChannelConfig{} if protoimpl.UnsafeEnabled { mi := &file_app_stats_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChannelConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChannelConfig) ProtoMessage() {} func (x *ChannelConfig) ProtoReflect() protoreflect.Message { mi := &file_app_stats_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChannelConfig.ProtoReflect.Descriptor instead. func (*ChannelConfig) Descriptor() ([]byte, []int) { return file_app_stats_config_proto_rawDescGZIP(), []int{1} } func (x *ChannelConfig) GetBlocking() bool { if x != nil { return x.Blocking } return false } func (x *ChannelConfig) GetSubscriberLimit() int32 { if x != nil { return x.SubscriberLimit } return 0 } func (x *ChannelConfig) GetBufferSize() int32 { if x != nil { return x.BufferSize } return 0 } var File_app_stats_config_proto protoreflect.FileDescriptor var file_app_stats_config_proto_rawDesc = []byte{ 0x0a, 0x16, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x75, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x28, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x4d, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x50, 0x01, 0x5a, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0xaa, 0x02, 0x14, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_app_stats_config_proto_rawDescOnce sync.Once file_app_stats_config_proto_rawDescData = file_app_stats_config_proto_rawDesc ) func file_app_stats_config_proto_rawDescGZIP() []byte { file_app_stats_config_proto_rawDescOnce.Do(func() { file_app_stats_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_stats_config_proto_rawDescData) }) return file_app_stats_config_proto_rawDescData } var file_app_stats_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_app_stats_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.app.stats.Config (*ChannelConfig)(nil), // 1: v2ray.core.app.stats.ChannelConfig } var file_app_stats_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_app_stats_config_proto_init() } func file_app_stats_config_proto_init() { if File_app_stats_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_app_stats_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_app_stats_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChannelConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_stats_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_app_stats_config_proto_goTypes, DependencyIndexes: file_app_stats_config_proto_depIdxs, MessageInfos: file_app_stats_config_proto_msgTypes, }.Build() File_app_stats_config_proto = out.File file_app_stats_config_proto_rawDesc = nil file_app_stats_config_proto_goTypes = nil file_app_stats_config_proto_depIdxs = nil } ================================================ FILE: app/stats/config.proto ================================================ syntax = "proto3"; package v2ray.core.app.stats; option csharp_namespace = "V2Ray.Core.App.Stats"; option go_package = "v2ray.com/core/app/stats"; option java_package = "com.v2ray.core.app.stats"; option java_multiple_files = true; message Config {} message ChannelConfig { bool Blocking = 1; int32 SubscriberLimit = 2; int32 BufferSize = 3; } ================================================ FILE: app/stats/counter.go ================================================ // +build !confonly package stats import "sync/atomic" // Counter is an implementation of stats.Counter. type Counter struct { value int64 } // Value implements stats.Counter. func (c *Counter) Value() int64 { return atomic.LoadInt64(&c.value) } // Set implements stats.Counter. func (c *Counter) Set(newValue int64) int64 { return atomic.SwapInt64(&c.value, newValue) } // Add implements stats.Counter. func (c *Counter) Add(delta int64) int64 { return atomic.AddInt64(&c.value, delta) } ================================================ FILE: app/stats/counter_test.go ================================================ package stats_test import ( "context" "testing" . "v2ray.com/core/app/stats" "v2ray.com/core/common" "v2ray.com/core/features/stats" ) func TestStatsCounter(t *testing.T) { raw, err := common.CreateObject(context.Background(), &Config{}) common.Must(err) m := raw.(stats.Manager) c, err := m.RegisterCounter("test.counter") common.Must(err) if v := c.Add(1); v != 1 { t.Fatal("unpexcted Add(1) return: ", v, ", wanted ", 1) } if v := c.Set(0); v != 1 { t.Fatal("unexpected Set(0) return: ", v, ", wanted ", 1) } if v := c.Value(); v != 0 { t.Fatal("unexpected Value() return: ", v, ", wanted ", 0) } } ================================================ FILE: app/stats/errors.generated.go ================================================ package stats import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: app/stats/stats.go ================================================ // +build !confonly package stats //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "sync" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/features/stats" ) // Manager is an implementation of stats.Manager. type Manager struct { access sync.RWMutex counters map[string]*Counter channels map[string]*Channel running bool } // NewManager creates an instance of Statistics Manager. func NewManager(ctx context.Context, config *Config) (*Manager, error) { m := &Manager{ counters: make(map[string]*Counter), channels: make(map[string]*Channel), } return m, nil } // Type implements common.HasType. func (*Manager) Type() interface{} { return stats.ManagerType() } // RegisterCounter implements stats.Manager. func (m *Manager) RegisterCounter(name string) (stats.Counter, error) { m.access.Lock() defer m.access.Unlock() if _, found := m.counters[name]; found { return nil, newError("Counter ", name, " already registered.") } newError("create new counter ", name).AtDebug().WriteToLog() c := new(Counter) m.counters[name] = c return c, nil } // UnregisterCounter implements stats.Manager. func (m *Manager) UnregisterCounter(name string) error { m.access.Lock() defer m.access.Unlock() if _, found := m.counters[name]; found { newError("remove counter ", name).AtDebug().WriteToLog() delete(m.counters, name) } return nil } // GetCounter implements stats.Manager. func (m *Manager) GetCounter(name string) stats.Counter { m.access.RLock() defer m.access.RUnlock() if c, found := m.counters[name]; found { return c } return nil } // VisitCounters calls visitor function on all managed counters. func (m *Manager) VisitCounters(visitor func(string, stats.Counter) bool) { m.access.RLock() defer m.access.RUnlock() for name, c := range m.counters { if !visitor(name, c) { break } } } // RegisterChannel implements stats.Manager. func (m *Manager) RegisterChannel(name string) (stats.Channel, error) { m.access.Lock() defer m.access.Unlock() if _, found := m.channels[name]; found { return nil, newError("Channel ", name, " already registered.") } newError("create new channel ", name).AtDebug().WriteToLog() c := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false}) m.channels[name] = c if m.running { return c, c.Start() } return c, nil } // UnregisterChannel implements stats.Manager. func (m *Manager) UnregisterChannel(name string) error { m.access.Lock() defer m.access.Unlock() if c, found := m.channels[name]; found { newError("remove channel ", name).AtDebug().WriteToLog() delete(m.channels, name) return c.Close() } return nil } // GetChannel implements stats.Manager. func (m *Manager) GetChannel(name string) stats.Channel { m.access.RLock() defer m.access.RUnlock() if c, found := m.channels[name]; found { return c } return nil } // Start implements common.Runnable. func (m *Manager) Start() error { m.access.Lock() defer m.access.Unlock() m.running = true errs := []error{} for _, channel := range m.channels { if err := channel.Start(); err != nil { errs = append(errs, err) } } if len(errs) != 0 { return errors.Combine(errs...) } return nil } // Close implement common.Closable. func (m *Manager) Close() error { m.access.Lock() defer m.access.Unlock() m.running = false errs := []error{} for name, channel := range m.channels { newError("remove channel ", name).AtDebug().WriteToLog() delete(m.channels, name) if err := channel.Close(); err != nil { errs = append(errs, err) } } if len(errs) != 0 { return errors.Combine(errs...) } return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewManager(ctx, config.(*Config)) })) } ================================================ FILE: app/stats/stats_test.go ================================================ package stats_test import ( "context" "testing" "time" . "v2ray.com/core/app/stats" "v2ray.com/core/common" "v2ray.com/core/features/stats" ) func TestInterface(t *testing.T) { _ = (stats.Manager)(new(Manager)) } func TestStatsChannelRunnable(t *testing.T) { raw, err := common.CreateObject(context.Background(), &Config{}) common.Must(err) m := raw.(stats.Manager) ch1, err := m.RegisterChannel("test.channel.1") c1 := ch1.(*Channel) common.Must(err) if c1.Running() { t.Fatalf("unexpected running channel: test.channel.%d", 1) } common.Must(m.Start()) if !c1.Running() { t.Fatalf("unexpected non-running channel: test.channel.%d", 1) } ch2, err := m.RegisterChannel("test.channel.2") c2 := ch2.(*Channel) common.Must(err) if !c2.Running() { t.Fatalf("unexpected non-running channel: test.channel.%d", 2) } s1, err := c1.Subscribe() common.Must(err) common.Must(c1.Close()) if c1.Running() { t.Fatalf("unexpected running channel: test.channel.%d", 1) } select { // Check all subscribers in closed channel are closed case _, ok := <-s1: if ok { t.Fatalf("unexpected non-closed subscriber in channel: test.channel.%d", 1) } case <-time.After(500 * time.Millisecond): t.Fatalf("unexpected non-closed subscriber in channel: test.channel.%d", 1) } if len(c1.Subscribers()) != 0 { // Check subscribers in closed channel are emptied t.Fatalf("unexpected non-empty subscribers in channel: test.channel.%d", 1) } common.Must(m.Close()) if c2.Running() { t.Fatalf("unexpected running channel: test.channel.%d", 2) } ch3, err := m.RegisterChannel("test.channel.3") c3 := ch3.(*Channel) common.Must(err) if c3.Running() { t.Fatalf("unexpected running channel: test.channel.%d", 3) } common.Must(c3.Start()) common.Must(m.UnregisterChannel("test.channel.3")) if c3.Running() { // Test that unregistering will close the channel. t.Fatalf("unexpected running channel: test.channel.%d", 3) } } ================================================ FILE: azure-pipelines.yml ================================================ trigger: batch: true branches: include: - master - dev* - refs/tags/* pool: vmImage: "ubuntu-latest" variables: - group: GithubToken steps: - checkout: self - task: GoTool@0 inputs: version: "1.15.2" - script: | go version go mod download workingDirectory: $(system.defaultWorkingDirectory) displayName: "Fetch sources" - script: | bazel build --action_env=PATH=$PATH --action_env=GOPATH=$(go env GOPATH) --action_env=GOCACHE=$(go env GOCACHE) --action_env=SPWD=$(pwd) --spawn_strategy local //release:all workingDirectory: $(system.defaultWorkingDirectory) displayName: "Build Binaries" - script: | echo $RELEASE_TAG ./release/bleedingrelease.sh workingDirectory: $(system.defaultWorkingDirectory) displayName: "Generate Bleeding Edge Release" env: WORKDIR: $(system.defaultWorkingDirectory) GITHUB_TOKEN: $(GITHUB_TOKEN) PRERELEASE: true RELEASE_TAG: unstable-$(Build.SourceVersion) RELEASE_SHA: $(Build.SourceVersion) TRIGGER_REASON: $(Build.SourceBranch) GITHUB_REPO_OWNER: v2fly GITHUB_REPO_NAME: v2ray-core - script: | echo $RELEASE_TAG ./release/tagrelease.sh workingDirectory: $(system.defaultWorkingDirectory) displayName: "Generate Tag Release" env: WORKDIR: $(system.defaultWorkingDirectory) GITHUB_TOKEN: $(GITHUB_TOKEN) PRERELEASE: true RELEASE_TAG: unstable-$(Build.SourceVersion) RELEASE_SHA: $(Build.SourceVersion) TRIGGER_REASON: $(Build.SourceBranch) ================================================ FILE: common/antireplay/antireplay.go ================================================ package antireplay import ( cuckoo "github.com/seiflotfy/cuckoofilter" "sync" "time" ) func NewAntiReplayWindow(AntiReplayTime int64) *AntiReplayWindow { arw := &AntiReplayWindow{} arw.AntiReplayTime = AntiReplayTime return arw } type AntiReplayWindow struct { lock sync.Mutex poolA *cuckoo.Filter poolB *cuckoo.Filter lastSwapTime int64 PoolSwap bool AntiReplayTime int64 } func (aw *AntiReplayWindow) Check(sum []byte) bool { aw.lock.Lock() if aw.lastSwapTime == 0 { aw.lastSwapTime = time.Now().Unix() aw.poolA = cuckoo.NewFilter(100000) aw.poolB = cuckoo.NewFilter(100000) } tnow := time.Now().Unix() timediff := tnow - aw.lastSwapTime if timediff >= aw.AntiReplayTime { if aw.PoolSwap { aw.PoolSwap = false aw.poolA.Reset() } else { aw.PoolSwap = true aw.poolB.Reset() } aw.lastSwapTime = tnow } ret := aw.poolA.InsertUnique(sum) && aw.poolB.InsertUnique(sum) aw.lock.Unlock() return ret } ================================================ FILE: common/bitmask/byte.go ================================================ package bitmask // Byte is a bitmask in byte. type Byte byte // Has returns true if this bitmask contains another bitmask. func (b Byte) Has(bb Byte) bool { return (b & bb) != 0 } func (b *Byte) Set(bb Byte) { *b |= bb } func (b *Byte) Clear(bb Byte) { *b &= ^bb } func (b *Byte) Toggle(bb Byte) { *b ^= bb } ================================================ FILE: common/bitmask/byte_test.go ================================================ package bitmask_test import ( "testing" . "v2ray.com/core/common/bitmask" ) func TestBitmaskByte(t *testing.T) { b := Byte(0) b.Set(Byte(1)) if !b.Has(1) { t.Fatal("expected ", b, " to contain 1, but actually not") } b.Set(Byte(2)) if !b.Has(2) { t.Fatal("expected ", b, " to contain 2, but actually not") } if !b.Has(1) { t.Fatal("expected ", b, " to contain 1, but actually not") } b.Clear(Byte(1)) if !b.Has(2) { t.Fatal("expected ", b, " to contain 2, but actually not") } if b.Has(1) { t.Fatal("expected ", b, " to not contain 1, but actually did") } b.Toggle(Byte(2)) if b.Has(2) { t.Fatal("expected ", b, " to not contain 2, but actually did") } } ================================================ FILE: common/buf/buf.go ================================================ // Package buf provides a light-weight memory allocation mechanism. package buf // import "v2ray.com/core/common/buf" //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: common/buf/buffer.go ================================================ package buf import ( "io" "v2ray.com/core/common/bytespool" ) const ( // Size of a regular buffer. Size = 2048 ) // Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles // the buffer into an internal buffer pool, in order to recreate a buffer more // quickly. type Buffer struct { v []byte start int32 end int32 } // Release recycles the buffer into an internal buffer pool. func (b *Buffer) Release() { if b == nil || b.v == nil { return } p := b.v b.v = nil b.Clear() pool.Put(p) } // Clear clears the content of the buffer, results an empty buffer with // Len() = 0. func (b *Buffer) Clear() { b.start = 0 b.end = 0 } // Byte returns the bytes at index. func (b *Buffer) Byte(index int32) byte { return b.v[b.start+index] } // SetByte sets the byte value at index. func (b *Buffer) SetByte(index int32, value byte) { b.v[b.start+index] = value } // Bytes returns the content bytes of this Buffer. func (b *Buffer) Bytes() []byte { return b.v[b.start:b.end] } // Extend increases the buffer size by n bytes, and returns the extended part. // It panics if result size is larger than buf.Size. func (b *Buffer) Extend(n int32) []byte { end := b.end + n if end > int32(len(b.v)) { panic("extending out of bound") } ext := b.v[b.end:end] b.end = end return ext } // BytesRange returns a slice of this buffer with given from and to boundary. func (b *Buffer) BytesRange(from, to int32) []byte { if from < 0 { from += b.Len() } if to < 0 { to += b.Len() } return b.v[b.start+from : b.start+to] } // BytesFrom returns a slice of this Buffer starting from the given position. func (b *Buffer) BytesFrom(from int32) []byte { if from < 0 { from += b.Len() } return b.v[b.start+from : b.end] } // BytesTo returns a slice of this Buffer from start to the given position. func (b *Buffer) BytesTo(to int32) []byte { if to < 0 { to += b.Len() } return b.v[b.start : b.start+to] } // Resize cuts the buffer at the given position. func (b *Buffer) Resize(from, to int32) { if from < 0 { from += b.Len() } if to < 0 { to += b.Len() } if to < from { panic("Invalid slice") } b.end = b.start + to b.start += from } // Advance cuts the buffer at the given position. func (b *Buffer) Advance(from int32) { if from < 0 { from += b.Len() } b.start += from } // Len returns the length of the buffer content. func (b *Buffer) Len() int32 { if b == nil { return 0 } return b.end - b.start } // IsEmpty returns true if the buffer is empty. func (b *Buffer) IsEmpty() bool { return b.Len() == 0 } // IsFull returns true if the buffer has no more room to grow. func (b *Buffer) IsFull() bool { return b != nil && b.end == int32(len(b.v)) } // Write implements Write method in io.Writer. func (b *Buffer) Write(data []byte) (int, error) { nBytes := copy(b.v[b.end:], data) b.end += int32(nBytes) return nBytes, nil } // WriteByte writes a single byte into the buffer. func (b *Buffer) WriteByte(v byte) error { if b.IsFull() { return newError("buffer full") } b.v[b.end] = v b.end++ return nil } // WriteString implements io.StringWriter. func (b *Buffer) WriteString(s string) (int, error) { return b.Write([]byte(s)) } // Read implements io.Reader.Read(). func (b *Buffer) Read(data []byte) (int, error) { if b.Len() == 0 { return 0, io.EOF } nBytes := copy(data, b.v[b.start:b.end]) if int32(nBytes) == b.Len() { b.Clear() } else { b.start += int32(nBytes) } return nBytes, nil } // ReadFrom implements io.ReaderFrom. func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) { n, err := reader.Read(b.v[b.end:]) b.end += int32(n) return int64(n), err } // ReadFullFrom reads exact size of bytes from given reader, or until error occurs. func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) { end := b.end + size if end > int32(len(b.v)) { v := end return 0, newError("out of bound: ", v) } n, err := io.ReadFull(reader, b.v[b.end:end]) b.end += int32(n) return int64(n), err } // String returns the string form of this Buffer. func (b *Buffer) String() string { return string(b.Bytes()) } var pool = bytespool.GetPool(Size) // New creates a Buffer with 0 length and 2K capacity. func New() *Buffer { return &Buffer{ v: pool.Get().([]byte), } } // StackNew creates a new Buffer object on stack. // This method is for buffers that is released in the same function. func StackNew() Buffer { return Buffer{ v: pool.Get().([]byte), } } ================================================ FILE: common/buf/buffer_test.go ================================================ package buf_test import ( "bytes" "crypto/rand" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/common/buf" ) func TestBufferClear(t *testing.T) { buffer := New() defer buffer.Release() payload := "Bytes" buffer.Write([]byte(payload)) if diff := cmp.Diff(buffer.Bytes(), []byte(payload)); diff != "" { t.Error(diff) } buffer.Clear() if buffer.Len() != 0 { t.Error("expect 0 length, but got ", buffer.Len()) } } func TestBufferIsEmpty(t *testing.T) { buffer := New() defer buffer.Release() if buffer.IsEmpty() != true { t.Error("expect empty buffer, but not") } } func TestBufferString(t *testing.T) { buffer := New() defer buffer.Release() const payload = "Test String" common.Must2(buffer.WriteString(payload)) if buffer.String() != payload { t.Error("expect buffer content as ", payload, " but actually ", buffer.String()) } } func TestBufferByte(t *testing.T) { { buffer := New() common.Must(buffer.WriteByte('m')) if buffer.String() != "m" { t.Error("expect buffer content as ", "m", " but actually ", buffer.String()) } buffer.Release() } { buffer := StackNew() common.Must(buffer.WriteByte('n')) if buffer.String() != "n" { t.Error("expect buffer content as ", "n", " but actually ", buffer.String()) } buffer.Release() } { buffer := StackNew() common.Must2(buffer.WriteString("HELLOWORLD")) if b := buffer.Byte(5); b != 'W' { t.Error("unexpected byte ", b) } buffer.SetByte(5, 'M') if buffer.String() != "HELLOMORLD" { t.Error("expect buffer content as ", "n", " but actually ", buffer.String()) } buffer.Release() } } func TestBufferResize(t *testing.T) { buffer := New() defer buffer.Release() const payload = "Test String" common.Must2(buffer.WriteString(payload)) if buffer.String() != payload { t.Error("expect buffer content as ", payload, " but actually ", buffer.String()) } buffer.Resize(-6, -3) if l := buffer.Len(); int(l) != 3 { t.Error("len error ", l) } if s := buffer.String(); s != "Str" { t.Error("unexpect buffer ", s) } buffer.Resize(int32(len(payload)), 200) if l := buffer.Len(); int(l) != 200-len(payload) { t.Error("len error ", l) } } func TestBufferSlice(t *testing.T) { { b := New() common.Must2(b.Write([]byte("abcd"))) bytes := b.BytesFrom(-2) if diff := cmp.Diff(bytes, []byte{'c', 'd'}); diff != "" { t.Error(diff) } } { b := New() common.Must2(b.Write([]byte("abcd"))) bytes := b.BytesTo(-2) if diff := cmp.Diff(bytes, []byte{'a', 'b'}); diff != "" { t.Error(diff) } } { b := New() common.Must2(b.Write([]byte("abcd"))) bytes := b.BytesRange(-3, -1) if diff := cmp.Diff(bytes, []byte{'b', 'c'}); diff != "" { t.Error(diff) } } } func TestBufferReadFullFrom(t *testing.T) { payload := make([]byte, 1024) common.Must2(rand.Read(payload)) reader := bytes.NewReader(payload) b := New() n, err := b.ReadFullFrom(reader, 1024) common.Must(err) if n != 1024 { t.Error("expect reading 1024 bytes, but actually ", n) } if diff := cmp.Diff(payload, b.Bytes()); diff != "" { t.Error(diff) } } func BenchmarkNewBuffer(b *testing.B) { for i := 0; i < b.N; i++ { buffer := New() buffer.Release() } } func BenchmarkNewBufferStack(b *testing.B) { for i := 0; i < b.N; i++ { buffer := StackNew() buffer.Release() } } func BenchmarkWrite2(b *testing.B) { buffer := New() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = buffer.Write([]byte{'a', 'b'}) buffer.Clear() } } func BenchmarkWrite8(b *testing.B) { buffer := New() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = buffer.Write([]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}) buffer.Clear() } } func BenchmarkWrite32(b *testing.B) { buffer := New() payload := make([]byte, 32) rand.Read(payload) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = buffer.Write(payload) buffer.Clear() } } func BenchmarkWriteByte2(b *testing.B) { buffer := New() b.ResetTimer() for i := 0; i < b.N; i++ { _ = buffer.WriteByte('a') _ = buffer.WriteByte('b') buffer.Clear() } } func BenchmarkWriteByte8(b *testing.B) { buffer := New() b.ResetTimer() for i := 0; i < b.N; i++ { _ = buffer.WriteByte('a') _ = buffer.WriteByte('b') _ = buffer.WriteByte('c') _ = buffer.WriteByte('d') _ = buffer.WriteByte('e') _ = buffer.WriteByte('f') _ = buffer.WriteByte('g') _ = buffer.WriteByte('h') buffer.Clear() } } ================================================ FILE: common/buf/copy.go ================================================ package buf import ( "io" "time" "v2ray.com/core/common/errors" "v2ray.com/core/common/signal" ) type dataHandler func(MultiBuffer) type copyHandler struct { onData []dataHandler } // SizeCounter is for counting bytes copied by Copy(). type SizeCounter struct { Size int64 } // CopyOption is an option for copying data. type CopyOption func(*copyHandler) // UpdateActivity is a CopyOption to update activity on each data copy operation. func UpdateActivity(timer signal.ActivityUpdater) CopyOption { return func(handler *copyHandler) { handler.onData = append(handler.onData, func(MultiBuffer) { timer.Update() }) } } // CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter. func CountSize(sc *SizeCounter) CopyOption { return func(handler *copyHandler) { handler.onData = append(handler.onData, func(b MultiBuffer) { sc.Size += int64(b.Len()) }) } } type readError struct { error } func (e readError) Error() string { return e.error.Error() } func (e readError) Inner() error { return e.error } // IsReadError returns true if the error in Copy() comes from reading. func IsReadError(err error) bool { _, ok := err.(readError) return ok } type writeError struct { error } func (e writeError) Error() string { return e.error.Error() } func (e writeError) Inner() error { return e.error } // IsWriteError returns true if the error in Copy() comes from writing. func IsWriteError(err error) bool { _, ok := err.(writeError) return ok } func copyInternal(reader Reader, writer Writer, handler *copyHandler) error { for { buffer, err := reader.ReadMultiBuffer() if !buffer.IsEmpty() { for _, handler := range handler.onData { handler(buffer) } if werr := writer.WriteMultiBuffer(buffer); werr != nil { return writeError{werr} } } if err != nil { return readError{err} } } } // Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF. func Copy(reader Reader, writer Writer, options ...CopyOption) error { var handler copyHandler for _, option := range options { option(&handler) } err := copyInternal(reader, writer, &handler) if err != nil && errors.Cause(err) != io.EOF { return err } return nil } var ErrNotTimeoutReader = newError("not a TimeoutReader") func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error { timeoutReader, ok := reader.(TimeoutReader) if !ok { return ErrNotTimeoutReader } mb, err := timeoutReader.ReadMultiBufferTimeout(timeout) if err != nil { return err } return writer.WriteMultiBuffer(mb) } ================================================ FILE: common/buf/copy_test.go ================================================ package buf_test import ( "crypto/rand" "io" "testing" "github.com/golang/mock/gomock" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/testing/mocks" ) func TestReadError(t *testing.T) { mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockReader := mocks.NewReader(mockCtl) mockReader.EXPECT().Read(gomock.Any()).Return(0, errors.New("error")) err := buf.Copy(buf.NewReader(mockReader), buf.Discard) if err == nil { t.Fatal("expected error, but nil") } if !buf.IsReadError(err) { t.Error("expected to be ReadError, but not") } if err.Error() != "error" { t.Fatal("unexpected error message: ", err.Error()) } } func TestWriteError(t *testing.T) { mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockWriter := mocks.NewWriter(mockCtl) mockWriter.EXPECT().Write(gomock.Any()).Return(0, errors.New("error")) err := buf.Copy(buf.NewReader(rand.Reader), buf.NewWriter(mockWriter)) if err == nil { t.Fatal("expected error, but nil") } if !buf.IsWriteError(err) { t.Error("expected to be WriteError, but not") } if err.Error() != "error" { t.Fatal("unexpected error message: ", err.Error()) } } type TestReader struct{} func (TestReader) Read(b []byte) (int, error) { return len(b), nil } func BenchmarkCopy(b *testing.B) { reader := buf.NewReader(io.LimitReader(TestReader{}, 10240)) writer := buf.Discard b.ResetTimer() for i := 0; i < b.N; i++ { _ = buf.Copy(reader, writer) } } ================================================ FILE: common/buf/errors.generated.go ================================================ package buf import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/buf/io.go ================================================ package buf import ( "io" "net" "os" "syscall" "time" ) // Reader extends io.Reader with MultiBuffer. type Reader interface { // ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer. ReadMultiBuffer() (MultiBuffer, error) } // ErrReadTimeout is an error that happens with IO timeout. var ErrReadTimeout = newError("IO timeout") // TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout. type TimeoutReader interface { ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error) } // Writer extends io.Writer with MultiBuffer. type Writer interface { // WriteMultiBuffer writes a MultiBuffer into underlying writer. WriteMultiBuffer(MultiBuffer) error } // WriteAllBytes ensures all bytes are written into the given writer. func WriteAllBytes(writer io.Writer, payload []byte) error { for len(payload) > 0 { n, err := writer.Write(payload) if err != nil { return err } payload = payload[n:] } return nil } func isPacketReader(reader io.Reader) bool { _, ok := reader.(net.PacketConn) return ok } // NewReader creates a new Reader. // The Reader instance doesn't take the ownership of reader. func NewReader(reader io.Reader) Reader { if mr, ok := reader.(Reader); ok { return mr } if isPacketReader(reader) { return &PacketReader{ Reader: reader, } } _, isFile := reader.(*os.File) if !isFile && useReadv { if sc, ok := reader.(syscall.Conn); ok { rawConn, err := sc.SyscallConn() if err != nil { newError("failed to get sysconn").Base(err).WriteToLog() } else { return NewReadVReader(reader, rawConn) } } } return &SingleReader{ Reader: reader, } } // NewPacketReader creates a new PacketReader based on the given reader. func NewPacketReader(reader io.Reader) Reader { if mr, ok := reader.(Reader); ok { return mr } return &PacketReader{ Reader: reader, } } func isPacketWriter(writer io.Writer) bool { if _, ok := writer.(net.PacketConn); ok { return true } // If the writer doesn't implement syscall.Conn, it is probably not a TCP connection. if _, ok := writer.(syscall.Conn); !ok { return true } return false } // NewWriter creates a new Writer. func NewWriter(writer io.Writer) Writer { if mw, ok := writer.(Writer); ok { return mw } if isPacketWriter(writer) { return &SequentialWriter{ Writer: writer, } } return &BufferToBytesWriter{ Writer: writer, } } ================================================ FILE: common/buf/io_test.go ================================================ package buf_test import ( "crypto/tls" "io" "testing" . "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/testing/servers/tcp" ) func TestWriterCreation(t *testing.T) { tcpServer := tcp.Server{} dest, err := tcpServer.Start() if err != nil { t.Fatal("failed to start tcp server: ", err) } defer tcpServer.Close() conn, err := net.Dial("tcp", dest.NetAddr()) if err != nil { t.Fatal("failed to dial a TCP connection: ", err) } defer conn.Close() { writer := NewWriter(conn) if _, ok := writer.(*BufferToBytesWriter); !ok { t.Fatal("writer is not a BufferToBytesWriter") } writer2 := NewWriter(writer.(io.Writer)) if writer2 != writer { t.Fatal("writer is not reused") } } tlsConn := tls.Client(conn, &tls.Config{ InsecureSkipVerify: true, }) defer tlsConn.Close() { writer := NewWriter(tlsConn) if _, ok := writer.(*SequentialWriter); !ok { t.Fatal("writer is not a SequentialWriter") } } } ================================================ FILE: common/buf/multi_buffer.go ================================================ package buf import ( "io" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/serial" ) // ReadAllToBytes reads all content from the reader into a byte array, until EOF. func ReadAllToBytes(reader io.Reader) ([]byte, error) { mb, err := ReadFrom(reader) if err != nil { return nil, err } if mb.Len() == 0 { return nil, nil } b := make([]byte, mb.Len()) mb, _ = SplitBytes(mb, b) ReleaseMulti(mb) return b, nil } // MultiBuffer is a list of Buffers. The order of Buffer matters. type MultiBuffer []*Buffer // MergeMulti merges content from src to dest, and returns the new address of dest and src func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) { dest = append(dest, src...) for idx := range src { src[idx] = nil } return dest, src[:0] } // MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer. func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer { n := len(dest) if n > 0 && !(dest)[n-1].IsFull() { nBytes, _ := (dest)[n-1].Write(src) src = src[nBytes:] } for len(src) > 0 { b := New() nBytes, _ := b.Write(src) src = src[nBytes:] dest = append(dest, b) } return dest } // ReleaseMulti release all content of the MultiBuffer, and returns an empty MultiBuffer. func ReleaseMulti(mb MultiBuffer) MultiBuffer { for i := range mb { mb[i].Release() mb[i] = nil } return mb[:0] } // Copy copied the beginning part of the MultiBuffer into the given byte array. func (mb MultiBuffer) Copy(b []byte) int { total := 0 for _, bb := range mb { nBytes := copy(b[total:], bb.Bytes()) total += nBytes if int32(nBytes) < bb.Len() { break } } return total } // ReadFrom reads all content from reader until EOF. func ReadFrom(reader io.Reader) (MultiBuffer, error) { mb := make(MultiBuffer, 0, 16) for { b := New() _, err := b.ReadFullFrom(reader, Size) if b.IsEmpty() { b.Release() } else { mb = append(mb, b) } if err != nil { if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF { return mb, nil } return mb, err } } } // SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer. // It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice. func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) { totalBytes := 0 endIndex := -1 for i := range mb { pBuffer := mb[i] nBytes, _ := pBuffer.Read(b) totalBytes += nBytes b = b[nBytes:] if !pBuffer.IsEmpty() { endIndex = i break } pBuffer.Release() mb[i] = nil } if endIndex == -1 { mb = mb[:0] } else { mb = mb[endIndex:] } return mb, totalBytes } // SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice. func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) { mb, b := SplitFirst(mb) if b == nil { return mb, 0 } n := copy(p, b.Bytes()) b.Release() return mb, n } // Compact returns another MultiBuffer by merging all content of the given one together. func Compact(mb MultiBuffer) MultiBuffer { if len(mb) == 0 { return mb } mb2 := make(MultiBuffer, 0, len(mb)) last := mb[0] for i := 1; i < len(mb); i++ { curr := mb[i] if last.Len()+curr.Len() > Size { mb2 = append(mb2, last) last = curr } else { common.Must2(last.ReadFrom(curr)) curr.Release() } } mb2 = append(mb2, last) return mb2 } // SplitFirst splits the first Buffer from the beginning of the MultiBuffer. func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) { if len(mb) == 0 { return mb, nil } b := mb[0] mb[0] = nil mb = mb[1:] return mb, b } // SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes. func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) { if len(mb) == 0 { return mb, nil } if mb[0].Len() > size { b := New() copy(b.Extend(size), mb[0].BytesTo(size)) mb[0].Advance(size) return mb, MultiBuffer{b} } totalBytes := int32(0) var r MultiBuffer endIndex := -1 for i := range mb { if totalBytes+mb[i].Len() > size { endIndex = i break } totalBytes += mb[i].Len() r = append(r, mb[i]) mb[i] = nil } if endIndex == -1 { // To reuse mb array mb = mb[:0] } else { mb = mb[endIndex:] } return mb, r } // WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer. func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) { for { mb2, b := SplitFirst(mb) mb = mb2 if b == nil { break } _, err := writer.Write(b.Bytes()) b.Release() if err != nil { return mb, err } } return nil, nil } // Len returns the total number of bytes in the MultiBuffer. func (mb MultiBuffer) Len() int32 { if mb == nil { return 0 } size := int32(0) for _, b := range mb { size += b.Len() } return size } // IsEmpty return true if the MultiBuffer has no content. func (mb MultiBuffer) IsEmpty() bool { for _, b := range mb { if !b.IsEmpty() { return false } } return true } // String returns the content of the MultiBuffer in string. func (mb MultiBuffer) String() string { v := make([]interface{}, len(mb)) for i, b := range mb { v[i] = b } return serial.Concat(v...) } // MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer. type MultiBufferContainer struct { MultiBuffer } // Read implements io.Reader. func (c *MultiBufferContainer) Read(b []byte) (int, error) { if c.MultiBuffer.IsEmpty() { return 0, io.EOF } mb, nBytes := SplitBytes(c.MultiBuffer, b) c.MultiBuffer = mb return nBytes, nil } // ReadMultiBuffer implements Reader. func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) { mb := c.MultiBuffer c.MultiBuffer = nil return mb, nil } // Write implements io.Writer. func (c *MultiBufferContainer) Write(b []byte) (int, error) { c.MultiBuffer = MergeBytes(c.MultiBuffer, b) return len(b), nil } // WriteMultiBuffer implement Writer. func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error { mb, _ := MergeMulti(c.MultiBuffer, b) c.MultiBuffer = mb return nil } // Close implement io.Closer. func (c *MultiBufferContainer) Close() error { c.MultiBuffer = ReleaseMulti(c.MultiBuffer) return nil } ================================================ FILE: common/buf/multi_buffer_test.go ================================================ package buf_test import ( "bytes" "crypto/rand" "io" "testing" "github.com/google/go-cmp/cmp" "io/ioutil" "os" "v2ray.com/core/common" . "v2ray.com/core/common/buf" ) func TestMultiBufferRead(t *testing.T) { b1 := New() common.Must2(b1.WriteString("ab")) b2 := New() common.Must2(b2.WriteString("cd")) mb := MultiBuffer{b1, b2} bs := make([]byte, 32) _, nBytes := SplitBytes(mb, bs) if nBytes != 4 { t.Error("expect 4 bytes split, but got ", nBytes) } if r := cmp.Diff(bs[:nBytes], []byte("abcd")); r != "" { t.Error(r) } } func TestMultiBufferAppend(t *testing.T) { var mb MultiBuffer b := New() common.Must2(b.WriteString("ab")) mb = append(mb, b) if mb.Len() != 2 { t.Error("expected length 2, but got ", mb.Len()) } } func TestMultiBufferSliceBySizeLarge(t *testing.T) { lb := make([]byte, 8*1024) common.Must2(io.ReadFull(rand.Reader, lb)) mb := MergeBytes(nil, lb) mb, mb2 := SplitSize(mb, 1024) if mb2.Len() != 1024 { t.Error("expect length 1024, but got ", mb2.Len()) } if mb.Len() != 7*1024 { t.Error("expect length 7*1024, but got ", mb.Len()) } mb, mb3 := SplitSize(mb, 7*1024) if mb3.Len() != 7*1024 { t.Error("expect length 7*1024, but got", mb.Len()) } if !mb.IsEmpty() { t.Error("expect empty buffer, but got ", mb.Len()) } } func TestMultiBufferSplitFirst(t *testing.T) { b1 := New() b1.WriteString("b1") b2 := New() b2.WriteString("b2") b3 := New() b3.WriteString("b3") var mb MultiBuffer mb = append(mb, b1, b2, b3) mb, c1 := SplitFirst(mb) if diff := cmp.Diff(b1.String(), c1.String()); diff != "" { t.Error(diff) } mb, c2 := SplitFirst(mb) if diff := cmp.Diff(b2.String(), c2.String()); diff != "" { t.Error(diff) } mb, c3 := SplitFirst(mb) if diff := cmp.Diff(b3.String(), c3.String()); diff != "" { t.Error(diff) } if !mb.IsEmpty() { t.Error("expect empty buffer, but got ", mb.String()) } } func TestMultiBufferReadAllToByte(t *testing.T) { { lb := make([]byte, 8*1024) common.Must2(io.ReadFull(rand.Reader, lb)) rd := bytes.NewBuffer(lb) b, err := ReadAllToBytes(rd) common.Must(err) if l := len(b); l != 8*1024 { t.Error("unexpceted length from ReadAllToBytes", l) } } { const dat = "data/test_MultiBufferReadAllToByte.dat" f, err := os.Open(dat) common.Must(err) buf2, err := ReadAllToBytes(f) common.Must(err) f.Close() cnt, err := ioutil.ReadFile(dat) common.Must(err) if d := cmp.Diff(buf2, cnt); d != "" { t.Error("fail to read from file: ", d) } } } func TestMultiBufferCopy(t *testing.T) { lb := make([]byte, 8*1024) common.Must2(io.ReadFull(rand.Reader, lb)) reader := bytes.NewBuffer(lb) mb, err := ReadFrom(reader) common.Must(err) lbdst := make([]byte, 8*1024) mb.Copy(lbdst) if d := cmp.Diff(lb, lbdst); d != "" { t.Error("unexpceted different from MultiBufferCopy ", d) } } func TestSplitFirstBytes(t *testing.T) { a := New() common.Must2(a.WriteString("ab")) b := New() common.Must2(b.WriteString("bc")) mb := MultiBuffer{a, b} o := make([]byte, 2) _, cnt := SplitFirstBytes(mb, o) if cnt != 2 { t.Error("unexpected cnt from SplitFirstBytes ", cnt) } if d := cmp.Diff(string(o), "ab"); d != "" { t.Error("unexpected splited result from SplitFirstBytes ", d) } } func TestCompact(t *testing.T) { a := New() common.Must2(a.WriteString("ab")) b := New() common.Must2(b.WriteString("bc")) mb := MultiBuffer{a, b} cmb := Compact(mb) if w := cmb.String(); w != "abbc" { t.Error("unexpected Compact result ", w) } } func BenchmarkSplitBytes(b *testing.B) { var mb MultiBuffer raw := make([]byte, Size) b.ResetTimer() for i := 0; i < b.N; i++ { buffer := StackNew() buffer.Extend(Size) mb = append(mb, &buffer) mb, _ = SplitBytes(mb, raw) } } ================================================ FILE: common/buf/reader.go ================================================ package buf import ( "io" "v2ray.com/core/common" "v2ray.com/core/common/errors" ) func readOneUDP(r io.Reader) (*Buffer, error) { b := New() for i := 0; i < 64; i++ { _, err := b.ReadFrom(r) if !b.IsEmpty() { return b, nil } if err != nil { b.Release() return nil, err } } b.Release() return nil, newError("Reader returns too many empty payloads.") } // ReadBuffer reads a Buffer from the given reader. func ReadBuffer(r io.Reader) (*Buffer, error) { b := New() n, err := b.ReadFrom(r) if n > 0 { return b, err } b.Release() return nil, err } // BufferedReader is a Reader that keeps its internal buffer. type BufferedReader struct { // Reader is the underlying reader to be read from Reader Reader // Buffer is the internal buffer to be read from first Buffer MultiBuffer // Spliter is a function to read bytes from MultiBuffer Spliter func(MultiBuffer, []byte) (MultiBuffer, int) } // BufferedBytes returns the number of bytes that is cached in this reader. func (r *BufferedReader) BufferedBytes() int32 { return r.Buffer.Len() } // ReadByte implements io.ByteReader. func (r *BufferedReader) ReadByte() (byte, error) { var b [1]byte _, err := r.Read(b[:]) return b[0], err } // Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader. func (r *BufferedReader) Read(b []byte) (int, error) { spliter := r.Spliter if spliter == nil { spliter = SplitBytes } if !r.Buffer.IsEmpty() { buffer, nBytes := spliter(r.Buffer, b) r.Buffer = buffer if r.Buffer.IsEmpty() { r.Buffer = nil } return nBytes, nil } mb, err := r.Reader.ReadMultiBuffer() if err != nil { return 0, err } mb, nBytes := spliter(mb, b) if !mb.IsEmpty() { r.Buffer = mb } return nBytes, nil } // ReadMultiBuffer implements Reader. func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) { if !r.Buffer.IsEmpty() { mb := r.Buffer r.Buffer = nil return mb, nil } return r.Reader.ReadMultiBuffer() } // ReadAtMost returns a MultiBuffer with at most size. func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) { if r.Buffer.IsEmpty() { mb, err := r.Reader.ReadMultiBuffer() if mb.IsEmpty() && err != nil { return nil, err } r.Buffer = mb } rb, mb := SplitSize(r.Buffer, size) r.Buffer = rb if r.Buffer.IsEmpty() { r.Buffer = nil } return mb, nil } func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) { mbWriter := NewWriter(writer) var sc SizeCounter if r.Buffer != nil { sc.Size = int64(r.Buffer.Len()) if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil { return 0, err } r.Buffer = nil } err := Copy(r.Reader, mbWriter, CountSize(&sc)) return sc.Size, err } // WriteTo implements io.WriterTo. func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) { nBytes, err := r.writeToInternal(writer) if errors.Cause(err) == io.EOF { return nBytes, nil } return nBytes, err } // Interrupt implements common.Interruptible. func (r *BufferedReader) Interrupt() { common.Interrupt(r.Reader) } // Close implements io.Closer. func (r *BufferedReader) Close() error { return common.Close(r.Reader) } // SingleReader is a Reader that read one Buffer every time. type SingleReader struct { io.Reader } // ReadMultiBuffer implements Reader. func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) { b, err := ReadBuffer(r.Reader) return MultiBuffer{b}, err } // PacketReader is a Reader that read one Buffer every time. type PacketReader struct { io.Reader } // ReadMultiBuffer implements Reader. func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) { b, err := readOneUDP(r.Reader) if err != nil { return nil, err } return MultiBuffer{b}, nil } ================================================ FILE: common/buf/reader_test.go ================================================ package buf_test import ( "bytes" "io" "strings" "testing" "v2ray.com/core/common" . "v2ray.com/core/common/buf" "v2ray.com/core/transport/pipe" ) func TestBytesReaderWriteTo(t *testing.T) { pReader, pWriter := pipe.New(pipe.WithSizeLimit(1024)) reader := &BufferedReader{Reader: pReader} b1 := New() b1.WriteString("abc") b2 := New() b2.WriteString("efg") common.Must(pWriter.WriteMultiBuffer(MultiBuffer{b1, b2})) pWriter.Close() pReader2, pWriter2 := pipe.New(pipe.WithSizeLimit(1024)) writer := NewBufferedWriter(pWriter2) writer.SetBuffered(false) nBytes, err := io.Copy(writer, reader) common.Must(err) if nBytes != 6 { t.Error("copy: ", nBytes) } mb, err := pReader2.ReadMultiBuffer() common.Must(err) if s := mb.String(); s != "abcefg" { t.Error("content: ", s) } } func TestBytesReaderMultiBuffer(t *testing.T) { pReader, pWriter := pipe.New(pipe.WithSizeLimit(1024)) reader := &BufferedReader{Reader: pReader} b1 := New() b1.WriteString("abc") b2 := New() b2.WriteString("efg") common.Must(pWriter.WriteMultiBuffer(MultiBuffer{b1, b2})) pWriter.Close() mbReader := NewReader(reader) mb, err := mbReader.ReadMultiBuffer() common.Must(err) if s := mb.String(); s != "abcefg" { t.Error("content: ", s) } } func TestReadByte(t *testing.T) { sr := strings.NewReader("abcd") reader := &BufferedReader{ Reader: NewReader(sr), } b, err := reader.ReadByte() common.Must(err) if b != 'a' { t.Error("unexpected byte: ", b, " want a") } if reader.BufferedBytes() != 3 { // 3 bytes left in buffer t.Error("unexpected buffered Bytes: ", reader.BufferedBytes()) } nBytes, err := reader.WriteTo(DiscardBytes) common.Must(err) if nBytes != 3 { t.Error("unexpect bytes written: ", nBytes) } } func TestReadBuffer(t *testing.T) { { sr := strings.NewReader("abcd") buf, err := ReadBuffer(sr) common.Must(err) if s := buf.String(); s != "abcd" { t.Error("unexpected str: ", s, " want abcd") } buf.Release() } } func TestReadAtMost(t *testing.T) { sr := strings.NewReader("abcd") reader := &BufferedReader{ Reader: NewReader(sr), } mb, err := reader.ReadAtMost(3) common.Must(err) if s := mb.String(); s != "abc" { t.Error("unexpected read result: ", s) } nBytes, err := reader.WriteTo(DiscardBytes) common.Must(err) if nBytes != 1 { t.Error("unexpect bytes written: ", nBytes) } } func TestPacketReader_ReadMultiBuffer(t *testing.T) { const alpha = "abcefg" buf := bytes.NewBufferString(alpha) reader := &PacketReader{buf} mb, err := reader.ReadMultiBuffer() common.Must(err) if s := mb.String(); s != alpha { t.Error("content: ", s) } } func TestReaderInterface(t *testing.T) { _ = (io.Reader)(new(ReadVReader)) _ = (Reader)(new(ReadVReader)) _ = (Reader)(new(BufferedReader)) _ = (io.Reader)(new(BufferedReader)) _ = (io.ByteReader)(new(BufferedReader)) _ = (io.WriterTo)(new(BufferedReader)) } ================================================ FILE: common/buf/readv_posix.go ================================================ // +build !windows // +build !wasm // +build !illumos package buf import ( "syscall" "unsafe" ) type posixReader struct { iovecs []syscall.Iovec } func (r *posixReader) Init(bs []*Buffer) { iovecs := r.iovecs if iovecs == nil { iovecs = make([]syscall.Iovec, 0, len(bs)) } for idx, b := range bs { iovecs = append(iovecs, syscall.Iovec{ Base: &(b.v[0]), }) iovecs[idx].SetLen(int(Size)) } r.iovecs = iovecs } func (r *posixReader) Read(fd uintptr) int32 { n, _, e := syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&r.iovecs[0])), uintptr(len(r.iovecs))) if e != 0 { return -1 } return int32(n) } func (r *posixReader) Clear() { for idx := range r.iovecs { r.iovecs[idx].Base = nil } r.iovecs = r.iovecs[:0] } func newMultiReader() multiReader { return &posixReader{} } ================================================ FILE: common/buf/readv_reader.go ================================================ // +build !wasm package buf import ( "io" "runtime" "syscall" "v2ray.com/core/common/platform" ) type allocStrategy struct { current uint32 } func (s *allocStrategy) Current() uint32 { return s.current } func (s *allocStrategy) Adjust(n uint32) { if n >= s.current { s.current *= 4 } else { s.current = n } if s.current > 32 { s.current = 32 } if s.current == 0 { s.current = 1 } } func (s *allocStrategy) Alloc() []*Buffer { bs := make([]*Buffer, s.current) for i := range bs { bs[i] = New() } return bs } type multiReader interface { Init([]*Buffer) Read(fd uintptr) int32 Clear() } // ReadVReader is a Reader that uses readv(2) syscall to read data. type ReadVReader struct { io.Reader rawConn syscall.RawConn mr multiReader alloc allocStrategy } // NewReadVReader creates a new ReadVReader. func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) *ReadVReader { return &ReadVReader{ Reader: reader, rawConn: rawConn, alloc: allocStrategy{ current: 1, }, mr: newMultiReader(), } } func (r *ReadVReader) readMulti() (MultiBuffer, error) { bs := r.alloc.Alloc() r.mr.Init(bs) var nBytes int32 err := r.rawConn.Read(func(fd uintptr) bool { n := r.mr.Read(fd) if n < 0 { return false } nBytes = n return true }) r.mr.Clear() if err != nil { ReleaseMulti(MultiBuffer(bs)) return nil, err } if nBytes == 0 { ReleaseMulti(MultiBuffer(bs)) return nil, io.EOF } nBuf := 0 for nBuf < len(bs) { if nBytes <= 0 { break } end := nBytes if end > Size { end = Size } bs[nBuf].end = end nBytes -= end nBuf++ } for i := nBuf; i < len(bs); i++ { bs[i].Release() bs[i] = nil } return MultiBuffer(bs[:nBuf]), nil } // ReadMultiBuffer implements Reader. func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) { if r.alloc.Current() == 1 { b, err := ReadBuffer(r.Reader) if b.IsFull() { r.alloc.Adjust(1) } return MultiBuffer{b}, err } mb, err := r.readMulti() if err != nil { return nil, err } r.alloc.Adjust(uint32(len(mb))) return mb, nil } var useReadv = false func init() { const defaultFlagValue = "NOT_DEFINED_AT_ALL" value := platform.NewEnvFlag("v2ray.buf.readv").GetValue(func() string { return defaultFlagValue }) switch value { case defaultFlagValue, "auto": if (runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x") && (runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows") { useReadv = true } case "enable": useReadv = true } } ================================================ FILE: common/buf/readv_reader_wasm.go ================================================ // +build wasm package buf import ( "io" "syscall" ) const useReadv = false func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) Reader { panic("not implemented") } ================================================ FILE: common/buf/readv_test.go ================================================ // +build !wasm package buf_test import ( "crypto/rand" "net" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" "v2ray.com/core/common" . "v2ray.com/core/common/buf" "v2ray.com/core/testing/servers/tcp" ) func TestReadvReader(t *testing.T) { tcpServer := &tcp.Server{ MsgProcessor: func(b []byte) []byte { return b }, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() // nolint: errcheck conn, err := net.Dial("tcp", dest.NetAddr()) common.Must(err) defer conn.Close() // nolint: errcheck const size = 8192 data := make([]byte, 8192) common.Must2(rand.Read(data)) var errg errgroup.Group errg.Go(func() error { writer := NewWriter(conn) mb := MergeBytes(nil, data) return writer.WriteMultiBuffer(mb) }) defer func() { if err := errg.Wait(); err != nil { t.Error(err) } }() rawConn, err := conn.(*net.TCPConn).SyscallConn() common.Must(err) reader := NewReadVReader(conn, rawConn) var rmb MultiBuffer for { mb, err := reader.ReadMultiBuffer() if err != nil { t.Fatal("unexpected error: ", err) } rmb, _ = MergeMulti(rmb, mb) if rmb.Len() == size { break } } rdata := make([]byte, size) SplitBytes(rmb, rdata) if r := cmp.Diff(data, rdata); r != "" { t.Fatal(r) } } ================================================ FILE: common/buf/readv_unix.go ================================================ // +build illumos package buf import "golang.org/x/sys/unix" type unixReader struct { iovs [][]byte } func (r *unixReader) Init(bs []*Buffer) { iovs := r.iovs if iovs == nil { iovs = make([][]byte, 0, len(bs)) } for _, b := range bs { iovs = append(iovs, b.v) } r.iovs = iovs } func (r *unixReader) Read(fd uintptr) int32 { n, e := unix.Readv(int(fd), r.iovs) if e != nil { return -1 } return int32(n) } func (r *unixReader) Clear() { r.iovs = r.iovs[:0] } func newMultiReader() multiReader { return &unixReader{} } ================================================ FILE: common/buf/readv_windows.go ================================================ package buf import ( "syscall" ) type windowsReader struct { bufs []syscall.WSABuf } func (r *windowsReader) Init(bs []*Buffer) { if r.bufs == nil { r.bufs = make([]syscall.WSABuf, 0, len(bs)) } for _, b := range bs { r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]}) } } func (r *windowsReader) Clear() { for idx := range r.bufs { r.bufs[idx].Buf = nil } r.bufs = r.bufs[:0] } func (r *windowsReader) Read(fd uintptr) int32 { var nBytes uint32 var flags uint32 err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil) if err != nil { return -1 } return int32(nBytes) } func newMultiReader() multiReader { return new(windowsReader) } ================================================ FILE: common/buf/writer.go ================================================ package buf import ( "io" "net" "sync" "v2ray.com/core/common" "v2ray.com/core/common/errors" ) // BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer. type BufferToBytesWriter struct { io.Writer cache [][]byte } // WriteMultiBuffer implements Writer. This method takes ownership of the given buffer. func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error { defer ReleaseMulti(mb) size := mb.Len() if size == 0 { return nil } if len(mb) == 1 { return WriteAllBytes(w.Writer, mb[0].Bytes()) } if cap(w.cache) < len(mb) { w.cache = make([][]byte, 0, len(mb)) } bs := w.cache for _, b := range mb { bs = append(bs, b.Bytes()) } defer func() { for idx := range bs { bs[idx] = nil } }() nb := net.Buffers(bs) for size > 0 { n, err := nb.WriteTo(w.Writer) if err != nil { return err } size -= int32(n) } return nil } // ReadFrom implements io.ReaderFrom. func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) { var sc SizeCounter err := Copy(NewReader(reader), w, CountSize(&sc)) return sc.Size, err } // BufferedWriter is a Writer with internal buffer. type BufferedWriter struct { sync.Mutex writer Writer buffer *Buffer buffered bool } // NewBufferedWriter creates a new BufferedWriter. func NewBufferedWriter(writer Writer) *BufferedWriter { return &BufferedWriter{ writer: writer, buffer: New(), buffered: true, } } // WriteByte implements io.ByteWriter. func (w *BufferedWriter) WriteByte(c byte) error { return common.Error2(w.Write([]byte{c})) } // Write implements io.Writer. func (w *BufferedWriter) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } w.Lock() defer w.Unlock() if !w.buffered { if writer, ok := w.writer.(io.Writer); ok { return writer.Write(b) } } totalBytes := 0 for len(b) > 0 { if w.buffer == nil { w.buffer = New() } nBytes, err := w.buffer.Write(b) totalBytes += nBytes if err != nil { return totalBytes, err } if !w.buffered || w.buffer.IsFull() { if err := w.flushInternal(); err != nil { return totalBytes, err } } b = b[nBytes:] } return totalBytes, nil } // WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer. func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error { if b.IsEmpty() { return nil } w.Lock() defer w.Unlock() if !w.buffered { return w.writer.WriteMultiBuffer(b) } reader := MultiBufferContainer{ MultiBuffer: b, } defer reader.Close() for !reader.MultiBuffer.IsEmpty() { if w.buffer == nil { w.buffer = New() } common.Must2(w.buffer.ReadFrom(&reader)) if w.buffer.IsFull() { if err := w.flushInternal(); err != nil { return err } } } return nil } // Flush flushes buffered content into underlying writer. func (w *BufferedWriter) Flush() error { w.Lock() defer w.Unlock() return w.flushInternal() } func (w *BufferedWriter) flushInternal() error { if w.buffer.IsEmpty() { return nil } b := w.buffer w.buffer = nil if writer, ok := w.writer.(io.Writer); ok { err := WriteAllBytes(writer, b.Bytes()) b.Release() return err } return w.writer.WriteMultiBuffer(MultiBuffer{b}) } // SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer. func (w *BufferedWriter) SetBuffered(f bool) error { w.Lock() defer w.Unlock() w.buffered = f if !f { return w.flushInternal() } return nil } // ReadFrom implements io.ReaderFrom. func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) { if err := w.SetBuffered(false); err != nil { return 0, err } var sc SizeCounter err := Copy(NewReader(reader), w, CountSize(&sc)) return sc.Size, err } // Close implements io.Closable. func (w *BufferedWriter) Close() error { if err := w.Flush(); err != nil { return err } return common.Close(w.writer) } // SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer. type SequentialWriter struct { io.Writer } // WriteMultiBuffer implements Writer. func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error { mb, err := WriteMultiBuffer(w.Writer, mb) ReleaseMulti(mb) return err } type noOpWriter byte func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error { ReleaseMulti(b) return nil } func (noOpWriter) Write(b []byte) (int, error) { return len(b), nil } func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) { b := New() defer b.Release() totalBytes := int64(0) for { b.Clear() _, err := b.ReadFrom(reader) totalBytes += int64(b.Len()) if err != nil { if errors.Cause(err) == io.EOF { return totalBytes, nil } return totalBytes, err } } } var ( // Discard is a Writer that swallows all contents written in. Discard Writer = noOpWriter(0) // DiscardBytes is an io.Writer that swallows all contents written in. DiscardBytes io.Writer = noOpWriter(0) ) ================================================ FILE: common/buf/writer_test.go ================================================ package buf_test import ( "bufio" "bytes" "crypto/rand" "io" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/common/buf" "v2ray.com/core/transport/pipe" ) func TestWriter(t *testing.T) { lb := New() common.Must2(lb.ReadFrom(rand.Reader)) expectedBytes := append([]byte(nil), lb.Bytes()...) writeBuffer := bytes.NewBuffer(make([]byte, 0, 1024*1024)) writer := NewBufferedWriter(NewWriter(writeBuffer)) writer.SetBuffered(false) common.Must(writer.WriteMultiBuffer(MultiBuffer{lb})) common.Must(writer.Flush()) if r := cmp.Diff(expectedBytes, writeBuffer.Bytes()); r != "" { t.Error(r) } } func TestBytesWriterReadFrom(t *testing.T) { const size = 50000 pReader, pWriter := pipe.New(pipe.WithSizeLimit(size)) reader := bufio.NewReader(io.LimitReader(rand.Reader, size)) writer := NewBufferedWriter(pWriter) writer.SetBuffered(false) nBytes, err := reader.WriteTo(writer) if nBytes != size { t.Fatal("unexpected size of bytes written: ", nBytes) } if err != nil { t.Fatal("expect success, but actually error: ", err.Error()) } mb, err := pReader.ReadMultiBuffer() common.Must(err) if mb.Len() != size { t.Fatal("unexpected size read: ", mb.Len()) } } func TestDiscardBytes(t *testing.T) { b := New() common.Must2(b.ReadFullFrom(rand.Reader, Size)) nBytes, err := io.Copy(DiscardBytes, b) common.Must(err) if nBytes != Size { t.Error("copy size: ", nBytes) } } func TestDiscardBytesMultiBuffer(t *testing.T) { const size = 10240*1024 + 1 buffer := bytes.NewBuffer(make([]byte, 0, size)) common.Must2(buffer.ReadFrom(io.LimitReader(rand.Reader, size))) r := NewReader(buffer) nBytes, err := io.Copy(DiscardBytes, &BufferedReader{Reader: r}) common.Must(err) if nBytes != size { t.Error("copy size: ", nBytes) } } func TestWriterInterface(t *testing.T) { { var writer interface{} = (*BufferToBytesWriter)(nil) switch writer.(type) { case Writer, io.Writer, io.ReaderFrom: default: t.Error("BufferToBytesWriter is not Writer, io.Writer or io.ReaderFrom") } } { var writer interface{} = (*BufferedWriter)(nil) switch writer.(type) { case Writer, io.Writer, io.ReaderFrom, io.ByteWriter: default: t.Error("BufferedWriter is not Writer, io.Writer, io.ReaderFrom or io.ByteWriter") } } } ================================================ FILE: common/bytespool/pool.go ================================================ package bytespool import "sync" func createAllocFunc(size int32) func() interface{} { return func() interface{} { return make([]byte, size) } } // The following parameters controls the size of buffer pools. // There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one. // Package buf is guaranteed to not use buffers larger than the largest pool. // Other packets may use larger buffers. const ( numPools = 4 sizeMulti = 4 ) var ( pool [numPools]sync.Pool poolSize [numPools]int32 ) func init() { size := int32(2048) for i := 0; i < numPools; i++ { pool[i] = sync.Pool{ New: createAllocFunc(size), } poolSize[i] = size size *= sizeMulti } } // GetPool returns a sync.Pool that generates bytes array with at least the given size. // It may return nil if no such pool exists. // // v2ray:api:stable func GetPool(size int32) *sync.Pool { for idx, ps := range poolSize { if size <= ps { return &pool[idx] } } return nil } // Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048. // // v2ray:api:stable func Alloc(size int32) []byte { pool := GetPool(size) if pool != nil { return pool.Get().([]byte) } return make([]byte, size) } // Free puts a byte slice into the internal pool. // // v2ray:api:stable func Free(b []byte) { size := int32(cap(b)) b = b[0:cap(b)] for i := numPools - 1; i >= 0; i-- { if size >= poolSize[i] { pool[i].Put(b) // nolint: megacheck return } } } ================================================ FILE: common/cmdarg/cmdarg.go ================================================ package cmdarg import "strings" // Arg is used by flag to accept multiple argument. type Arg []string func (c *Arg) String() string { return strings.Join([]string(*c), " ") } // Set is the method flag package calls func (c *Arg) Set(value string) error { *c = append(*c, value) return nil } ================================================ FILE: common/common.go ================================================ // Package common contains common utilities that are shared among other packages. // See each sub-package for detail. package common import ( "fmt" "go/build" "io/ioutil" "os" "path/filepath" "strings" "v2ray.com/core/common/errors" ) //go:generate go run v2ray.com/core/common/errors/errorgen var ( // ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route. ErrNoClue = errors.New("not enough information for making a decision") ) // Must panics if err is not nil. func Must(err error) { if err != nil { panic(err) } } // Must2 panics if the second parameter is not nil, otherwise returns the first parameter. func Must2(v interface{}, err error) interface{} { Must(err) return v } // Error2 returns the err from the 2nd parameter. func Error2(v interface{}, err error) error { return err } // envFile returns the name of the Go environment configuration file. // Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 func envFile() (string, error) { if file := os.Getenv("GOENV"); file != "" { if file == "off" { return "", fmt.Errorf("GOENV=off") } return file, nil } dir, err := os.UserConfigDir() if err != nil { return "", err } if dir == "" { return "", fmt.Errorf("missing user-config dir") } return filepath.Join(dir, "go", "env"), nil } // GetRuntimeEnv returns the value of runtime environment variable, // that is set by running following command: `go env -w key=value`. func GetRuntimeEnv(key string) (string, error) { file, err := envFile() if err != nil { return "", err } if file == "" { return "", fmt.Errorf("missing runtime env file") } var data []byte var runtimeEnv string data, readErr := ioutil.ReadFile(file) if readErr != nil { return "", readErr } envStrings := strings.Split(string(data), "\n") for _, envItem := range envStrings { envItem = strings.TrimSuffix(envItem, "\r") envKeyValue := strings.Split(envItem, "=") if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { runtimeEnv = strings.TrimSpace(envKeyValue[1]) } } return runtimeEnv, nil } // GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. func GetGOBIN() string { // The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command` GOBIN := os.Getenv("GOBIN") if GOBIN == "" { var err error // The one set by user by running `go env -w GOBIN=/path` GOBIN, err = GetRuntimeEnv("GOBIN") if err != nil { // The default one that Golang uses return filepath.Join(build.Default.GOPATH, "bin") } if GOBIN == "" { return filepath.Join(build.Default.GOPATH, "bin") } return GOBIN } return GOBIN } // GetGOPATH returns GOPATH environment variable as a string. It will NOT be empty. func GetGOPATH() string { // The one set by user explicitly by `export GOPATH=/path` or `env GOPATH=/path command` GOPATH := os.Getenv("GOPATH") if GOPATH == "" { var err error // The one set by user by running `go env -w GOPATH=/path` GOPATH, err = GetRuntimeEnv("GOPATH") if err != nil { // The default one that Golang uses return build.Default.GOPATH } if GOPATH == "" { return build.Default.GOPATH } return GOPATH } return GOPATH } // GetModuleName returns the value of module in `go.mod` file. func GetModuleName(pathToProjectRoot string) (string, error) { var moduleName string loopPath := pathToProjectRoot for { if idx := strings.LastIndex(loopPath, string(filepath.Separator)); idx >= 0 { gomodPath := filepath.Join(loopPath, "go.mod") gomodBytes, err := ioutil.ReadFile(gomodPath) if err != nil { loopPath = loopPath[:idx] continue } gomodContent := string(gomodBytes) moduleIdx := strings.Index(gomodContent, "module ") newLineIdx := strings.Index(gomodContent, "\n") if moduleIdx >= 0 { if newLineIdx >= 0 { moduleName = strings.TrimSpace(gomodContent[moduleIdx+6 : newLineIdx]) moduleName = strings.TrimSuffix(moduleName, "\r") } else { moduleName = strings.TrimSpace(gomodContent[moduleIdx+6:]) } return moduleName, nil } return "", fmt.Errorf("can not get module path in `%s`", gomodPath) } break } return moduleName, fmt.Errorf("no `go.mod` file in every parent directory of `%s`", pathToProjectRoot) } ================================================ FILE: common/common_test.go ================================================ package common_test import ( "errors" "testing" . "v2ray.com/core/common" ) func TestMust(t *testing.T) { hasPanic := func(f func()) (ret bool) { defer func() { if r := recover(); r != nil { ret = true } }() f() return false } testCases := []struct { Input func() Panic bool }{ { Panic: true, Input: func() { Must(func() error { return errors.New("test error") }()) }, }, { Panic: true, Input: func() { Must2(func() (int, error) { return 0, errors.New("test error") }()) }, }, { Panic: false, Input: func() { Must(func() error { return nil }()) }, }, } for idx, test := range testCases { if hasPanic(test.Input) != test.Panic { t.Error("test case #", idx, " expect panic ", test.Panic, " but actually not") } } } ================================================ FILE: common/crypto/aes.go ================================================ package crypto import ( "crypto/aes" "crypto/cipher" "v2ray.com/core/common" ) // NewAesDecryptionStream creates a new AES encryption stream based on given key and IV. // Caller must ensure the length of key and IV is either 16, 24 or 32 bytes. func NewAesDecryptionStream(key []byte, iv []byte) cipher.Stream { return NewAesStreamMethod(key, iv, cipher.NewCFBDecrypter) } // NewAesEncryptionStream creates a new AES description stream based on given key and IV. // Caller must ensure the length of key and IV is either 16, 24 or 32 bytes. func NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream { return NewAesStreamMethod(key, iv, cipher.NewCFBEncrypter) } func NewAesStreamMethod(key []byte, iv []byte, f func(cipher.Block, []byte) cipher.Stream) cipher.Stream { aesBlock, err := aes.NewCipher(key) common.Must(err) return f(aesBlock, iv) } // NewAesCTRStream creates a stream cipher based on AES-CTR. func NewAesCTRStream(key []byte, iv []byte) cipher.Stream { return NewAesStreamMethod(key, iv, cipher.NewCTR) } // NewAesGcm creates a AEAD cipher based on AES-GCM. func NewAesGcm(key []byte) cipher.AEAD { block, err := aes.NewCipher(key) common.Must(err) aead, err := cipher.NewGCM(block) common.Must(err) return aead } ================================================ FILE: common/crypto/auth.go ================================================ package crypto import ( "crypto/cipher" "io" "math/rand" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/bytespool" "v2ray.com/core/common/protocol" ) type BytesGenerator func() []byte func GenerateEmptyBytes() BytesGenerator { var b [1]byte return func() []byte { return b[:0] } } func GenerateStaticBytes(content []byte) BytesGenerator { return func() []byte { return content } } func GenerateIncreasingNonce(nonce []byte) BytesGenerator { c := append([]byte(nil), nonce...) return func() []byte { for i := range c { c[i]++ if c[i] != 0 { break } } return c } } func GenerateInitialAEADNonce() BytesGenerator { return GenerateIncreasingNonce([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) } type Authenticator interface { NonceSize() int Overhead() int Open(dst, cipherText []byte) ([]byte, error) Seal(dst, plainText []byte) ([]byte, error) } type AEADAuthenticator struct { cipher.AEAD NonceGenerator BytesGenerator AdditionalDataGenerator BytesGenerator } func (v *AEADAuthenticator) Open(dst, cipherText []byte) ([]byte, error) { iv := v.NonceGenerator() if len(iv) != v.AEAD.NonceSize() { return nil, newError("invalid AEAD nonce size: ", len(iv)) } var additionalData []byte if v.AdditionalDataGenerator != nil { additionalData = v.AdditionalDataGenerator() } return v.AEAD.Open(dst, iv, cipherText, additionalData) } func (v *AEADAuthenticator) Seal(dst, plainText []byte) ([]byte, error) { iv := v.NonceGenerator() if len(iv) != v.AEAD.NonceSize() { return nil, newError("invalid AEAD nonce size: ", len(iv)) } var additionalData []byte if v.AdditionalDataGenerator != nil { additionalData = v.AdditionalDataGenerator() } return v.AEAD.Seal(dst, iv, plainText, additionalData), nil } type AuthenticationReader struct { auth Authenticator reader *buf.BufferedReader sizeParser ChunkSizeDecoder sizeBytes []byte transferType protocol.TransferType padding PaddingLengthGenerator size uint16 paddingLen uint16 hasSize bool done bool } func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType, paddingLen PaddingLengthGenerator) *AuthenticationReader { r := &AuthenticationReader{ auth: auth, sizeParser: sizeParser, transferType: transferType, padding: paddingLen, sizeBytes: make([]byte, sizeParser.SizeBytes()), } if breader, ok := reader.(*buf.BufferedReader); ok { r.reader = breader } else { r.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)} } return r } func (r *AuthenticationReader) readSize() (uint16, uint16, error) { if r.hasSize { r.hasSize = false return r.size, r.paddingLen, nil } if _, err := io.ReadFull(r.reader, r.sizeBytes); err != nil { return 0, 0, err } var padding uint16 if r.padding != nil { padding = r.padding.NextPaddingLen() } size, err := r.sizeParser.Decode(r.sizeBytes) return size, padding, err } var errSoft = newError("waiting for more data") func (r *AuthenticationReader) readBuffer(size int32, padding int32) (*buf.Buffer, error) { b := buf.New() if _, err := b.ReadFullFrom(r.reader, size); err != nil { b.Release() return nil, err } size -= padding rb, err := r.auth.Open(b.BytesTo(0), b.BytesTo(size)) if err != nil { b.Release() return nil, err } b.Resize(0, int32(len(rb))) return b, nil } func (r *AuthenticationReader) readInternal(soft bool, mb *buf.MultiBuffer) error { if soft && r.reader.BufferedBytes() < r.sizeParser.SizeBytes() { return errSoft } if r.done { return io.EOF } size, padding, err := r.readSize() if err != nil { return err } if size == uint16(r.auth.Overhead())+padding { r.done = true return io.EOF } if soft && int32(size) > r.reader.BufferedBytes() { r.size = size r.paddingLen = padding r.hasSize = true return errSoft } if size <= buf.Size { b, err := r.readBuffer(int32(size), int32(padding)) if err != nil { return nil } *mb = append(*mb, b) return nil } payload := bytespool.Alloc(int32(size)) defer bytespool.Free(payload) if _, err := io.ReadFull(r.reader, payload[:size]); err != nil { return err } size -= padding rb, err := r.auth.Open(payload[:0], payload[:size]) if err != nil { return err } *mb = buf.MergeBytes(*mb, rb) return nil } func (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) { const readSize = 16 mb := make(buf.MultiBuffer, 0, readSize) if err := r.readInternal(false, &mb); err != nil { buf.ReleaseMulti(mb) return nil, err } for i := 1; i < readSize; i++ { err := r.readInternal(true, &mb) if err == errSoft || err == io.EOF { break } if err != nil { buf.ReleaseMulti(mb) return nil, err } } return mb, nil } type AuthenticationWriter struct { auth Authenticator writer buf.Writer sizeParser ChunkSizeEncoder transferType protocol.TransferType padding PaddingLengthGenerator } func NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, writer io.Writer, transferType protocol.TransferType, padding PaddingLengthGenerator) *AuthenticationWriter { w := &AuthenticationWriter{ auth: auth, writer: buf.NewWriter(writer), sizeParser: sizeParser, transferType: transferType, } if padding != nil { w.padding = padding } return w } func (w *AuthenticationWriter) seal(b []byte) (*buf.Buffer, error) { encryptedSize := int32(len(b) + w.auth.Overhead()) var paddingSize int32 if w.padding != nil { paddingSize = int32(w.padding.NextPaddingLen()) } sizeBytes := w.sizeParser.SizeBytes() totalSize := sizeBytes + encryptedSize + paddingSize if totalSize > buf.Size { return nil, newError("size too large: ", totalSize) } eb := buf.New() w.sizeParser.Encode(uint16(encryptedSize+paddingSize), eb.Extend(sizeBytes)) if _, err := w.auth.Seal(eb.Extend(encryptedSize)[:0], b); err != nil { eb.Release() return nil, err } if paddingSize > 0 { // With size of the chunk and padding length encrypted, the content of padding doesn't matter much. paddingBytes := eb.Extend(paddingSize) common.Must2(rand.Read(paddingBytes)) } return eb, nil } func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error { defer buf.ReleaseMulti(mb) var maxPadding int32 if w.padding != nil { maxPadding = int32(w.padding.MaxPaddingLen()) } payloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes() - maxPadding mb2Write := make(buf.MultiBuffer, 0, len(mb)+10) temp := buf.New() defer temp.Release() rawBytes := temp.Extend(payloadSize) for { nb, nBytes := buf.SplitBytes(mb, rawBytes) mb = nb eb, err := w.seal(rawBytes[:nBytes]) if err != nil { buf.ReleaseMulti(mb2Write) return err } mb2Write = append(mb2Write, eb) if mb.IsEmpty() { break } } return w.writer.WriteMultiBuffer(mb2Write) } func (w *AuthenticationWriter) writePacket(mb buf.MultiBuffer) error { defer buf.ReleaseMulti(mb) mb2Write := make(buf.MultiBuffer, 0, len(mb)+1) for _, b := range mb { if b.IsEmpty() { continue } eb, err := w.seal(b.Bytes()) if err != nil { continue } mb2Write = append(mb2Write, eb) } if mb2Write.IsEmpty() { return nil } return w.writer.WriteMultiBuffer(mb2Write) } // WriteMultiBuffer implements buf.Writer. func (w *AuthenticationWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if mb.IsEmpty() { eb, err := w.seal([]byte{}) common.Must(err) return w.writer.WriteMultiBuffer(buf.MultiBuffer{eb}) } if w.transferType == protocol.TransferTypeStream { return w.writeStream(mb) } return w.writePacket(mb) } ================================================ FILE: common/crypto/auth_test.go ================================================ package crypto_test import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "io" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/common/crypto" "v2ray.com/core/common/protocol" ) func TestAuthenticationReaderWriter(t *testing.T) { key := make([]byte, 16) rand.Read(key) block, err := aes.NewCipher(key) common.Must(err) aead, err := cipher.NewGCM(block) common.Must(err) const payloadSize = 1024 * 80 rawPayload := make([]byte, payloadSize) rand.Read(rawPayload) payload := buf.MergeBytes(nil, rawPayload) cache := bytes.NewBuffer(nil) iv := make([]byte, 12) rand.Read(iv) writer := NewAuthenticationWriter(&AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil) common.Must(writer.WriteMultiBuffer(payload)) if cache.Len() <= 1024*80 { t.Error("cache len: ", cache.Len()) } common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{})) reader := NewAuthenticationReader(&AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil) var mb buf.MultiBuffer for mb.Len() < payloadSize { mb2, err := reader.ReadMultiBuffer() common.Must(err) mb, _ = buf.MergeMulti(mb, mb2) } if mb.Len() != payloadSize { t.Error("mb len: ", mb.Len()) } mbContent := make([]byte, payloadSize) buf.SplitBytes(mb, mbContent) if r := cmp.Diff(mbContent, rawPayload); r != "" { t.Error(r) } _, err = reader.ReadMultiBuffer() if err != io.EOF { t.Error("error: ", err) } } func TestAuthenticationReaderWriterPacket(t *testing.T) { key := make([]byte, 16) common.Must2(rand.Read(key)) block, err := aes.NewCipher(key) common.Must(err) aead, err := cipher.NewGCM(block) common.Must(err) cache := buf.New() iv := make([]byte, 12) rand.Read(iv) writer := NewAuthenticationWriter(&AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil) var payload buf.MultiBuffer pb1 := buf.New() pb1.Write([]byte("abcd")) payload = append(payload, pb1) pb2 := buf.New() pb2.Write([]byte("efgh")) payload = append(payload, pb2) common.Must(writer.WriteMultiBuffer(payload)) if cache.Len() == 0 { t.Error("cache len: ", cache.Len()) } common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{})) reader := NewAuthenticationReader(&AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil) mb, err := reader.ReadMultiBuffer() common.Must(err) mb, b1 := buf.SplitFirst(mb) if b1.String() != "abcd" { t.Error("b1: ", b1.String()) } mb, b2 := buf.SplitFirst(mb) if b2.String() != "efgh" { t.Error("b2: ", b2.String()) } if !mb.IsEmpty() { t.Error("not empty") } _, err = reader.ReadMultiBuffer() if err != io.EOF { t.Error("error: ", err) } } ================================================ FILE: common/crypto/benchmark_test.go ================================================ package crypto_test import ( "crypto/cipher" "testing" . "v2ray.com/core/common/crypto" ) const benchSize = 1024 * 1024 func benchmarkStream(b *testing.B, c cipher.Stream) { b.SetBytes(benchSize) input := make([]byte, benchSize) output := make([]byte, benchSize) b.ResetTimer() for i := 0; i < b.N; i++ { c.XORKeyStream(output, input) } } func BenchmarkChaCha20(b *testing.B) { key := make([]byte, 32) nonce := make([]byte, 8) c := NewChaCha20Stream(key, nonce) benchmarkStream(b, c) } func BenchmarkChaCha20IETF(b *testing.B) { key := make([]byte, 32) nonce := make([]byte, 12) c := NewChaCha20Stream(key, nonce) benchmarkStream(b, c) } func BenchmarkAESEncryption(b *testing.B) { key := make([]byte, 32) iv := make([]byte, 16) c := NewAesEncryptionStream(key, iv) benchmarkStream(b, c) } func BenchmarkAESDecryption(b *testing.B) { key := make([]byte, 32) iv := make([]byte, 16) c := NewAesDecryptionStream(key, iv) benchmarkStream(b, c) } ================================================ FILE: common/crypto/chacha20.go ================================================ package crypto import ( "crypto/cipher" "v2ray.com/core/common/crypto/internal" ) // NewChaCha20Stream creates a new Chacha20 encryption/descryption stream based on give key and IV. // Caller must ensure the length of key is 32 bytes, and length of IV is either 8 or 12 bytes. func NewChaCha20Stream(key []byte, iv []byte) cipher.Stream { return internal.NewChaCha20Stream(key, iv, 20) } ================================================ FILE: common/crypto/chacha20_test.go ================================================ package crypto_test import ( "crypto/rand" "encoding/hex" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/common/crypto" ) func mustDecodeHex(s string) []byte { b, err := hex.DecodeString(s) common.Must(err) return b } func TestChaCha20Stream(t *testing.T) { var cases = []struct { key []byte iv []byte output []byte }{ { key: mustDecodeHex("0000000000000000000000000000000000000000000000000000000000000000"), iv: mustDecodeHex("0000000000000000"), output: mustDecodeHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586" + "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" + "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"), }, { key: mustDecodeHex("5555555555555555555555555555555555555555555555555555555555555555"), iv: mustDecodeHex("5555555555555555"), output: mustDecodeHex("bea9411aa453c5434a5ae8c92862f564396855a9ea6e22d6d3b50ae1b3663311" + "a4a3606c671d605ce16c3aece8e61ea145c59775017bee2fa6f88afc758069f7" + "e0b8f676e644216f4d2a3422d7fa36c6c4931aca950e9da42788e6d0b6d1cd83" + "8ef652e97b145b14871eae6c6804c7004db5ac2fce4c68c726d004b10fcaba86"), }, { key: mustDecodeHex("0000000000000000000000000000000000000000000000000000000000000000"), iv: mustDecodeHex("000000000000000000000000"), output: mustDecodeHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"), }, } for _, c := range cases { s := NewChaCha20Stream(c.key, c.iv) input := make([]byte, len(c.output)) actualOutout := make([]byte, len(c.output)) s.XORKeyStream(actualOutout, input) if r := cmp.Diff(c.output, actualOutout); r != "" { t.Fatal(r) } } } func TestChaCha20Decoding(t *testing.T) { key := make([]byte, 32) common.Must2(rand.Read(key)) iv := make([]byte, 8) common.Must2(rand.Read(iv)) stream := NewChaCha20Stream(key, iv) payload := make([]byte, 1024) common.Must2(rand.Read(payload)) x := make([]byte, len(payload)) stream.XORKeyStream(x, payload) stream2 := NewChaCha20Stream(key, iv) stream2.XORKeyStream(x, x) if r := cmp.Diff(x, payload); r != "" { t.Fatal(r) } } ================================================ FILE: common/crypto/chunk.go ================================================ package crypto import ( "encoding/binary" "io" "v2ray.com/core/common" "v2ray.com/core/common/buf" ) // ChunkSizeDecoder is a utility class to decode size value from bytes. type ChunkSizeDecoder interface { SizeBytes() int32 Decode([]byte) (uint16, error) } // ChunkSizeEncoder is a utility class to encode size value into bytes. type ChunkSizeEncoder interface { SizeBytes() int32 Encode(uint16, []byte) []byte } type PaddingLengthGenerator interface { MaxPaddingLen() uint16 NextPaddingLen() uint16 } type PlainChunkSizeParser struct{} func (PlainChunkSizeParser) SizeBytes() int32 { return 2 } func (PlainChunkSizeParser) Encode(size uint16, b []byte) []byte { binary.BigEndian.PutUint16(b, size) return b[:2] } func (PlainChunkSizeParser) Decode(b []byte) (uint16, error) { return binary.BigEndian.Uint16(b), nil } type AEADChunkSizeParser struct { Auth *AEADAuthenticator } func (p *AEADChunkSizeParser) SizeBytes() int32 { return 2 + int32(p.Auth.Overhead()) } func (p *AEADChunkSizeParser) Encode(size uint16, b []byte) []byte { binary.BigEndian.PutUint16(b, size-uint16(p.Auth.Overhead())) b, err := p.Auth.Seal(b[:0], b[:2]) common.Must(err) return b } func (p *AEADChunkSizeParser) Decode(b []byte) (uint16, error) { b, err := p.Auth.Open(b[:0], b) if err != nil { return 0, err } return binary.BigEndian.Uint16(b) + uint16(p.Auth.Overhead()), nil } type ChunkStreamReader struct { sizeDecoder ChunkSizeDecoder reader *buf.BufferedReader buffer []byte leftOverSize int32 maxNumChunk uint32 numChunk uint32 } func NewChunkStreamReader(sizeDecoder ChunkSizeDecoder, reader io.Reader) *ChunkStreamReader { return NewChunkStreamReaderWithChunkCount(sizeDecoder, reader, 0) } func NewChunkStreamReaderWithChunkCount(sizeDecoder ChunkSizeDecoder, reader io.Reader, maxNumChunk uint32) *ChunkStreamReader { r := &ChunkStreamReader{ sizeDecoder: sizeDecoder, buffer: make([]byte, sizeDecoder.SizeBytes()), maxNumChunk: maxNumChunk, } if breader, ok := reader.(*buf.BufferedReader); ok { r.reader = breader } else { r.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)} } return r } func (r *ChunkStreamReader) readSize() (uint16, error) { if _, err := io.ReadFull(r.reader, r.buffer); err != nil { return 0, err } return r.sizeDecoder.Decode(r.buffer) } func (r *ChunkStreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) { size := r.leftOverSize if size == 0 { r.numChunk++ if r.maxNumChunk > 0 && r.numChunk > r.maxNumChunk { return nil, io.EOF } nextSize, err := r.readSize() if err != nil { return nil, err } if nextSize == 0 { return nil, io.EOF } size = int32(nextSize) } r.leftOverSize = size mb, err := r.reader.ReadAtMost(size) if !mb.IsEmpty() { r.leftOverSize -= mb.Len() return mb, nil } return nil, err } type ChunkStreamWriter struct { sizeEncoder ChunkSizeEncoder writer buf.Writer } func NewChunkStreamWriter(sizeEncoder ChunkSizeEncoder, writer io.Writer) *ChunkStreamWriter { return &ChunkStreamWriter{ sizeEncoder: sizeEncoder, writer: buf.NewWriter(writer), } } func (w *ChunkStreamWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { const sliceSize = 8192 mbLen := mb.Len() mb2Write := make(buf.MultiBuffer, 0, mbLen/buf.Size+mbLen/sliceSize+2) for { mb2, slice := buf.SplitSize(mb, sliceSize) mb = mb2 b := buf.New() w.sizeEncoder.Encode(uint16(slice.Len()), b.Extend(w.sizeEncoder.SizeBytes())) mb2Write = append(mb2Write, b) mb2Write = append(mb2Write, slice...) if mb.IsEmpty() { break } } return w.writer.WriteMultiBuffer(mb2Write) } ================================================ FILE: common/crypto/chunk_test.go ================================================ package crypto_test import ( "bytes" "io" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/common/crypto" ) func TestChunkStreamIO(t *testing.T) { cache := bytes.NewBuffer(make([]byte, 0, 8192)) writer := NewChunkStreamWriter(PlainChunkSizeParser{}, cache) reader := NewChunkStreamReader(PlainChunkSizeParser{}, cache) b := buf.New() b.WriteString("abcd") common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b})) b = buf.New() b.WriteString("efg") common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b})) common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{})) if cache.Len() != 13 { t.Fatalf("Cache length is %d, want 13", cache.Len()) } mb, err := reader.ReadMultiBuffer() common.Must(err) if s := mb.String(); s != "abcd" { t.Error("content: ", s) } mb, err = reader.ReadMultiBuffer() common.Must(err) if s := mb.String(); s != "efg" { t.Error("content: ", s) } _, err = reader.ReadMultiBuffer() if err != io.EOF { t.Error("error: ", err) } } ================================================ FILE: common/crypto/crypto.go ================================================ // Package crypto provides common crypto libraries for V2Ray. package crypto // import "v2ray.com/core/common/crypto" //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: common/crypto/errors.generated.go ================================================ package crypto import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/crypto/internal/chacha.go ================================================ package internal //go:generate go run chacha_core_gen.go import ( "encoding/binary" ) const ( wordSize = 4 // the size of ChaCha20's words stateSize = 16 // the size of ChaCha20's state, in words blockSize = stateSize * wordSize // the size of ChaCha20's block, in bytes ) type ChaCha20Stream struct { state [stateSize]uint32 // the state as an array of 16 32-bit words block [blockSize]byte // the keystream as an array of 64 bytes offset int // the offset of used bytes in block rounds int } func NewChaCha20Stream(key []byte, nonce []byte, rounds int) *ChaCha20Stream { s := new(ChaCha20Stream) // the magic constants for 256-bit keys s.state[0] = 0x61707865 s.state[1] = 0x3320646e s.state[2] = 0x79622d32 s.state[3] = 0x6b206574 for i := 0; i < 8; i++ { s.state[i+4] = binary.LittleEndian.Uint32(key[i*4 : i*4+4]) } switch len(nonce) { case 8: s.state[14] = binary.LittleEndian.Uint32(nonce[0:]) s.state[15] = binary.LittleEndian.Uint32(nonce[4:]) case 12: s.state[13] = binary.LittleEndian.Uint32(nonce[0:4]) s.state[14] = binary.LittleEndian.Uint32(nonce[4:8]) s.state[15] = binary.LittleEndian.Uint32(nonce[8:12]) default: panic("bad nonce length") } s.rounds = rounds ChaCha20Block(&s.state, s.block[:], s.rounds) return s } func (s *ChaCha20Stream) XORKeyStream(dst, src []byte) { // Stride over the input in 64-byte blocks, minus the amount of keystream // previously used. This will produce best results when processing blocks // of a size evenly divisible by 64. i := 0 max := len(src) for i < max { gap := blockSize - s.offset limit := i + gap if limit > max { limit = max } o := s.offset for j := i; j < limit; j++ { dst[j] = src[j] ^ s.block[o] o++ } i += gap s.offset = o if o == blockSize { s.offset = 0 s.state[12]++ ChaCha20Block(&s.state, s.block[:], s.rounds) } } } ================================================ FILE: common/crypto/internal/chacha_core.generated.go ================================================ package internal import "encoding/binary" func ChaCha20Block(s *[16]uint32, out []byte, rounds int) { var x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 = s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15] for i := 0; i < rounds; i += 2 { var x uint32 x0 += x4 x = x12 ^ x0 x12 = (x << 16) | (x >> (32 - 16)) x8 += x12 x = x4 ^ x8 x4 = (x << 12) | (x >> (32 - 12)) x0 += x4 x = x12 ^ x0 x12 = (x << 8) | (x >> (32 - 8)) x8 += x12 x = x4 ^ x8 x4 = (x << 7) | (x >> (32 - 7)) x1 += x5 x = x13 ^ x1 x13 = (x << 16) | (x >> (32 - 16)) x9 += x13 x = x5 ^ x9 x5 = (x << 12) | (x >> (32 - 12)) x1 += x5 x = x13 ^ x1 x13 = (x << 8) | (x >> (32 - 8)) x9 += x13 x = x5 ^ x9 x5 = (x << 7) | (x >> (32 - 7)) x2 += x6 x = x14 ^ x2 x14 = (x << 16) | (x >> (32 - 16)) x10 += x14 x = x6 ^ x10 x6 = (x << 12) | (x >> (32 - 12)) x2 += x6 x = x14 ^ x2 x14 = (x << 8) | (x >> (32 - 8)) x10 += x14 x = x6 ^ x10 x6 = (x << 7) | (x >> (32 - 7)) x3 += x7 x = x15 ^ x3 x15 = (x << 16) | (x >> (32 - 16)) x11 += x15 x = x7 ^ x11 x7 = (x << 12) | (x >> (32 - 12)) x3 += x7 x = x15 ^ x3 x15 = (x << 8) | (x >> (32 - 8)) x11 += x15 x = x7 ^ x11 x7 = (x << 7) | (x >> (32 - 7)) x0 += x5 x = x15 ^ x0 x15 = (x << 16) | (x >> (32 - 16)) x10 += x15 x = x5 ^ x10 x5 = (x << 12) | (x >> (32 - 12)) x0 += x5 x = x15 ^ x0 x15 = (x << 8) | (x >> (32 - 8)) x10 += x15 x = x5 ^ x10 x5 = (x << 7) | (x >> (32 - 7)) x1 += x6 x = x12 ^ x1 x12 = (x << 16) | (x >> (32 - 16)) x11 += x12 x = x6 ^ x11 x6 = (x << 12) | (x >> (32 - 12)) x1 += x6 x = x12 ^ x1 x12 = (x << 8) | (x >> (32 - 8)) x11 += x12 x = x6 ^ x11 x6 = (x << 7) | (x >> (32 - 7)) x2 += x7 x = x13 ^ x2 x13 = (x << 16) | (x >> (32 - 16)) x8 += x13 x = x7 ^ x8 x7 = (x << 12) | (x >> (32 - 12)) x2 += x7 x = x13 ^ x2 x13 = (x << 8) | (x >> (32 - 8)) x8 += x13 x = x7 ^ x8 x7 = (x << 7) | (x >> (32 - 7)) x3 += x4 x = x14 ^ x3 x14 = (x << 16) | (x >> (32 - 16)) x9 += x14 x = x4 ^ x9 x4 = (x << 12) | (x >> (32 - 12)) x3 += x4 x = x14 ^ x3 x14 = (x << 8) | (x >> (32 - 8)) x9 += x14 x = x4 ^ x9 x4 = (x << 7) | (x >> (32 - 7)) } binary.LittleEndian.PutUint32(out[0:4], s[0]+x0) binary.LittleEndian.PutUint32(out[4:8], s[1]+x1) binary.LittleEndian.PutUint32(out[8:12], s[2]+x2) binary.LittleEndian.PutUint32(out[12:16], s[3]+x3) binary.LittleEndian.PutUint32(out[16:20], s[4]+x4) binary.LittleEndian.PutUint32(out[20:24], s[5]+x5) binary.LittleEndian.PutUint32(out[24:28], s[6]+x6) binary.LittleEndian.PutUint32(out[28:32], s[7]+x7) binary.LittleEndian.PutUint32(out[32:36], s[8]+x8) binary.LittleEndian.PutUint32(out[36:40], s[9]+x9) binary.LittleEndian.PutUint32(out[40:44], s[10]+x10) binary.LittleEndian.PutUint32(out[44:48], s[11]+x11) binary.LittleEndian.PutUint32(out[48:52], s[12]+x12) binary.LittleEndian.PutUint32(out[52:56], s[13]+x13) binary.LittleEndian.PutUint32(out[56:60], s[14]+x14) binary.LittleEndian.PutUint32(out[60:64], s[15]+x15) } ================================================ FILE: common/crypto/internal/chacha_core_gen.go ================================================ // +build generate package main import ( "fmt" "log" "os" ) func writeQuarterRound(file *os.File, a, b, c, d int) { add := "x%d+=x%d\n" xor := "x=x%d^x%d\n" rotate := "x%d=(x << %d) | (x >> (32 - %d))\n" fmt.Fprintf(file, add, a, b) fmt.Fprintf(file, xor, d, a) fmt.Fprintf(file, rotate, d, 16, 16) fmt.Fprintf(file, add, c, d) fmt.Fprintf(file, xor, b, c) fmt.Fprintf(file, rotate, b, 12, 12) fmt.Fprintf(file, add, a, b) fmt.Fprintf(file, xor, d, a) fmt.Fprintf(file, rotate, d, 8, 8) fmt.Fprintf(file, add, c, d) fmt.Fprintf(file, xor, b, c) fmt.Fprintf(file, rotate, b, 7, 7) } func writeChacha20Block(file *os.File) { fmt.Fprintln(file, ` func ChaCha20Block(s *[16]uint32, out []byte, rounds int) { var x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15 = s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9],s[10],s[11],s[12],s[13],s[14],s[15] for i := 0; i < rounds; i+=2 { var x uint32 `) writeQuarterRound(file, 0, 4, 8, 12) writeQuarterRound(file, 1, 5, 9, 13) writeQuarterRound(file, 2, 6, 10, 14) writeQuarterRound(file, 3, 7, 11, 15) writeQuarterRound(file, 0, 5, 10, 15) writeQuarterRound(file, 1, 6, 11, 12) writeQuarterRound(file, 2, 7, 8, 13) writeQuarterRound(file, 3, 4, 9, 14) fmt.Fprintln(file, "}") for i := 0; i < 16; i++ { fmt.Fprintf(file, "binary.LittleEndian.PutUint32(out[%d:%d], s[%d]+x%d)\n", i*4, i*4+4, i, i) } fmt.Fprintln(file, "}") fmt.Fprintln(file) } func main() { file, err := os.OpenFile("chacha_core.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) if err != nil { log.Fatalf("Failed to generate chacha_core.go: %v", err) } defer file.Close() fmt.Fprintln(file, "package internal") fmt.Fprintln(file) fmt.Fprintln(file, "import \"encoding/binary\"") fmt.Fprintln(file) writeChacha20Block(file) } ================================================ FILE: common/crypto/io.go ================================================ package crypto import ( "crypto/cipher" "io" "v2ray.com/core/common/buf" ) type CryptionReader struct { stream cipher.Stream reader io.Reader } func NewCryptionReader(stream cipher.Stream, reader io.Reader) *CryptionReader { return &CryptionReader{ stream: stream, reader: reader, } } func (r *CryptionReader) Read(data []byte) (int, error) { nBytes, err := r.reader.Read(data) if nBytes > 0 { r.stream.XORKeyStream(data[:nBytes], data[:nBytes]) } return nBytes, err } var ( _ buf.Writer = (*CryptionWriter)(nil) ) type CryptionWriter struct { stream cipher.Stream writer io.Writer bufWriter buf.Writer } // NewCryptionWriter creates a new CryptionWriter. func NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter { return &CryptionWriter{ stream: stream, writer: writer, bufWriter: buf.NewWriter(writer), } } // Write implements io.Writer.Write(). func (w *CryptionWriter) Write(data []byte) (int, error) { w.stream.XORKeyStream(data, data) if err := buf.WriteAllBytes(w.writer, data); err != nil { return 0, err } return len(data), nil } // WriteMultiBuffer implements buf.Writer. func (w *CryptionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { for _, b := range mb { w.stream.XORKeyStream(b.Bytes(), b.Bytes()) } return w.bufWriter.WriteMultiBuffer(mb) } ================================================ FILE: common/dice/dice.go ================================================ // Package dice contains common functions to generate random number. // It also initialize math/rand with the time in seconds at launch time. package dice // import "v2ray.com/core/common/dice" import ( "math/rand" "time" ) // Roll returns a non-negative number between 0 (inclusive) and n (exclusive). func Roll(n int) int { if n == 1 { return 0 } return rand.Intn(n) } // Roll returns a non-negative number between 0 (inclusive) and n (exclusive). func RollDeterministic(n int, seed int64) int { if n == 1 { return 0 } return rand.New(rand.NewSource(seed)).Intn(n) } // RollUint16 returns a random uint16 value. func RollUint16() uint16 { return uint16(rand.Intn(65536)) } func RollUint64() uint64 { return rand.Uint64() } func NewDeterministicDice(seed int64) *deterministicDice { return &deterministicDice{rand.New(rand.NewSource(seed))} } type deterministicDice struct { *rand.Rand } func (dd *deterministicDice) Roll(n int) int { if n == 1 { return 0 } return dd.Intn(n) } func init() { rand.Seed(time.Now().Unix()) } ================================================ FILE: common/dice/dice_test.go ================================================ package dice_test import ( "math/rand" "testing" . "v2ray.com/core/common/dice" ) func BenchmarkRoll1(b *testing.B) { for i := 0; i < b.N; i++ { Roll(1) } } func BenchmarkRoll20(b *testing.B) { for i := 0; i < b.N; i++ { Roll(20) } } func BenchmarkIntn1(b *testing.B) { for i := 0; i < b.N; i++ { rand.Intn(1) } } func BenchmarkIntn20(b *testing.B) { for i := 0; i < b.N; i++ { rand.Intn(20) } } ================================================ FILE: common/errors/errorgen/main.go ================================================ package main import ( "fmt" "log" "os" "path/filepath" "v2ray.com/core/common" ) func main() { pwd, err := os.Getwd() if err != nil { fmt.Println("can not get current working directory") os.Exit(1) } pkg := filepath.Base(pwd) if pkg == "v2ray-core" { pkg = "core" } moduleName, gmnErr := common.GetModuleName(pwd) if gmnErr != nil { fmt.Println("can not get module path", gmnErr) os.Exit(1) } file, err := os.OpenFile("errors.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) if err != nil { log.Fatalf("Failed to generate errors.generated.go: %v", err) os.Exit(1) } defer file.Close() fmt.Fprintln(file, "package", pkg) fmt.Fprintln(file, "") fmt.Fprintln(file, "import \""+moduleName+"/common/errors\"") fmt.Fprintln(file, "") fmt.Fprintln(file, "type errPathObjHolder struct{}") fmt.Fprintln(file, "") fmt.Fprintln(file, "func newError(values ...interface{}) *errors.Error {") fmt.Fprintln(file, " return errors.New(values...).WithPathObj(errPathObjHolder{})") fmt.Fprintln(file, "}") } ================================================ FILE: common/errors/errors.go ================================================ // Package errors is a drop-in replacement for Golang lib 'errors'. package errors // import "v2ray.com/core/common/errors" import ( "os" "reflect" "strings" "v2ray.com/core/common/log" "v2ray.com/core/common/serial" ) type hasInnerError interface { // Inner returns the underlying error of this one. Inner() error } type hasSeverity interface { Severity() log.Severity } // Error is an error object with underlying error. type Error struct { pathObj interface{} prefix []interface{} message []interface{} inner error severity log.Severity } func (err *Error) WithPathObj(obj interface{}) *Error { err.pathObj = obj return err } func (err *Error) pkgPath() string { if err.pathObj == nil { return "" } return reflect.TypeOf(err.pathObj).PkgPath() } // Error implements error.Error(). func (err *Error) Error() string { builder := strings.Builder{} for _, prefix := range err.prefix { builder.WriteByte('[') builder.WriteString(serial.ToString(prefix)) builder.WriteString("] ") } path := err.pkgPath() if len(path) > 0 { builder.WriteString(path) builder.WriteString(": ") } msg := serial.Concat(err.message...) builder.WriteString(msg) if err.inner != nil { builder.WriteString(" > ") builder.WriteString(err.inner.Error()) } return builder.String() } // Inner implements hasInnerError.Inner() func (err *Error) Inner() error { if err.inner == nil { return nil } return err.inner } func (err *Error) Base(e error) *Error { err.inner = e return err } func (err *Error) atSeverity(s log.Severity) *Error { err.severity = s return err } func (err *Error) Severity() log.Severity { if err.inner == nil { return err.severity } if s, ok := err.inner.(hasSeverity); ok { as := s.Severity() if as < err.severity { return as } } return err.severity } // AtDebug sets the severity to debug. func (err *Error) AtDebug() *Error { return err.atSeverity(log.Severity_Debug) } // AtInfo sets the severity to info. func (err *Error) AtInfo() *Error { return err.atSeverity(log.Severity_Info) } // AtWarning sets the severity to warning. func (err *Error) AtWarning() *Error { return err.atSeverity(log.Severity_Warning) } // AtError sets the severity to error. func (err *Error) AtError() *Error { return err.atSeverity(log.Severity_Error) } // String returns the string representation of this error. func (err *Error) String() string { return err.Error() } // WriteToLog writes current error into log. func (err *Error) WriteToLog(opts ...ExportOption) { var holder ExportOptionHolder for _, opt := range opts { opt(&holder) } if holder.SessionID > 0 { err.prefix = append(err.prefix, holder.SessionID) } log.Record(&log.GeneralMessage{ Severity: GetSeverity(err), Content: err, }) } type ExportOptionHolder struct { SessionID uint32 } type ExportOption func(*ExportOptionHolder) // New returns a new error object with message formed from given arguments. func New(msg ...interface{}) *Error { return &Error{ message: msg, severity: log.Severity_Info, } } // Cause returns the root cause of this error. func Cause(err error) error { if err == nil { return nil } L: for { switch inner := err.(type) { case hasInnerError: if inner.Inner() == nil { break L } err = inner.Inner() case *os.PathError: if inner.Err == nil { break L } err = inner.Err case *os.SyscallError: if inner.Err == nil { break L } err = inner.Err default: break L } } return err } // GetSeverity returns the actual severity of the error, including inner errors. func GetSeverity(err error) log.Severity { if s, ok := err.(hasSeverity); ok { return s.Severity() } return log.Severity_Info } ================================================ FILE: common/errors/errors_test.go ================================================ package errors_test import ( "io" "strings" "testing" "github.com/google/go-cmp/cmp" . "v2ray.com/core/common/errors" "v2ray.com/core/common/log" ) func TestError(t *testing.T) { err := New("TestError") if v := GetSeverity(err); v != log.Severity_Info { t.Error("severity: ", v) } err = New("TestError2").Base(io.EOF) if v := GetSeverity(err); v != log.Severity_Info { t.Error("severity: ", v) } err = New("TestError3").Base(io.EOF).AtWarning() if v := GetSeverity(err); v != log.Severity_Warning { t.Error("severity: ", v) } err = New("TestError4").Base(io.EOF).AtWarning() err = New("TestError5").Base(err) if v := GetSeverity(err); v != log.Severity_Warning { t.Error("severity: ", v) } if v := err.Error(); !strings.Contains(v, "EOF") { t.Error("error: ", v) } } type e struct{} func TestErrorMessage(t *testing.T) { data := []struct { err error msg string }{ { err: New("a").Base(New("b")).WithPathObj(e{}), msg: "v2ray.com/core/common/errors_test: a > b", }, { err: New("a").Base(New("b").WithPathObj(e{})), msg: "a > v2ray.com/core/common/errors_test: b", }, } for _, d := range data { if diff := cmp.Diff(d.msg, d.err.Error()); diff != "" { t.Error(diff) } } } ================================================ FILE: common/errors/multi_error.go ================================================ package errors import ( "strings" ) type multiError []error func (e multiError) Error() string { var r strings.Builder r.WriteString("multierr: ") for _, err := range e { r.WriteString(err.Error()) r.WriteString(" | ") } return r.String() } func Combine(maybeError ...error) error { var errs multiError for _, err := range maybeError { if err != nil { errs = append(errs, err) } } if len(errs) == 0 { return nil } return errs } ================================================ FILE: common/errors.generated.go ================================================ package common import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/interfaces.go ================================================ package common import "v2ray.com/core/common/errors" // Closable is the interface for objects that can release its resources. // // v2ray:api:beta type Closable interface { // Close release all resources used by this object, including goroutines. Close() error } // Interruptible is an interface for objects that can be stopped before its completion. // // v2ray:api:beta type Interruptible interface { Interrupt() } // Close closes the obj if it is a Closable. // // v2ray:api:beta func Close(obj interface{}) error { if c, ok := obj.(Closable); ok { return c.Close() } return nil } // Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface. // // v2ray:api:beta func Interrupt(obj interface{}) error { if c, ok := obj.(Interruptible); ok { c.Interrupt() return nil } return Close(obj) } // Runnable is the interface for objects that can start to work and stop on demand. type Runnable interface { // Start starts the runnable object. Upon the method returning nil, the object begins to function properly. Start() error Closable } // HasType is the interface for objects that knows its type. type HasType interface { // Type returns the type of the object. // Usually it returns (*Type)(nil) of the object. Type() interface{} } // ChainedClosable is a Closable that consists of multiple Closable objects. type ChainedClosable []Closable // Close implements Closable. func (cc ChainedClosable) Close() error { var errs []error for _, c := range cc { if err := c.Close(); err != nil { errs = append(errs, err) } } return errors.Combine(errs...) } ================================================ FILE: common/log/access.go ================================================ package log import ( "context" "strings" "v2ray.com/core/common/serial" ) type logKey int const ( accessMessageKey logKey = iota ) type AccessStatus string const ( AccessAccepted = AccessStatus("accepted") AccessRejected = AccessStatus("rejected") ) type AccessMessage struct { From interface{} To interface{} Status AccessStatus Reason interface{} Email string Detour string } func (m *AccessMessage) String() string { builder := strings.Builder{} builder.WriteString(serial.ToString(m.From)) builder.WriteByte(' ') builder.WriteString(string(m.Status)) builder.WriteByte(' ') builder.WriteString(serial.ToString(m.To)) builder.WriteByte(' ') if len(m.Detour) > 0 { builder.WriteByte('[') builder.WriteString(m.Detour) builder.WriteString("] ") } builder.WriteString(serial.ToString(m.Reason)) if len(m.Email) > 0 { builder.WriteString("email:") builder.WriteString(m.Email) builder.WriteByte(' ') } return builder.String() } func ContextWithAccessMessage(ctx context.Context, accessMessage *AccessMessage) context.Context { return context.WithValue(ctx, accessMessageKey, accessMessage) } func AccessMessageFromContext(ctx context.Context) *AccessMessage { if accessMessage, ok := ctx.Value(accessMessageKey).(*AccessMessage); ok { return accessMessage } return nil } ================================================ FILE: common/log/log.go ================================================ package log // import "v2ray.com/core/common/log" import ( "sync" "v2ray.com/core/common/serial" ) // Message is the interface for all log messages. type Message interface { String() string } // Handler is the interface for log handler. type Handler interface { Handle(msg Message) } // GeneralMessage is a general log message that can contain all kind of content. type GeneralMessage struct { Severity Severity Content interface{} } // String implements Message. func (m *GeneralMessage) String() string { return serial.Concat("[", m.Severity, "] ", m.Content) } // Record writes a message into log stream. func Record(msg Message) { logHandler.Handle(msg) } var ( logHandler syncHandler ) // RegisterHandler register a new handler as current log handler. Previous registered handler will be discarded. func RegisterHandler(handler Handler) { if handler == nil { panic("Log handler is nil") } logHandler.Set(handler) } type syncHandler struct { sync.RWMutex Handler } func (h *syncHandler) Handle(msg Message) { h.RLock() defer h.RUnlock() if h.Handler != nil { h.Handler.Handle(msg) } } func (h *syncHandler) Set(handler Handler) { h.Lock() defer h.Unlock() h.Handler = handler } ================================================ FILE: common/log/log.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/log/log.proto package log import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Severity int32 const ( Severity_Unknown Severity = 0 Severity_Error Severity = 1 Severity_Warning Severity = 2 Severity_Info Severity = 3 Severity_Debug Severity = 4 ) // Enum value maps for Severity. var ( Severity_name = map[int32]string{ 0: "Unknown", 1: "Error", 2: "Warning", 3: "Info", 4: "Debug", } Severity_value = map[string]int32{ "Unknown": 0, "Error": 1, "Warning": 2, "Info": 3, "Debug": 4, } ) func (x Severity) Enum() *Severity { p := new(Severity) *p = x return p } func (x Severity) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Severity) Descriptor() protoreflect.EnumDescriptor { return file_common_log_log_proto_enumTypes[0].Descriptor() } func (Severity) Type() protoreflect.EnumType { return &file_common_log_log_proto_enumTypes[0] } func (x Severity) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Severity.Descriptor instead. func (Severity) EnumDescriptor() ([]byte, []int) { return file_common_log_log_proto_rawDescGZIP(), []int{0} } var File_common_log_log_proto protoreflect.FileDescriptor var file_common_log_log_proto_rawDesc = []byte{ 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6c, 0x6f, 0x67, 0x2a, 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x04, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6c, 0x6f, 0x67, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6c, 0x6f, 0x67, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_log_log_proto_rawDescOnce sync.Once file_common_log_log_proto_rawDescData = file_common_log_log_proto_rawDesc ) func file_common_log_log_proto_rawDescGZIP() []byte { file_common_log_log_proto_rawDescOnce.Do(func() { file_common_log_log_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_log_log_proto_rawDescData) }) return file_common_log_log_proto_rawDescData } var file_common_log_log_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_common_log_log_proto_goTypes = []interface{}{ (Severity)(0), // 0: v2ray.core.common.log.Severity } var file_common_log_log_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_common_log_log_proto_init() } func file_common_log_log_proto_init() { if File_common_log_log_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_log_log_proto_rawDesc, NumEnums: 1, NumMessages: 0, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_log_log_proto_goTypes, DependencyIndexes: file_common_log_log_proto_depIdxs, EnumInfos: file_common_log_log_proto_enumTypes, }.Build() File_common_log_log_proto = out.File file_common_log_log_proto_rawDesc = nil file_common_log_log_proto_goTypes = nil file_common_log_log_proto_depIdxs = nil } ================================================ FILE: common/log/log.proto ================================================ syntax = "proto3"; package v2ray.core.common.log; option csharp_namespace = "V2Ray.Core.Common.Log"; option go_package = "v2ray.com/core/common/log"; option java_package = "com.v2ray.core.common.log"; option java_multiple_files = true; enum Severity { Unknown = 0; Error = 1; Warning = 2; Info = 3; Debug = 4; } ================================================ FILE: common/log/log_test.go ================================================ package log_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common/log" "v2ray.com/core/common/net" ) type testLogger struct { value string } func (l *testLogger) Handle(msg log.Message) { l.value = msg.String() } func TestLogRecord(t *testing.T) { var logger testLogger log.RegisterHandler(&logger) ip := "8.8.8.8" log.Record(&log.GeneralMessage{ Severity: log.Severity_Error, Content: net.ParseAddress(ip), }) if diff := cmp.Diff("[Error] "+ip, logger.value); diff != "" { t.Error(diff) } } ================================================ FILE: common/log/logger.go ================================================ package log import ( "io" "log" "os" "time" "v2ray.com/core/common/platform" "v2ray.com/core/common/signal/done" "v2ray.com/core/common/signal/semaphore" ) // Writer is the interface for writing logs. type Writer interface { Write(string) error io.Closer } // WriterCreator is a function to create LogWriters. type WriterCreator func() Writer type generalLogger struct { creator WriterCreator buffer chan Message access *semaphore.Instance done *done.Instance } // NewLogger returns a generic log handler that can handle all type of messages. func NewLogger(logWriterCreator WriterCreator) Handler { return &generalLogger{ creator: logWriterCreator, buffer: make(chan Message, 16), access: semaphore.New(1), done: done.New(), } } func (l *generalLogger) run() { defer l.access.Signal() dataWritten := false ticker := time.NewTicker(time.Minute) defer ticker.Stop() logger := l.creator() if logger == nil { return } defer logger.Close() // nolint: errcheck for { select { case <-l.done.Wait(): return case msg := <-l.buffer: logger.Write(msg.String() + platform.LineSeparator()) // nolint: errcheck dataWritten = true case <-ticker.C: if !dataWritten { return } dataWritten = false } } } func (l *generalLogger) Handle(msg Message) { select { case l.buffer <- msg: default: } select { case <-l.access.Wait(): go l.run() default: } } func (l *generalLogger) Close() error { return l.done.Close() } type consoleLogWriter struct { logger *log.Logger } func (w *consoleLogWriter) Write(s string) error { w.logger.Print(s) return nil } func (w *consoleLogWriter) Close() error { return nil } type fileLogWriter struct { file *os.File logger *log.Logger } func (w *fileLogWriter) Write(s string) error { w.logger.Print(s) return nil } func (w *fileLogWriter) Close() error { return w.file.Close() } // CreateStdoutLogWriter returns a LogWriterCreator that creates LogWriter for stdout. func CreateStdoutLogWriter() WriterCreator { return func() Writer { return &consoleLogWriter{ logger: log.New(os.Stdout, "", log.Ldate|log.Ltime), } } } // CreateStderrLogWriter returns a LogWriterCreator that creates LogWriter for stderr. func CreateStderrLogWriter() WriterCreator { return func() Writer { return &consoleLogWriter{ logger: log.New(os.Stderr, "", log.Ldate|log.Ltime), } } } // CreateFileLogWriter returns a LogWriterCreator that creates LogWriter for the given file. func CreateFileLogWriter(path string) (WriterCreator, error) { file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return nil, err } file.Close() return func() Writer { file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return nil } return &fileLogWriter{ file: file, logger: log.New(file, "", log.Ldate|log.Ltime), } }, nil } func init() { RegisterHandler(NewLogger(CreateStdoutLogWriter())) } ================================================ FILE: common/log/logger_test.go ================================================ package log_test import ( "io/ioutil" "os" "strings" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/common/log" ) func TestFileLogger(t *testing.T) { f, err := ioutil.TempFile("", "vtest") common.Must(err) path := f.Name() common.Must(f.Close()) creator, err := CreateFileLogWriter(path) common.Must(err) handler := NewLogger(creator) handler.Handle(&GeneralMessage{Content: "Test Log"}) time.Sleep(2 * time.Second) common.Must(common.Close(handler)) f, err = os.Open(path) common.Must(err) defer f.Close() // nolint: errcheck b, err := buf.ReadAllToBytes(f) common.Must(err) if !strings.Contains(string(b), "Test Log") { t.Fatal("Expect log text contains 'Test Log', but actually: ", string(b)) } } ================================================ FILE: common/mux/client.go ================================================ package mux import ( "context" "io" "sync" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/common/signal/done" "v2ray.com/core/common/task" "v2ray.com/core/proxy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/pipe" ) type ClientManager struct { Enabled bool // wheather mux is enabled from user config Picker WorkerPicker } func (m *ClientManager) Dispatch(ctx context.Context, link *transport.Link) error { for i := 0; i < 16; i++ { worker, err := m.Picker.PickAvailable() if err != nil { return err } if worker.Dispatch(ctx, link) { return nil } } return newError("unable to find an available mux client").AtWarning() } type WorkerPicker interface { PickAvailable() (*ClientWorker, error) } type IncrementalWorkerPicker struct { Factory ClientWorkerFactory access sync.Mutex workers []*ClientWorker cleanupTask *task.Periodic } func (p *IncrementalWorkerPicker) cleanupFunc() error { p.access.Lock() defer p.access.Unlock() if len(p.workers) == 0 { return newError("no worker") } p.cleanup() return nil } func (p *IncrementalWorkerPicker) cleanup() { var activeWorkers []*ClientWorker for _, w := range p.workers { if !w.Closed() { activeWorkers = append(activeWorkers, w) } } p.workers = activeWorkers } func (p *IncrementalWorkerPicker) findAvailable() int { for idx, w := range p.workers { if !w.IsFull() { return idx } } return -1 } func (p *IncrementalWorkerPicker) pickInternal() (*ClientWorker, bool, error) { p.access.Lock() defer p.access.Unlock() idx := p.findAvailable() if idx >= 0 { n := len(p.workers) if n > 1 && idx != n-1 { p.workers[n-1], p.workers[idx] = p.workers[idx], p.workers[n-1] } return p.workers[idx], false, nil } p.cleanup() worker, err := p.Factory.Create() if err != nil { return nil, false, err } p.workers = append(p.workers, worker) if p.cleanupTask == nil { p.cleanupTask = &task.Periodic{ Interval: time.Second * 30, Execute: p.cleanupFunc, } } return worker, true, nil } func (p *IncrementalWorkerPicker) PickAvailable() (*ClientWorker, error) { worker, start, err := p.pickInternal() if start { common.Must(p.cleanupTask.Start()) } return worker, err } type ClientWorkerFactory interface { Create() (*ClientWorker, error) } type DialingWorkerFactory struct { Proxy proxy.Outbound Dialer internet.Dialer Strategy ClientStrategy } func (f *DialingWorkerFactory) Create() (*ClientWorker, error) { opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)} uplinkReader, upLinkWriter := pipe.New(opts...) downlinkReader, downlinkWriter := pipe.New(opts...) c, err := NewClientWorker(transport.Link{ Reader: downlinkReader, Writer: upLinkWriter, }, f.Strategy) if err != nil { return nil, err } go func(p proxy.Outbound, d internet.Dialer, c common.Closable) { ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{ Target: net.TCPDestination(muxCoolAddress, muxCoolPort), }) ctx, cancel := context.WithCancel(ctx) if err := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); err != nil { errors.New("failed to handler mux client connection").Base(err).WriteToLog() } common.Must(c.Close()) cancel() }(f.Proxy, f.Dialer, c.done) return c, nil } type ClientStrategy struct { MaxConcurrency uint32 MaxConnection uint32 } type ClientWorker struct { sessionManager *SessionManager link transport.Link done *done.Instance strategy ClientStrategy } var muxCoolAddress = net.DomainAddress("v1.mux.cool") var muxCoolPort = net.Port(9527) // NewClientWorker creates a new mux.Client. func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, error) { c := &ClientWorker{ sessionManager: NewSessionManager(), link: stream, done: done.New(), strategy: s, } go c.fetchOutput() go c.monitor() return c, nil } func (m *ClientWorker) TotalConnections() uint32 { return uint32(m.sessionManager.Count()) } func (m *ClientWorker) ActiveConnections() uint32 { return uint32(m.sessionManager.Size()) } // Closed returns true if this Client is closed. func (m *ClientWorker) Closed() bool { return m.done.Done() } func (m *ClientWorker) monitor() { timer := time.NewTicker(time.Second * 16) defer timer.Stop() for { select { case <-m.done.Wait(): m.sessionManager.Close() common.Close(m.link.Writer) // nolint: errcheck common.Interrupt(m.link.Reader) // nolint: errcheck return case <-timer.C: size := m.sessionManager.Size() if size == 0 && m.sessionManager.CloseIfNoSession() { common.Must(m.done.Close()) } } } } func writeFirstPayload(reader buf.Reader, writer *Writer) error { err := buf.CopyOnceTimeout(reader, writer, time.Millisecond*100) if err == buf.ErrNotTimeoutReader || err == buf.ErrReadTimeout { return writer.WriteMultiBuffer(buf.MultiBuffer{}) } if err != nil { return err } return nil } func fetchInput(ctx context.Context, s *Session, output buf.Writer) { dest := session.OutboundFromContext(ctx).Target transferType := protocol.TransferTypeStream if dest.Network == net.Network_UDP { transferType = protocol.TransferTypePacket } s.transferType = transferType writer := NewWriter(s.ID, dest, output, transferType) defer s.Close() // nolint: errcheck defer writer.Close() // nolint: errcheck newError("dispatching request to ", dest).WriteToLog(session.ExportIDToError(ctx)) if err := writeFirstPayload(s.input, writer); err != nil { newError("failed to write first payload").Base(err).WriteToLog(session.ExportIDToError(ctx)) writer.hasError = true common.Interrupt(s.input) return } if err := buf.Copy(s.input, writer); err != nil { newError("failed to fetch all input").Base(err).WriteToLog(session.ExportIDToError(ctx)) writer.hasError = true common.Interrupt(s.input) return } } func (m *ClientWorker) IsClosing() bool { sm := m.sessionManager if m.strategy.MaxConnection > 0 && sm.Count() >= int(m.strategy.MaxConnection) { return true } return false } func (m *ClientWorker) IsFull() bool { if m.IsClosing() || m.Closed() { return true } sm := m.sessionManager if m.strategy.MaxConcurrency > 0 && sm.Size() >= int(m.strategy.MaxConcurrency) { return true } return false } func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool { if m.IsFull() || m.Closed() { return false } sm := m.sessionManager s := sm.Allocate() if s == nil { return false } s.input = link.Reader s.output = link.Writer go fetchInput(ctx, s, m.link.Writer) return true } func (m *ClientWorker) handleStatueKeepAlive(meta *FrameMetadata, reader *buf.BufferedReader) error { if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) } return nil } func (m *ClientWorker) handleStatusNew(meta *FrameMetadata, reader *buf.BufferedReader) error { if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) } return nil } func (m *ClientWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error { if !meta.Option.Has(OptionData) { return nil } s, found := m.sessionManager.Get(meta.SessionID) if !found { // Notify remote peer to close this session. closingWriter := NewResponseWriter(meta.SessionID, m.link.Writer, protocol.TransferTypeStream) closingWriter.Close() return buf.Copy(NewStreamReader(reader), buf.Discard) } rr := s.NewReader(reader) err := buf.Copy(rr, s.output) if err != nil && buf.IsWriteError(err) { newError("failed to write to downstream. closing session ", s.ID).Base(err).WriteToLog() // Notify remote peer to close this session. closingWriter := NewResponseWriter(meta.SessionID, m.link.Writer, protocol.TransferTypeStream) closingWriter.Close() drainErr := buf.Copy(rr, buf.Discard) common.Interrupt(s.input) s.Close() return drainErr } return err } func (m *ClientWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error { if s, found := m.sessionManager.Get(meta.SessionID); found { if meta.Option.Has(OptionError) { common.Interrupt(s.input) common.Interrupt(s.output) } s.Close() } if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) } return nil } func (m *ClientWorker) fetchOutput() { defer func() { common.Must(m.done.Close()) }() reader := &buf.BufferedReader{Reader: m.link.Reader} var meta FrameMetadata for { err := meta.Unmarshal(reader) if err != nil { if errors.Cause(err) != io.EOF { newError("failed to read metadata").Base(err).WriteToLog() } break } switch meta.SessionStatus { case SessionStatusKeepAlive: err = m.handleStatueKeepAlive(&meta, reader) case SessionStatusEnd: err = m.handleStatusEnd(&meta, reader) case SessionStatusNew: err = m.handleStatusNew(&meta, reader) case SessionStatusKeep: err = m.handleStatusKeep(&meta, reader) default: status := meta.SessionStatus newError("unknown status: ", status).AtError().WriteToLog() return } if err != nil { newError("failed to process data").Base(err).WriteToLog() return } } } ================================================ FILE: common/mux/client_test.go ================================================ package mux_test import ( "context" "testing" "time" "github.com/golang/mock/gomock" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/testing/mocks" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) func TestIncrementalPickerFailure(t *testing.T) { mockCtl := gomock.NewController(t) defer mockCtl.Finish() mockWorkerFactory := mocks.NewMuxClientWorkerFactory(mockCtl) mockWorkerFactory.EXPECT().Create().Return(nil, errors.New("test")) picker := mux.IncrementalWorkerPicker{ Factory: mockWorkerFactory, } _, err := picker.PickAvailable() if err == nil { t.Error("expected error, but nil") } } func TestClientWorkerEOF(t *testing.T) { reader, writer := pipe.New(pipe.WithoutSizeLimit()) common.Must(writer.Close()) worker, err := mux.NewClientWorker(transport.Link{Reader: reader, Writer: writer}, mux.ClientStrategy{}) common.Must(err) time.Sleep(time.Millisecond * 500) f := worker.Dispatch(context.Background(), nil) if f { t.Error("expected failed dispatching, but actually not") } } func TestClientWorkerClose(t *testing.T) { mockCtl := gomock.NewController(t) defer mockCtl.Finish() r1, w1 := pipe.New(pipe.WithoutSizeLimit()) worker1, err := mux.NewClientWorker(transport.Link{ Reader: r1, Writer: w1, }, mux.ClientStrategy{ MaxConcurrency: 4, MaxConnection: 4, }) common.Must(err) r2, w2 := pipe.New(pipe.WithoutSizeLimit()) worker2, err := mux.NewClientWorker(transport.Link{ Reader: r2, Writer: w2, }, mux.ClientStrategy{ MaxConcurrency: 4, MaxConnection: 4, }) common.Must(err) factory := mocks.NewMuxClientWorkerFactory(mockCtl) gomock.InOrder( factory.EXPECT().Create().Return(worker1, nil), factory.EXPECT().Create().Return(worker2, nil), ) picker := &mux.IncrementalWorkerPicker{ Factory: factory, } manager := &mux.ClientManager{ Picker: picker, } tr1, tw1 := pipe.New(pipe.WithoutSizeLimit()) ctx1 := session.ContextWithOutbound(context.Background(), &session.Outbound{ Target: net.TCPDestination(net.DomainAddress("www.v2ray.com"), 80), }) common.Must(manager.Dispatch(ctx1, &transport.Link{ Reader: tr1, Writer: tw1, })) defer tw1.Close() common.Must(w1.Close()) time.Sleep(time.Millisecond * 500) if !worker1.Closed() { t.Error("worker1 is not finished") } tr2, tw2 := pipe.New(pipe.WithoutSizeLimit()) ctx2 := session.ContextWithOutbound(context.Background(), &session.Outbound{ Target: net.TCPDestination(net.DomainAddress("www.v2ray.com"), 80), }) common.Must(manager.Dispatch(ctx2, &transport.Link{ Reader: tr2, Writer: tw2, })) defer tw2.Close() common.Must(w2.Close()) } ================================================ FILE: common/mux/errors.generated.go ================================================ package mux import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/mux/frame.go ================================================ package mux import ( "encoding/binary" "io" "v2ray.com/core/common" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" ) type SessionStatus byte const ( SessionStatusNew SessionStatus = 0x01 SessionStatusKeep SessionStatus = 0x02 SessionStatusEnd SessionStatus = 0x03 SessionStatusKeepAlive SessionStatus = 0x04 ) const ( OptionData bitmask.Byte = 0x01 OptionError bitmask.Byte = 0x02 ) type TargetNetwork byte const ( TargetNetworkTCP TargetNetwork = 0x01 TargetNetworkUDP TargetNetwork = 0x02 ) var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6), protocol.PortThenAddress(), ) /* Frame format 2 bytes - length 2 bytes - session id 1 bytes - status 1 bytes - option 1 byte - network 2 bytes - port n bytes - address */ type FrameMetadata struct { Target net.Destination SessionID uint16 Option bitmask.Byte SessionStatus SessionStatus } func (f FrameMetadata) WriteTo(b *buf.Buffer) error { lenBytes := b.Extend(2) len0 := b.Len() sessionBytes := b.Extend(2) binary.BigEndian.PutUint16(sessionBytes, f.SessionID) common.Must(b.WriteByte(byte(f.SessionStatus))) common.Must(b.WriteByte(byte(f.Option))) if f.SessionStatus == SessionStatusNew { switch f.Target.Network { case net.Network_TCP: common.Must(b.WriteByte(byte(TargetNetworkTCP))) case net.Network_UDP: common.Must(b.WriteByte(byte(TargetNetworkUDP))) } if err := addrParser.WriteAddressPort(b, f.Target.Address, f.Target.Port); err != nil { return err } } len1 := b.Len() binary.BigEndian.PutUint16(lenBytes, uint16(len1-len0)) return nil } // Unmarshal reads FrameMetadata from the given reader. func (f *FrameMetadata) Unmarshal(reader io.Reader) error { metaLen, err := serial.ReadUint16(reader) if err != nil { return err } if metaLen > 512 { return newError("invalid metalen ", metaLen).AtError() } b := buf.New() defer b.Release() if _, err := b.ReadFullFrom(reader, int32(metaLen)); err != nil { return err } return f.UnmarshalFromBuffer(b) } // UnmarshalFromBuffer reads a FrameMetadata from the given buffer. // Visible for testing only. func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer) error { if b.Len() < 4 { return newError("insufficient buffer: ", b.Len()) } f.SessionID = binary.BigEndian.Uint16(b.BytesTo(2)) f.SessionStatus = SessionStatus(b.Byte(2)) f.Option = bitmask.Byte(b.Byte(3)) f.Target.Network = net.Network_Unknown if f.SessionStatus == SessionStatusNew { if b.Len() < 8 { return newError("insufficient buffer: ", b.Len()) } network := TargetNetwork(b.Byte(4)) b.Advance(5) addr, port, err := addrParser.ReadAddressPort(nil, b) if err != nil { return newError("failed to parse address and port").Base(err) } switch network { case TargetNetworkTCP: f.Target = net.TCPDestination(addr, port) case TargetNetworkUDP: f.Target = net.UDPDestination(addr, port) default: return newError("unknown network type: ", network) } } return nil } ================================================ FILE: common/mux/frame_test.go ================================================ package mux_test import ( "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/mux" "v2ray.com/core/common/net" ) func BenchmarkFrameWrite(b *testing.B) { frame := mux.FrameMetadata{ Target: net.TCPDestination(net.DomainAddress("www.v2ray.com"), net.Port(80)), SessionID: 1, SessionStatus: mux.SessionStatusNew, } writer := buf.New() defer writer.Release() for i := 0; i < b.N; i++ { common.Must(frame.WriteTo(writer)) writer.Clear() } } ================================================ FILE: common/mux/mux.go ================================================ package mux //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: common/mux/mux_test.go ================================================ package mux_test import ( "io" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/common/mux" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/transport/pipe" ) func readAll(reader buf.Reader) (buf.MultiBuffer, error) { var mb buf.MultiBuffer for { b, err := reader.ReadMultiBuffer() if err == io.EOF { break } if err != nil { return nil, err } mb = append(mb, b...) } return mb, nil } func TestReaderWriter(t *testing.T) { pReader, pWriter := pipe.New(pipe.WithSizeLimit(1024)) dest := net.TCPDestination(net.DomainAddress("v2ray.com"), 80) writer := NewWriter(1, dest, pWriter, protocol.TransferTypeStream) dest2 := net.TCPDestination(net.LocalHostIP, 443) writer2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream) dest3 := net.TCPDestination(net.LocalHostIPv6, 18374) writer3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream) writePayload := func(writer *Writer, payload ...byte) error { b := buf.New() b.Write(payload) return writer.WriteMultiBuffer(buf.MultiBuffer{b}) } common.Must(writePayload(writer, 'a', 'b', 'c', 'd')) common.Must(writePayload(writer2)) common.Must(writePayload(writer, 'e', 'f', 'g', 'h')) common.Must(writePayload(writer3, 'x')) writer.Close() writer3.Close() common.Must(writePayload(writer2, 'y')) writer2.Close() bytesReader := &buf.BufferedReader{Reader: pReader} { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 1, SessionStatus: SessionStatusNew, Target: dest, Option: OptionData, }); r != "" { t.Error("metadata: ", r) } data, err := readAll(NewStreamReader(bytesReader)) common.Must(err) if s := data.String(); s != "abcd" { t.Error("data: ", s) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionStatus: SessionStatusNew, SessionID: 2, Option: 0, Target: dest2, }); r != "" { t.Error("meta: ", r) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 1, SessionStatus: SessionStatusKeep, Option: 1, }); r != "" { t.Error("meta: ", r) } data, err := readAll(NewStreamReader(bytesReader)) common.Must(err) if s := data.String(); s != "efgh" { t.Error("data: ", s) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 3, SessionStatus: SessionStatusNew, Option: 1, Target: dest3, }); r != "" { t.Error("meta: ", r) } data, err := readAll(NewStreamReader(bytesReader)) common.Must(err) if s := data.String(); s != "x" { t.Error("data: ", s) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 1, SessionStatus: SessionStatusEnd, Option: 0, }); r != "" { t.Error("meta: ", r) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 3, SessionStatus: SessionStatusEnd, Option: 0, }); r != "" { t.Error("meta: ", r) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 2, SessionStatus: SessionStatusKeep, Option: 1, }); r != "" { t.Error("meta: ", r) } data, err := readAll(NewStreamReader(bytesReader)) common.Must(err) if s := data.String(); s != "y" { t.Error("data: ", s) } } { var meta FrameMetadata common.Must(meta.Unmarshal(bytesReader)) if r := cmp.Diff(meta, FrameMetadata{ SessionID: 2, SessionStatus: SessionStatusEnd, Option: 0, }); r != "" { t.Error("meta: ", r) } } pWriter.Close() { var meta FrameMetadata err := meta.Unmarshal(bytesReader) if err == nil { t.Error("nil error") } } } ================================================ FILE: common/mux/reader.go ================================================ package mux import ( "io" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/serial" ) // PacketReader is an io.Reader that reads whole chunk of Mux frames every time. type PacketReader struct { reader io.Reader eof bool } // NewPacketReader creates a new PacketReader. func NewPacketReader(reader io.Reader) *PacketReader { return &PacketReader{ reader: reader, eof: false, } } // ReadMultiBuffer implements buf.Reader. func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { if r.eof { return nil, io.EOF } size, err := serial.ReadUint16(r.reader) if err != nil { return nil, err } if size > buf.Size { return nil, newError("packet size too large: ", size) } b := buf.New() if _, err := b.ReadFullFrom(r.reader, int32(size)); err != nil { b.Release() return nil, err } r.eof = true return buf.MultiBuffer{b}, nil } // NewStreamReader creates a new StreamReader. func NewStreamReader(reader *buf.BufferedReader) buf.Reader { return crypto.NewChunkStreamReaderWithChunkCount(crypto.PlainChunkSizeParser{}, reader, 1) } ================================================ FILE: common/mux/server.go ================================================ package mux import ( "context" "io" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/features/routing" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) type Server struct { dispatcher routing.Dispatcher } // NewServer creates a new mux.Server. func NewServer(ctx context.Context) *Server { s := &Server{} core.RequireFeatures(ctx, func(d routing.Dispatcher) { s.dispatcher = d }) return s } // Type implements common.HasType. func (s *Server) Type() interface{} { return s.dispatcher.Type() } // Dispatch implements routing.Dispatcher func (s *Server) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { if dest.Address != muxCoolAddress { return s.dispatcher.Dispatch(ctx, dest) } opts := pipe.OptionsFromContext(ctx) uplinkReader, uplinkWriter := pipe.New(opts...) downlinkReader, downlinkWriter := pipe.New(opts...) _, err := NewServerWorker(ctx, s.dispatcher, &transport.Link{ Reader: uplinkReader, Writer: downlinkWriter, }) if err != nil { return nil, err } return &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, nil } // Start implements common.Runnable. func (s *Server) Start() error { return nil } // Close implements common.Closable. func (s *Server) Close() error { return nil } type ServerWorker struct { dispatcher routing.Dispatcher link *transport.Link sessionManager *SessionManager } func NewServerWorker(ctx context.Context, d routing.Dispatcher, link *transport.Link) (*ServerWorker, error) { worker := &ServerWorker{ dispatcher: d, link: link, sessionManager: NewSessionManager(), } go worker.run(ctx) return worker, nil } func handle(ctx context.Context, s *Session, output buf.Writer) { writer := NewResponseWriter(s.ID, output, s.transferType) if err := buf.Copy(s.input, writer); err != nil { newError("session ", s.ID, " ends.").Base(err).WriteToLog(session.ExportIDToError(ctx)) writer.hasError = true } writer.Close() s.Close() } func (w *ServerWorker) ActiveConnections() uint32 { return uint32(w.sessionManager.Size()) } func (w *ServerWorker) Closed() bool { return w.sessionManager.Closed() } func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.BufferedReader) error { if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) } return nil } func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error { newError("received request for ", meta.Target).WriteToLog(session.ExportIDToError(ctx)) { msg := &log.AccessMessage{ To: meta.Target, Status: log.AccessAccepted, Reason: "", } if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() { msg.From = inbound.Source msg.Email = inbound.User.Email } ctx = log.ContextWithAccessMessage(ctx, msg) } link, err := w.dispatcher.Dispatch(ctx, meta.Target) if err != nil { if meta.Option.Has(OptionData) { buf.Copy(NewStreamReader(reader), buf.Discard) } return newError("failed to dispatch request.").Base(err) } s := &Session{ input: link.Reader, output: link.Writer, parent: w.sessionManager, ID: meta.SessionID, transferType: protocol.TransferTypeStream, } if meta.Target.Network == net.Network_UDP { s.transferType = protocol.TransferTypePacket } w.sessionManager.Add(s) go handle(ctx, s, w.link.Writer) if !meta.Option.Has(OptionData) { return nil } rr := s.NewReader(reader) if err := buf.Copy(rr, s.output); err != nil { buf.Copy(rr, buf.Discard) common.Interrupt(s.input) return s.Close() } return nil } func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error { if !meta.Option.Has(OptionData) { return nil } s, found := w.sessionManager.Get(meta.SessionID) if !found { // Notify remote peer to close this session. closingWriter := NewResponseWriter(meta.SessionID, w.link.Writer, protocol.TransferTypeStream) closingWriter.Close() return buf.Copy(NewStreamReader(reader), buf.Discard) } rr := s.NewReader(reader) err := buf.Copy(rr, s.output) if err != nil && buf.IsWriteError(err) { newError("failed to write to downstream writer. closing session ", s.ID).Base(err).WriteToLog() // Notify remote peer to close this session. closingWriter := NewResponseWriter(meta.SessionID, w.link.Writer, protocol.TransferTypeStream) closingWriter.Close() drainErr := buf.Copy(rr, buf.Discard) common.Interrupt(s.input) s.Close() return drainErr } return err } func (w *ServerWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error { if s, found := w.sessionManager.Get(meta.SessionID); found { if meta.Option.Has(OptionError) { common.Interrupt(s.input) common.Interrupt(s.output) } s.Close() } if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) } return nil } func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedReader) error { var meta FrameMetadata err := meta.Unmarshal(reader) if err != nil { return newError("failed to read metadata").Base(err) } switch meta.SessionStatus { case SessionStatusKeepAlive: err = w.handleStatusKeepAlive(&meta, reader) case SessionStatusEnd: err = w.handleStatusEnd(&meta, reader) case SessionStatusNew: err = w.handleStatusNew(ctx, &meta, reader) case SessionStatusKeep: err = w.handleStatusKeep(&meta, reader) default: status := meta.SessionStatus return newError("unknown status: ", status).AtError() } if err != nil { return newError("failed to process data").Base(err) } return nil } func (w *ServerWorker) run(ctx context.Context) { input := w.link.Reader reader := &buf.BufferedReader{Reader: input} defer w.sessionManager.Close() // nolint: errcheck for { select { case <-ctx.Done(): return default: err := w.handleFrame(ctx, reader) if err != nil { if errors.Cause(err) != io.EOF { newError("unexpected EOF").Base(err).WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(input) } return } } } } ================================================ FILE: common/mux/session.go ================================================ package mux import ( "sync" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/protocol" ) type SessionManager struct { sync.RWMutex sessions map[uint16]*Session count uint16 closed bool } func NewSessionManager() *SessionManager { return &SessionManager{ count: 0, sessions: make(map[uint16]*Session, 16), } } func (m *SessionManager) Closed() bool { m.RLock() defer m.RUnlock() return m.closed } func (m *SessionManager) Size() int { m.RLock() defer m.RUnlock() return len(m.sessions) } func (m *SessionManager) Count() int { m.RLock() defer m.RUnlock() return int(m.count) } func (m *SessionManager) Allocate() *Session { m.Lock() defer m.Unlock() if m.closed { return nil } m.count++ s := &Session{ ID: m.count, parent: m, } m.sessions[s.ID] = s return s } func (m *SessionManager) Add(s *Session) { m.Lock() defer m.Unlock() if m.closed { return } m.count++ m.sessions[s.ID] = s } func (m *SessionManager) Remove(id uint16) { m.Lock() defer m.Unlock() if m.closed { return } delete(m.sessions, id) if len(m.sessions) == 0 { m.sessions = make(map[uint16]*Session, 16) } } func (m *SessionManager) Get(id uint16) (*Session, bool) { m.RLock() defer m.RUnlock() if m.closed { return nil, false } s, found := m.sessions[id] return s, found } func (m *SessionManager) CloseIfNoSession() bool { m.Lock() defer m.Unlock() if m.closed { return true } if len(m.sessions) != 0 { return false } m.closed = true return true } func (m *SessionManager) Close() error { m.Lock() defer m.Unlock() if m.closed { return nil } m.closed = true for _, s := range m.sessions { common.Close(s.input) // nolint: errcheck common.Close(s.output) // nolint: errcheck } m.sessions = nil return nil } // Session represents a client connection in a Mux connection. type Session struct { input buf.Reader output buf.Writer parent *SessionManager ID uint16 transferType protocol.TransferType } // Close closes all resources associated with this session. func (s *Session) Close() error { common.Close(s.output) // nolint: errcheck common.Close(s.input) // nolint: errcheck s.parent.Remove(s.ID) return nil } // NewReader creates a buf.Reader based on the transfer type of this Session. func (s *Session) NewReader(reader *buf.BufferedReader) buf.Reader { if s.transferType == protocol.TransferTypeStream { return NewStreamReader(reader) } return NewPacketReader(reader) } ================================================ FILE: common/mux/session_test.go ================================================ package mux_test import ( "testing" . "v2ray.com/core/common/mux" ) func TestSessionManagerAdd(t *testing.T) { m := NewSessionManager() s := m.Allocate() if s.ID != 1 { t.Error("id: ", s.ID) } if m.Size() != 1 { t.Error("size: ", m.Size()) } s = m.Allocate() if s.ID != 2 { t.Error("id: ", s.ID) } if m.Size() != 2 { t.Error("size: ", m.Size()) } s = &Session{ ID: 4, } m.Add(s) if s.ID != 4 { t.Error("id: ", s.ID) } if m.Size() != 3 { t.Error("size: ", m.Size()) } } func TestSessionManagerClose(t *testing.T) { m := NewSessionManager() s := m.Allocate() if m.CloseIfNoSession() { t.Error("able to close") } m.Remove(s.ID) if !m.CloseIfNoSession() { t.Error("not able to close") } } ================================================ FILE: common/mux/writer.go ================================================ package mux import ( "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" ) type Writer struct { dest net.Destination writer buf.Writer id uint16 followup bool hasError bool transferType protocol.TransferType } func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType) *Writer { return &Writer{ id: id, dest: dest, writer: writer, followup: false, transferType: transferType, } } func NewResponseWriter(id uint16, writer buf.Writer, transferType protocol.TransferType) *Writer { return &Writer{ id: id, writer: writer, followup: true, transferType: transferType, } } func (w *Writer) getNextFrameMeta() FrameMetadata { meta := FrameMetadata{ SessionID: w.id, Target: w.dest, } if w.followup { meta.SessionStatus = SessionStatusKeep } else { w.followup = true meta.SessionStatus = SessionStatusNew } return meta } func (w *Writer) writeMetaOnly() error { meta := w.getNextFrameMeta() b := buf.New() if err := meta.WriteTo(b); err != nil { return err } return w.writer.WriteMultiBuffer(buf.MultiBuffer{b}) } func writeMetaWithFrame(writer buf.Writer, meta FrameMetadata, data buf.MultiBuffer) error { frame := buf.New() if err := meta.WriteTo(frame); err != nil { return err } if _, err := serial.WriteUint16(frame, uint16(data.Len())); err != nil { return err } mb2 := make(buf.MultiBuffer, 0, len(data)+1) mb2 = append(mb2, frame) mb2 = append(mb2, data...) return writer.WriteMultiBuffer(mb2) } func (w *Writer) writeData(mb buf.MultiBuffer) error { meta := w.getNextFrameMeta() meta.Option.Set(OptionData) return writeMetaWithFrame(w.writer, meta, mb) } // WriteMultiBuffer implements buf.Writer. func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error { defer buf.ReleaseMulti(mb) if mb.IsEmpty() { return w.writeMetaOnly() } for !mb.IsEmpty() { var chunk buf.MultiBuffer if w.transferType == protocol.TransferTypeStream { mb, chunk = buf.SplitSize(mb, 8*1024) } else { mb2, b := buf.SplitFirst(mb) mb = mb2 chunk = buf.MultiBuffer{b} } if err := w.writeData(chunk); err != nil { return err } } return nil } // Close implements common.Closable. func (w *Writer) Close() error { meta := FrameMetadata{ SessionID: w.id, SessionStatus: SessionStatusEnd, } if w.hasError { meta.Option.Set(OptionError) } frame := buf.New() common.Must(meta.WriteTo(frame)) w.writer.WriteMultiBuffer(buf.MultiBuffer{frame}) // nolint: errcheck return nil } ================================================ FILE: common/net/address.go ================================================ package net import ( "bytes" "net" "strings" ) var ( // LocalHostIP is a constant value for localhost IP in IPv4. LocalHostIP = IPAddress([]byte{127, 0, 0, 1}) // AnyIP is a constant value for any IP in IPv4. AnyIP = IPAddress([]byte{0, 0, 0, 0}) // LocalHostDomain is a constant value for localhost domain. LocalHostDomain = DomainAddress("localhost") // LocalHostIPv6 is a constant value for localhost IP in IPv6. LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) // AnyIPv6 is a constant value for any IP in IPv6. AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) ) // AddressFamily is the type of address. type AddressFamily byte const ( // AddressFamilyIPv4 represents address as IPv4 AddressFamilyIPv4 = AddressFamily(0) // AddressFamilyIPv6 represents address as IPv6 AddressFamilyIPv6 = AddressFamily(1) // AddressFamilyDomain represents address as Domain AddressFamilyDomain = AddressFamily(2) ) // IsIPv4 returns true if current AddressFamily is IPv4. func (af AddressFamily) IsIPv4() bool { return af == AddressFamilyIPv4 } // IsIPv6 returns true if current AddressFamily is IPv6. func (af AddressFamily) IsIPv6() bool { return af == AddressFamilyIPv6 } // IsIP returns true if current AddressFamily is IPv6 or IPv4. func (af AddressFamily) IsIP() bool { return af == AddressFamilyIPv4 || af == AddressFamilyIPv6 } // IsDomain returns true if current AddressFamily is Domain. func (af AddressFamily) IsDomain() bool { return af == AddressFamilyDomain } // Address represents a network address to be communicated with. It may be an IP address or domain // address, not both. This interface doesn't resolve IP address for a given domain. type Address interface { IP() net.IP // IP of this Address Domain() string // Domain of this Address Family() AddressFamily String() string // String representation of this Address } func isAlphaNum(c byte) bool { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } // ParseAddress parses a string into an Address. The return value will be an IPAddress when // the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise. func ParseAddress(addr string) Address { // Handle IPv6 address in form as "[2001:4860:0:2001::68]" lenAddr := len(addr) if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' { addr = addr[1 : lenAddr-1] lenAddr -= 2 } if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) { addr = strings.TrimSpace(addr) } ip := net.ParseIP(addr) if ip != nil { return IPAddress(ip) } return DomainAddress(addr) } var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // IPAddress creates an Address with given IP. func IPAddress(ip []byte) Address { switch len(ip) { case net.IPv4len: var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]} return addr case net.IPv6len: if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff { return IPAddress(ip[12:16]) } var addr ipv6Address = [16]byte{ ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15], } return addr default: newError("invalid IP format: ", ip).AtError().WriteToLog() return nil } } // DomainAddress creates an Address with given domain. func DomainAddress(domain string) Address { return domainAddress(domain) } type ipv4Address [4]byte func (a ipv4Address) IP() net.IP { return net.IP(a[:]) } func (ipv4Address) Domain() string { panic("Calling Domain() on an IPv4Address.") } func (ipv4Address) Family() AddressFamily { return AddressFamilyIPv4 } func (a ipv4Address) String() string { return a.IP().String() } type ipv6Address [16]byte func (a ipv6Address) IP() net.IP { return net.IP(a[:]) } func (ipv6Address) Domain() string { panic("Calling Domain() on an IPv6Address.") } func (ipv6Address) Family() AddressFamily { return AddressFamilyIPv6 } func (a ipv6Address) String() string { return "[" + a.IP().String() + "]" } type domainAddress string func (domainAddress) IP() net.IP { panic("Calling IP() on a DomainAddress.") } func (a domainAddress) Domain() string { return string(a) } func (domainAddress) Family() AddressFamily { return AddressFamilyDomain } func (a domainAddress) String() string { return a.Domain() } // AsAddress translates IPOrDomain to Address. func (d *IPOrDomain) AsAddress() Address { if d == nil { return nil } switch addr := d.Address.(type) { case *IPOrDomain_Ip: return IPAddress(addr.Ip) case *IPOrDomain_Domain: return DomainAddress(addr.Domain) } panic("Common|Net: Invalid address.") } // NewIPOrDomain translates Address to IPOrDomain func NewIPOrDomain(addr Address) *IPOrDomain { switch addr.Family() { case AddressFamilyDomain: return &IPOrDomain{ Address: &IPOrDomain_Domain{ Domain: addr.Domain(), }, } case AddressFamilyIPv4, AddressFamilyIPv6: return &IPOrDomain{ Address: &IPOrDomain_Ip{ Ip: addr.IP(), }, } default: panic("Unknown Address type.") } } ================================================ FILE: common/net/address.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/net/address.proto package net import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Address of a network host. It may be either an IP address or a domain // address. type IPOrDomain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Address: // *IPOrDomain_Ip // *IPOrDomain_Domain Address isIPOrDomain_Address `protobuf_oneof:"address"` } func (x *IPOrDomain) Reset() { *x = IPOrDomain{} if protoimpl.UnsafeEnabled { mi := &file_common_net_address_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *IPOrDomain) String() string { return protoimpl.X.MessageStringOf(x) } func (*IPOrDomain) ProtoMessage() {} func (x *IPOrDomain) ProtoReflect() protoreflect.Message { mi := &file_common_net_address_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use IPOrDomain.ProtoReflect.Descriptor instead. func (*IPOrDomain) Descriptor() ([]byte, []int) { return file_common_net_address_proto_rawDescGZIP(), []int{0} } func (m *IPOrDomain) GetAddress() isIPOrDomain_Address { if m != nil { return m.Address } return nil } func (x *IPOrDomain) GetIp() []byte { if x, ok := x.GetAddress().(*IPOrDomain_Ip); ok { return x.Ip } return nil } func (x *IPOrDomain) GetDomain() string { if x, ok := x.GetAddress().(*IPOrDomain_Domain); ok { return x.Domain } return "" } type isIPOrDomain_Address interface { isIPOrDomain_Address() } type IPOrDomain_Ip struct { // IP address. Must by either 4 or 16 bytes. Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3,oneof"` } type IPOrDomain_Domain struct { // Domain address. Domain string `protobuf:"bytes,2,opt,name=domain,proto3,oneof"` } func (*IPOrDomain_Ip) isIPOrDomain_Address() {} func (*IPOrDomain_Domain) isIPOrDomain_Address() {} var File_common_net_address_proto protoreflect.FileDescriptor var file_common_net_address_proto_rawDesc = []byte{ 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x22, 0x43, 0x0a, 0x0a, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x02, 0x69, 0x70, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_net_address_proto_rawDescOnce sync.Once file_common_net_address_proto_rawDescData = file_common_net_address_proto_rawDesc ) func file_common_net_address_proto_rawDescGZIP() []byte { file_common_net_address_proto_rawDescOnce.Do(func() { file_common_net_address_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_address_proto_rawDescData) }) return file_common_net_address_proto_rawDescData } var file_common_net_address_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_net_address_proto_goTypes = []interface{}{ (*IPOrDomain)(nil), // 0: v2ray.core.common.net.IPOrDomain } var file_common_net_address_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_common_net_address_proto_init() } func file_common_net_address_proto_init() { if File_common_net_address_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_net_address_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IPOrDomain); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_common_net_address_proto_msgTypes[0].OneofWrappers = []interface{}{ (*IPOrDomain_Ip)(nil), (*IPOrDomain_Domain)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_net_address_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_net_address_proto_goTypes, DependencyIndexes: file_common_net_address_proto_depIdxs, MessageInfos: file_common_net_address_proto_msgTypes, }.Build() File_common_net_address_proto = out.File file_common_net_address_proto_rawDesc = nil file_common_net_address_proto_goTypes = nil file_common_net_address_proto_depIdxs = nil } ================================================ FILE: common/net/address.proto ================================================ syntax = "proto3"; package v2ray.core.common.net; option csharp_namespace = "V2Ray.Core.Common.Net"; option go_package = "v2ray.com/core/common/net"; option java_package = "com.v2ray.core.common.net"; option java_multiple_files = true; // Address of a network host. It may be either an IP address or a domain // address. message IPOrDomain { oneof address { // IP address. Must by either 4 or 16 bytes. bytes ip = 1; // Domain address. string domain = 2; } } ================================================ FILE: common/net/address_test.go ================================================ package net_test import ( "net" "testing" "github.com/google/go-cmp/cmp" . "v2ray.com/core/common/net" ) func TestAddressProperty(t *testing.T) { type addrProprty struct { IP []byte Domain string Family AddressFamily String string } testCases := []struct { Input Address Output addrProprty }{ { Input: IPAddress([]byte{byte(1), byte(2), byte(3), byte(4)}), Output: addrProprty{ IP: []byte{byte(1), byte(2), byte(3), byte(4)}, Family: AddressFamilyIPv4, String: "1.2.3.4", }, }, { Input: IPAddress([]byte{ byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), }), Output: addrProprty{ IP: []byte{ byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), byte(1), byte(2), byte(3), byte(4), }, Family: AddressFamilyIPv6, String: "[102:304:102:304:102:304:102:304]", }, }, { Input: IPAddress([]byte{ byte(0), byte(0), byte(0), byte(0), byte(0), byte(0), byte(0), byte(0), byte(0), byte(0), byte(255), byte(255), byte(1), byte(2), byte(3), byte(4), }), Output: addrProprty{ IP: []byte{byte(1), byte(2), byte(3), byte(4)}, Family: AddressFamilyIPv4, String: "1.2.3.4", }, }, { Input: DomainAddress("v2ray.com"), Output: addrProprty{ Domain: "v2ray.com", Family: AddressFamilyDomain, String: "v2ray.com", }, }, { Input: IPAddress(net.IPv4(1, 2, 3, 4)), Output: addrProprty{ IP: []byte{byte(1), byte(2), byte(3), byte(4)}, Family: AddressFamilyIPv4, String: "1.2.3.4", }, }, { Input: ParseAddress("[2001:4860:0:2001::68]"), Output: addrProprty{ IP: []byte{0x20, 0x01, 0x48, 0x60, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68}, Family: AddressFamilyIPv6, String: "[2001:4860:0:2001::68]", }, }, { Input: ParseAddress("::0"), Output: addrProprty{ IP: AnyIPv6.IP(), Family: AddressFamilyIPv6, String: "[::]", }, }, { Input: ParseAddress("[::ffff:123.151.71.143]"), Output: addrProprty{ IP: []byte{123, 151, 71, 143}, Family: AddressFamilyIPv4, String: "123.151.71.143", }, }, { Input: NewIPOrDomain(ParseAddress("v2ray.com")).AsAddress(), Output: addrProprty{ Domain: "v2ray.com", Family: AddressFamilyDomain, String: "v2ray.com", }, }, { Input: NewIPOrDomain(ParseAddress("8.8.8.8")).AsAddress(), Output: addrProprty{ IP: []byte{8, 8, 8, 8}, Family: AddressFamilyIPv4, String: "8.8.8.8", }, }, { Input: NewIPOrDomain(ParseAddress("[2001:4860:0:2001::68]")).AsAddress(), Output: addrProprty{ IP: []byte{0x20, 0x01, 0x48, 0x60, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68}, Family: AddressFamilyIPv6, String: "[2001:4860:0:2001::68]", }, }, } for _, testCase := range testCases { actual := addrProprty{ Family: testCase.Input.Family(), String: testCase.Input.String(), } if testCase.Input.Family().IsIP() { actual.IP = testCase.Input.IP() } else { actual.Domain = testCase.Input.Domain() } if r := cmp.Diff(actual, testCase.Output); r != "" { t.Error("for input: ", testCase.Input, ":", r) } } } func TestInvalidAddressConvertion(t *testing.T) { panics := func(f func()) (ret bool) { defer func() { if r := recover(); r != nil { ret = true } }() f() return false } testCases := []func(){ func() { ParseAddress("8.8.8.8").Domain() }, func() { ParseAddress("2001:4860:0:2001::68").Domain() }, func() { ParseAddress("v2ray.com").IP() }, } for idx, testCase := range testCases { if !panics(testCase) { t.Error("case ", idx, " failed") } } } func BenchmarkParseAddressIPv4(b *testing.B) { for i := 0; i < b.N; i++ { addr := ParseAddress("8.8.8.8") if addr.Family() != AddressFamilyIPv4 { panic("not ipv4") } } } func BenchmarkParseAddressIPv6(b *testing.B) { for i := 0; i < b.N; i++ { addr := ParseAddress("2001:4860:0:2001::68") if addr.Family() != AddressFamilyIPv6 { panic("not ipv6") } } } func BenchmarkParseAddressDomain(b *testing.B) { for i := 0; i < b.N; i++ { addr := ParseAddress("v2ray.com") if addr.Family() != AddressFamilyDomain { panic("not domain") } } } ================================================ FILE: common/net/connection.go ================================================ // +build !confonly package net import ( "io" "net" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/signal/done" ) type ConnectionOption func(*connection) func ConnectionLocalAddr(a net.Addr) ConnectionOption { return func(c *connection) { c.local = a } } func ConnectionRemoteAddr(a net.Addr) ConnectionOption { return func(c *connection) { c.remote = a } } func ConnectionInput(writer io.Writer) ConnectionOption { return func(c *connection) { c.writer = buf.NewWriter(writer) } } func ConnectionInputMulti(writer buf.Writer) ConnectionOption { return func(c *connection) { c.writer = writer } } func ConnectionOutput(reader io.Reader) ConnectionOption { return func(c *connection) { c.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)} } } func ConnectionOutputMulti(reader buf.Reader) ConnectionOption { return func(c *connection) { c.reader = &buf.BufferedReader{Reader: reader} } } func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption { return func(c *connection) { c.reader = &buf.BufferedReader{ Reader: reader, Spliter: buf.SplitFirstBytes, } } } func ConnectionOnClose(n io.Closer) ConnectionOption { return func(c *connection) { c.onClose = n } } func NewConnection(opts ...ConnectionOption) net.Conn { c := &connection{ done: done.New(), local: &net.TCPAddr{ IP: []byte{0, 0, 0, 0}, Port: 0, }, remote: &net.TCPAddr{ IP: []byte{0, 0, 0, 0}, Port: 0, }, } for _, opt := range opts { opt(c) } return c } type connection struct { reader *buf.BufferedReader writer buf.Writer done *done.Instance onClose io.Closer local Addr remote Addr } func (c *connection) Read(b []byte) (int, error) { return c.reader.Read(b) } // ReadMultiBuffer implements buf.Reader. func (c *connection) ReadMultiBuffer() (buf.MultiBuffer, error) { return c.reader.ReadMultiBuffer() } // Write implements net.Conn.Write(). func (c *connection) Write(b []byte) (int, error) { if c.done.Done() { return 0, io.ErrClosedPipe } l := len(b) mb := make(buf.MultiBuffer, 0, l/buf.Size+1) mb = buf.MergeBytes(mb, b) return l, c.writer.WriteMultiBuffer(mb) } func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error { if c.done.Done() { buf.ReleaseMulti(mb) return io.ErrClosedPipe } return c.writer.WriteMultiBuffer(mb) } // Close implements net.Conn.Close(). func (c *connection) Close() error { common.Must(c.done.Close()) common.Interrupt(c.reader) common.Close(c.writer) if c.onClose != nil { return c.onClose.Close() } return nil } // LocalAddr implements net.Conn.LocalAddr(). func (c *connection) LocalAddr() net.Addr { return c.local } // RemoteAddr implements net.Conn.RemoteAddr(). func (c *connection) RemoteAddr() net.Addr { return c.remote } // SetDeadline implements net.Conn.SetDeadline(). func (c *connection) SetDeadline(t time.Time) error { return nil } // SetReadDeadline implements net.Conn.SetReadDeadline(). func (c *connection) SetReadDeadline(t time.Time) error { return nil } // SetWriteDeadline implements net.Conn.SetWriteDeadline(). func (c *connection) SetWriteDeadline(t time.Time) error { return nil } ================================================ FILE: common/net/destination.go ================================================ package net import ( "net" "strings" ) // Destination represents a network destination including address and protocol (tcp / udp). type Destination struct { Address Address Port Port Network Network } // DestinationFromAddr generates a Destination from a net address. func DestinationFromAddr(addr net.Addr) Destination { switch addr := addr.(type) { case *net.TCPAddr: return TCPDestination(IPAddress(addr.IP), Port(addr.Port)) case *net.UDPAddr: return UDPDestination(IPAddress(addr.IP), Port(addr.Port)) case *net.UnixAddr: // TODO: deal with Unix domain socket return TCPDestination(LocalHostIP, Port(9)) default: panic("Net: Unknown address type.") } } // ParseDestination converts a destination from its string presentation. func ParseDestination(dest string) (Destination, error) { d := Destination{ Address: AnyIP, Port: Port(0), } if strings.HasPrefix(dest, "tcp:") { d.Network = Network_TCP dest = dest[4:] } else if strings.HasPrefix(dest, "udp:") { d.Network = Network_UDP dest = dest[4:] } hstr, pstr, err := SplitHostPort(dest) if err != nil { return d, err } if len(hstr) > 0 { d.Address = ParseAddress(hstr) } if len(pstr) > 0 { port, err := PortFromString(pstr) if err != nil { return d, err } d.Port = port } return d, nil } // TCPDestination creates a TCP destination with given address func TCPDestination(address Address, port Port) Destination { return Destination{ Network: Network_TCP, Address: address, Port: port, } } // UDPDestination creates a UDP destination with given address func UDPDestination(address Address, port Port) Destination { return Destination{ Network: Network_UDP, Address: address, Port: port, } } // NetAddr returns the network address in this Destination in string form. func (d Destination) NetAddr() string { return d.Address.String() + ":" + d.Port.String() } // String returns the strings form of this Destination. func (d Destination) String() string { prefix := "unknown:" switch d.Network { case Network_TCP: prefix = "tcp:" case Network_UDP: prefix = "udp:" } return prefix + d.NetAddr() } // IsValid returns true if this Destination is valid. func (d Destination) IsValid() bool { return d.Network != Network_Unknown } // AsDestination converts current Endpoint into Destination. func (p *Endpoint) AsDestination() Destination { return Destination{ Network: p.Network, Address: p.Address.AsAddress(), Port: Port(p.Port), } } ================================================ FILE: common/net/destination.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/net/destination.proto package net import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Endpoint of a network connection. type Endpoint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Network Network `protobuf:"varint,1,opt,name=network,proto3,enum=v2ray.core.common.net.Network" json:"network,omitempty"` Address *IPOrDomain `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` } func (x *Endpoint) Reset() { *x = Endpoint{} if protoimpl.UnsafeEnabled { mi := &file_common_net_destination_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Endpoint) String() string { return protoimpl.X.MessageStringOf(x) } func (*Endpoint) ProtoMessage() {} func (x *Endpoint) ProtoReflect() protoreflect.Message { mi := &file_common_net_destination_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Endpoint.ProtoReflect.Descriptor instead. func (*Endpoint) Descriptor() ([]byte, []int) { return file_common_net_destination_proto_rawDescGZIP(), []int{0} } func (x *Endpoint) GetNetwork() Network { if x != nil { return x.Network } return Network_Unknown } func (x *Endpoint) GetAddress() *IPOrDomain { if x != nil { return x.Address } return nil } func (x *Endpoint) GetPort() uint32 { if x != nil { return x.Port } return 0 } var File_common_net_destination_proto protoreflect.FileDescriptor var file_common_net_destination_proto_rawDesc = []byte{ 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x95, 0x01, 0x0a, 0x08, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_net_destination_proto_rawDescOnce sync.Once file_common_net_destination_proto_rawDescData = file_common_net_destination_proto_rawDesc ) func file_common_net_destination_proto_rawDescGZIP() []byte { file_common_net_destination_proto_rawDescOnce.Do(func() { file_common_net_destination_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_destination_proto_rawDescData) }) return file_common_net_destination_proto_rawDescData } var file_common_net_destination_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_net_destination_proto_goTypes = []interface{}{ (*Endpoint)(nil), // 0: v2ray.core.common.net.Endpoint (Network)(0), // 1: v2ray.core.common.net.Network (*IPOrDomain)(nil), // 2: v2ray.core.common.net.IPOrDomain } var file_common_net_destination_proto_depIdxs = []int32{ 1, // 0: v2ray.core.common.net.Endpoint.network:type_name -> v2ray.core.common.net.Network 2, // 1: v2ray.core.common.net.Endpoint.address:type_name -> v2ray.core.common.net.IPOrDomain 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_common_net_destination_proto_init() } func file_common_net_destination_proto_init() { if File_common_net_destination_proto != nil { return } file_common_net_network_proto_init() file_common_net_address_proto_init() if !protoimpl.UnsafeEnabled { file_common_net_destination_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Endpoint); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_net_destination_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_net_destination_proto_goTypes, DependencyIndexes: file_common_net_destination_proto_depIdxs, MessageInfos: file_common_net_destination_proto_msgTypes, }.Build() File_common_net_destination_proto = out.File file_common_net_destination_proto_rawDesc = nil file_common_net_destination_proto_goTypes = nil file_common_net_destination_proto_depIdxs = nil } ================================================ FILE: common/net/destination.proto ================================================ syntax = "proto3"; package v2ray.core.common.net; option csharp_namespace = "V2Ray.Core.Common.Net"; option go_package = "v2ray.com/core/common/net"; option java_package = "com.v2ray.core.common.net"; option java_multiple_files = true; import "common/net/network.proto"; import "common/net/address.proto"; // Endpoint of a network connection. message Endpoint { Network network = 1; IPOrDomain address = 2; uint32 port = 3; } ================================================ FILE: common/net/destination_test.go ================================================ package net_test import ( "testing" "github.com/google/go-cmp/cmp" . "v2ray.com/core/common/net" ) func TestDestinationProperty(t *testing.T) { testCases := []struct { Input Destination Network Network String string NetString string }{ { Input: TCPDestination(IPAddress([]byte{1, 2, 3, 4}), 80), Network: Network_TCP, String: "tcp:1.2.3.4:80", NetString: "1.2.3.4:80", }, { Input: UDPDestination(IPAddress([]byte{0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88}), 53), Network: Network_UDP, String: "udp:[2001:4860:4860::8888]:53", NetString: "[2001:4860:4860::8888]:53", }, } for _, testCase := range testCases { dest := testCase.Input if r := cmp.Diff(dest.Network, testCase.Network); r != "" { t.Error("unexpected Network in ", dest.String(), ": ", r) } if r := cmp.Diff(dest.String(), testCase.String); r != "" { t.Error(r) } if r := cmp.Diff(dest.NetAddr(), testCase.NetString); r != "" { t.Error(r) } } } func TestDestinationParse(t *testing.T) { cases := []struct { Input string Output Destination Error bool }{ { Input: "tcp:127.0.0.1:80", Output: TCPDestination(LocalHostIP, Port(80)), }, { Input: "udp:8.8.8.8:53", Output: UDPDestination(IPAddress([]byte{8, 8, 8, 8}), Port(53)), }, { Input: "8.8.8.8:53", Output: Destination{ Address: IPAddress([]byte{8, 8, 8, 8}), Port: Port(53), }, }, { Input: ":53", Output: Destination{ Address: AnyIP, Port: Port(53), }, }, { Input: "8.8.8.8", Error: true, }, { Input: "8.8.8.8:http", Error: true, }, } for _, testcase := range cases { d, err := ParseDestination(testcase.Input) if !testcase.Error { if err != nil { t.Error("for test case: ", testcase.Input, " expected no error, but got ", err) } if d != testcase.Output { t.Error("for test case: ", testcase.Input, " expected output: ", testcase.Output.String(), " but got ", d.String()) } } else { if err == nil { t.Error("for test case: ", testcase.Input, " expected error, but got nil") } } } } ================================================ FILE: common/net/errors.generated.go ================================================ package net import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/net/net.go ================================================ // Package net is a drop-in replacement to Golang's net package, with some more functionalities. package net // import "v2ray.com/core/common/net" //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: common/net/network.go ================================================ package net func (n Network) SystemString() string { switch n { case Network_TCP: return "tcp" case Network_UDP: return "udp" default: return "unknown" } } // HasNetwork returns true if the network list has a certain network. func HasNetwork(list []Network, network Network) bool { for _, value := range list { if value == network { return true } } return false } ================================================ FILE: common/net/network.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/net/network.proto package net import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Network int32 const ( Network_Unknown Network = 0 // Deprecated: Do not use. Network_RawTCP Network = 1 Network_TCP Network = 2 Network_UDP Network = 3 ) // Enum value maps for Network. var ( Network_name = map[int32]string{ 0: "Unknown", 1: "RawTCP", 2: "TCP", 3: "UDP", } Network_value = map[string]int32{ "Unknown": 0, "RawTCP": 1, "TCP": 2, "UDP": 3, } ) func (x Network) Enum() *Network { p := new(Network) *p = x return p } func (x Network) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Network) Descriptor() protoreflect.EnumDescriptor { return file_common_net_network_proto_enumTypes[0].Descriptor() } func (Network) Type() protoreflect.EnumType { return &file_common_net_network_proto_enumTypes[0] } func (x Network) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Network.Descriptor instead. func (Network) EnumDescriptor() ([]byte, []int) { return file_common_net_network_proto_rawDescGZIP(), []int{0} } // NetworkList is a list of Networks. type NetworkList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Network []Network `protobuf:"varint,1,rep,packed,name=network,proto3,enum=v2ray.core.common.net.Network" json:"network,omitempty"` } func (x *NetworkList) Reset() { *x = NetworkList{} if protoimpl.UnsafeEnabled { mi := &file_common_net_network_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *NetworkList) String() string { return protoimpl.X.MessageStringOf(x) } func (*NetworkList) ProtoMessage() {} func (x *NetworkList) ProtoReflect() protoreflect.Message { mi := &file_common_net_network_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use NetworkList.ProtoReflect.Descriptor instead. func (*NetworkList) Descriptor() ([]byte, []int) { return file_common_net_network_proto_rawDescGZIP(), []int{0} } func (x *NetworkList) GetNetwork() []Network { if x != nil { return x.Network } return nil } var File_common_net_network_proto protoreflect.FileDescriptor var file_common_net_network_proto_rawDesc = []byte{ 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x22, 0x47, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2a, 0x38, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x06, 0x52, 0x61, 0x77, 0x54, 0x43, 0x50, 0x10, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_net_network_proto_rawDescOnce sync.Once file_common_net_network_proto_rawDescData = file_common_net_network_proto_rawDesc ) func file_common_net_network_proto_rawDescGZIP() []byte { file_common_net_network_proto_rawDescOnce.Do(func() { file_common_net_network_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_network_proto_rawDescData) }) return file_common_net_network_proto_rawDescData } var file_common_net_network_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_common_net_network_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_net_network_proto_goTypes = []interface{}{ (Network)(0), // 0: v2ray.core.common.net.Network (*NetworkList)(nil), // 1: v2ray.core.common.net.NetworkList } var file_common_net_network_proto_depIdxs = []int32{ 0, // 0: v2ray.core.common.net.NetworkList.network:type_name -> v2ray.core.common.net.Network 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_common_net_network_proto_init() } func file_common_net_network_proto_init() { if File_common_net_network_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_net_network_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NetworkList); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_net_network_proto_rawDesc, NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_net_network_proto_goTypes, DependencyIndexes: file_common_net_network_proto_depIdxs, EnumInfos: file_common_net_network_proto_enumTypes, MessageInfos: file_common_net_network_proto_msgTypes, }.Build() File_common_net_network_proto = out.File file_common_net_network_proto_rawDesc = nil file_common_net_network_proto_goTypes = nil file_common_net_network_proto_depIdxs = nil } ================================================ FILE: common/net/network.proto ================================================ syntax = "proto3"; package v2ray.core.common.net; option csharp_namespace = "V2Ray.Core.Common.Net"; option go_package = "v2ray.com/core/common/net"; option java_package = "com.v2ray.core.common.net"; option java_multiple_files = true; enum Network { Unknown = 0; RawTCP = 1 [deprecated = true]; TCP = 2; UDP = 3; } // NetworkList is a list of Networks. message NetworkList { repeated Network network = 1; } ================================================ FILE: common/net/port.go ================================================ package net import ( "encoding/binary" "strconv" ) // Port represents a network port in TCP and UDP protocol. type Port uint16 // PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order. // @unsafe Caller must ensure that the byte array has at least 2 elements. func PortFromBytes(port []byte) Port { return Port(binary.BigEndian.Uint16(port)) } // PortFromInt converts an integer to a Port. // @error when the integer is not positive or larger then 65535 func PortFromInt(val uint32) (Port, error) { if val > 65535 { return Port(0), newError("invalid port range: ", val) } return Port(val), nil } // PortFromString converts a string to a Port. // @error when the string is not an integer or the integral value is a not a valid Port. func PortFromString(s string) (Port, error) { val, err := strconv.ParseUint(s, 10, 32) if err != nil { return Port(0), newError("invalid port range: ", s) } return PortFromInt(uint32(val)) } // Value return the corresponding uint16 value of a Port. func (p Port) Value() uint16 { return uint16(p) } // String returns the string presentation of a Port. func (p Port) String() string { return strconv.Itoa(int(p)) } // FromPort returns the beginning port of this PortRange. func (p *PortRange) FromPort() Port { return Port(p.From) } // ToPort returns the end port of this PortRange. func (p *PortRange) ToPort() Port { return Port(p.To) } // Contains returns true if the given port is within the range of a PortRange. func (p *PortRange) Contains(port Port) bool { return p.FromPort() <= port && port <= p.ToPort() } // SinglePortRange returns a PortRange contains a single port. func SinglePortRange(p Port) *PortRange { return &PortRange{ From: uint32(p), To: uint32(p), } } type MemoryPortRange struct { From Port To Port } func (r MemoryPortRange) Contains(port Port) bool { return r.From <= port && port <= r.To } type MemoryPortList []MemoryPortRange func PortListFromProto(l *PortList) MemoryPortList { mpl := make(MemoryPortList, 0, len(l.Range)) for _, r := range l.Range { mpl = append(mpl, MemoryPortRange{From: Port(r.From), To: Port(r.To)}) } return mpl } func (mpl MemoryPortList) Contains(port Port) bool { for _, pr := range mpl { if pr.Contains(port) { return true } } return false } ================================================ FILE: common/net/port.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/net/port.proto package net import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // PortRange represents a range of ports. type PortRange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The port that this range starts from. From uint32 `protobuf:"varint,1,opt,name=From,proto3" json:"From,omitempty"` // The port that this range ends with (inclusive). To uint32 `protobuf:"varint,2,opt,name=To,proto3" json:"To,omitempty"` } func (x *PortRange) Reset() { *x = PortRange{} if protoimpl.UnsafeEnabled { mi := &file_common_net_port_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PortRange) String() string { return protoimpl.X.MessageStringOf(x) } func (*PortRange) ProtoMessage() {} func (x *PortRange) ProtoReflect() protoreflect.Message { mi := &file_common_net_port_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PortRange.ProtoReflect.Descriptor instead. func (*PortRange) Descriptor() ([]byte, []int) { return file_common_net_port_proto_rawDescGZIP(), []int{0} } func (x *PortRange) GetFrom() uint32 { if x != nil { return x.From } return 0 } func (x *PortRange) GetTo() uint32 { if x != nil { return x.To } return 0 } // PortList is a list of ports. type PortList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Range []*PortRange `protobuf:"bytes,1,rep,name=range,proto3" json:"range,omitempty"` } func (x *PortList) Reset() { *x = PortList{} if protoimpl.UnsafeEnabled { mi := &file_common_net_port_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PortList) String() string { return protoimpl.X.MessageStringOf(x) } func (*PortList) ProtoMessage() {} func (x *PortList) ProtoReflect() protoreflect.Message { mi := &file_common_net_port_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PortList.ProtoReflect.Descriptor instead. func (*PortList) Descriptor() ([]byte, []int) { return file_common_net_port_proto_rawDescGZIP(), []int{1} } func (x *PortList) GetRange() []*PortRange { if x != nil { return x.Range } return nil } var File_common_net_port_proto protoreflect.FileDescriptor var file_common_net_port_proto_rawDesc = []byte{ 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x22, 0x2f, 0x0a, 0x09, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x54, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x54, 0x6f, 0x22, 0x42, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_net_port_proto_rawDescOnce sync.Once file_common_net_port_proto_rawDescData = file_common_net_port_proto_rawDesc ) func file_common_net_port_proto_rawDescGZIP() []byte { file_common_net_port_proto_rawDescOnce.Do(func() { file_common_net_port_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_port_proto_rawDescData) }) return file_common_net_port_proto_rawDescData } var file_common_net_port_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_common_net_port_proto_goTypes = []interface{}{ (*PortRange)(nil), // 0: v2ray.core.common.net.PortRange (*PortList)(nil), // 1: v2ray.core.common.net.PortList } var file_common_net_port_proto_depIdxs = []int32{ 0, // 0: v2ray.core.common.net.PortList.range:type_name -> v2ray.core.common.net.PortRange 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_common_net_port_proto_init() } func file_common_net_port_proto_init() { if File_common_net_port_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_net_port_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PortRange); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_common_net_port_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PortList); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_net_port_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_net_port_proto_goTypes, DependencyIndexes: file_common_net_port_proto_depIdxs, MessageInfos: file_common_net_port_proto_msgTypes, }.Build() File_common_net_port_proto = out.File file_common_net_port_proto_rawDesc = nil file_common_net_port_proto_goTypes = nil file_common_net_port_proto_depIdxs = nil } ================================================ FILE: common/net/port.proto ================================================ syntax = "proto3"; package v2ray.core.common.net; option csharp_namespace = "V2Ray.Core.Common.Net"; option go_package = "v2ray.com/core/common/net"; option java_package = "com.v2ray.core.common.net"; option java_multiple_files = true; // PortRange represents a range of ports. message PortRange { // The port that this range starts from. uint32 From = 1; // The port that this range ends with (inclusive). uint32 To = 2; } // PortList is a list of ports. message PortList { repeated PortRange range = 1; } ================================================ FILE: common/net/port_test.go ================================================ package net_test import ( "testing" . "v2ray.com/core/common/net" ) func TestPortRangeContains(t *testing.T) { portRange := &PortRange{ From: 53, To: 53, } if !portRange.Contains(Port(53)) { t.Error("expected port range containing 53, but actually not") } } ================================================ FILE: common/net/system.go ================================================ package net import "net" // DialTCP is an alias of net.DialTCP. var DialTCP = net.DialTCP var DialUDP = net.DialUDP var DialUnix = net.DialUnix var Dial = net.Dial type ListenConfig = net.ListenConfig var Listen = net.Listen var ListenTCP = net.ListenTCP var ListenUDP = net.ListenUDP var ListenUnix = net.ListenUnix var LookupIP = net.LookupIP var FileConn = net.FileConn // ParseIP is an alias of net.ParseIP var ParseIP = net.ParseIP var SplitHostPort = net.SplitHostPort var CIDRMask = net.CIDRMask type Addr = net.Addr type Conn = net.Conn type PacketConn = net.PacketConn type TCPAddr = net.TCPAddr type TCPConn = net.TCPConn type UDPAddr = net.UDPAddr type UDPConn = net.UDPConn type UnixAddr = net.UnixAddr type UnixConn = net.UnixConn // IP is an alias for net.IP. type IP = net.IP type IPMask = net.IPMask type IPNet = net.IPNet const IPv4len = net.IPv4len const IPv6len = net.IPv6len type Error = net.Error type AddrError = net.AddrError type Dialer = net.Dialer type Listener = net.Listener type TCPListener = net.TCPListener type UnixListener = net.UnixListener var ResolveUnixAddr = net.ResolveUnixAddr var ResolveUDPAddr = net.ResolveUDPAddr type Resolver = net.Resolver ================================================ FILE: common/peer/latency.go ================================================ package peer import ( "sync" ) type Latency interface { Value() uint64 } type HasLatency interface { ConnectionLatency() Latency HandshakeLatency() Latency } type AverageLatency struct { access sync.Mutex value uint64 } func (al *AverageLatency) Update(newValue uint64) { al.access.Lock() defer al.access.Unlock() al.value = (al.value + newValue*2) / 3 } func (al *AverageLatency) Value() uint64 { return al.value } ================================================ FILE: common/peer/peer.go ================================================ package peer ================================================ FILE: common/platform/ctlcmd/attr_other.go ================================================ // +build !windows package ctlcmd import "syscall" func getSysProcAttr() *syscall.SysProcAttr { return nil } ================================================ FILE: common/platform/ctlcmd/attr_windows.go ================================================ // +build windows package ctlcmd import "syscall" func getSysProcAttr() *syscall.SysProcAttr { return &syscall.SysProcAttr{ HideWindow: true, } } ================================================ FILE: common/platform/ctlcmd/ctlcmd.go ================================================ package ctlcmd import ( "io" "os" "os/exec" "strings" "v2ray.com/core/common/buf" "v2ray.com/core/common/platform" ) //go:generate go run v2ray.com/core/common/errors/errorgen func Run(args []string, input io.Reader) (buf.MultiBuffer, error) { v2ctl := platform.GetToolLocation("v2ctl") if _, err := os.Stat(v2ctl); err != nil { return nil, newError("v2ctl doesn't exist").Base(err) } var errBuffer buf.MultiBufferContainer var outBuffer buf.MultiBufferContainer cmd := exec.Command(v2ctl, args...) cmd.Stderr = &errBuffer cmd.Stdout = &outBuffer cmd.SysProcAttr = getSysProcAttr() if input != nil { cmd.Stdin = input } if err := cmd.Start(); err != nil { return nil, newError("failed to start v2ctl").Base(err) } if err := cmd.Wait(); err != nil { msg := "failed to execute v2ctl" if errBuffer.Len() > 0 { msg += ": \n" + strings.TrimSpace(errBuffer.MultiBuffer.String()) } return nil, newError(msg).Base(err) } // log stderr, info message if !errBuffer.IsEmpty() { newError(" \n", strings.TrimSpace(errBuffer.MultiBuffer.String())).AtInfo().WriteToLog() } return outBuffer.MultiBuffer, nil } ================================================ FILE: common/platform/ctlcmd/errors.generated.go ================================================ package ctlcmd import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/platform/filesystem/file.go ================================================ package filesystem import ( "io" "os" "v2ray.com/core/common/buf" "v2ray.com/core/common/platform" ) type FileReaderFunc func(path string) (io.ReadCloser, error) var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) { return os.Open(path) } func ReadFile(path string) ([]byte, error) { reader, err := NewFileReader(path) if err != nil { return nil, err } defer reader.Close() return buf.ReadAllToBytes(reader) } func ReadAsset(file string) ([]byte, error) { return ReadFile(platform.GetAssetLocation(file)) } func CopyFile(dst string, src string) error { bytes, err := ReadFile(src) if err != nil { return err } f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() _, err = f.Write(bytes) return err } ================================================ FILE: common/platform/others.go ================================================ // +build !windows package platform import ( "os" "path/filepath" ) func ExpandEnv(s string) string { return os.ExpandEnv(s) } func LineSeparator() string { return "\n" } func GetToolLocation(file string) string { const name = "v2ray.location.tool" toolPath := EnvFlag{Name: name, AltName: NormalizeEnvName(name)}.GetValue(getExecutableDir) return filepath.Join(toolPath, file) } // GetAssetLocation search for `file` in certain locations func GetAssetLocation(file string) string { const name = "v2ray.location.asset" assetPath := NewEnvFlag(name).GetValue(getExecutableDir) defPath := filepath.Join(assetPath, file) for _, p := range []string{ defPath, filepath.Join("/usr/local/share/v2ray/", file), filepath.Join("/usr/share/v2ray/", file), } { if _, err := os.Stat(p); os.IsNotExist(err) { continue } // asset found return p } // asset not found, let the caller throw out the error return defPath } ================================================ FILE: common/platform/platform.go ================================================ package platform // import "v2ray.com/core/common/platform" import ( "os" "path/filepath" "strconv" "strings" ) type EnvFlag struct { Name string AltName string } func NewEnvFlag(name string) EnvFlag { return EnvFlag{ Name: name, AltName: NormalizeEnvName(name), } } func (f EnvFlag) GetValue(defaultValue func() string) string { if v, found := os.LookupEnv(f.Name); found { return v } if len(f.AltName) > 0 { if v, found := os.LookupEnv(f.AltName); found { return v } } return defaultValue() } func (f EnvFlag) GetValueAsInt(defaultValue int) int { useDefaultValue := false s := f.GetValue(func() string { useDefaultValue = true return "" }) if useDefaultValue { return defaultValue } v, err := strconv.ParseInt(s, 10, 32) if err != nil { return defaultValue } return int(v) } func NormalizeEnvName(name string) string { return strings.Replace(strings.ToUpper(strings.TrimSpace(name)), ".", "_", -1) } func getExecutableDir() string { exec, err := os.Executable() if err != nil { return "" } return filepath.Dir(exec) } func getExecutableSubDir(dir string) func() string { return func() string { return filepath.Join(getExecutableDir(), dir) } } func GetPluginDirectory() string { const name = "v2ray.location.plugin" pluginDir := NewEnvFlag(name).GetValue(getExecutableSubDir("plugins")) return pluginDir } func GetConfigurationPath() string { const name = "v2ray.location.config" configPath := NewEnvFlag(name).GetValue(getExecutableDir) return filepath.Join(configPath, "config.json") } // GetConfDirPath reads "v2ray.location.confdir" func GetConfDirPath() string { const name = "v2ray.location.confdir" configPath := NewEnvFlag(name).GetValue(func() string { return "" }) return configPath } ================================================ FILE: common/platform/platform_test.go ================================================ package platform_test import ( "os" "path/filepath" "runtime" "testing" "v2ray.com/core/common" . "v2ray.com/core/common/platform" ) func TestNormalizeEnvName(t *testing.T) { cases := []struct { input string output string }{ { input: "a", output: "A", }, { input: "a.a", output: "A_A", }, { input: "A.A.B", output: "A_A_B", }, } for _, test := range cases { if v := NormalizeEnvName(test.input); v != test.output { t.Error("unexpected output: ", v, " want ", test.output) } } } func TestEnvFlag(t *testing.T) { if v := (EnvFlag{ Name: "xxxxx.y", }.GetValueAsInt(10)); v != 10 { t.Error("env value: ", v) } } func TestGetAssetLocation(t *testing.T) { exec, err := os.Executable() common.Must(err) loc := GetAssetLocation("t") if filepath.Dir(loc) != filepath.Dir(exec) { t.Error("asset dir: ", loc, " not in ", exec) } os.Setenv("v2ray.location.asset", "/v2ray") if runtime.GOOS == "windows" { if v := GetAssetLocation("t"); v != "\\v2ray\\t" { t.Error("asset loc: ", v) } } else { if v := GetAssetLocation("t"); v != "/v2ray/t" { t.Error("asset loc: ", v) } } } ================================================ FILE: common/platform/windows.go ================================================ // +build windows package platform import "path/filepath" func ExpandEnv(s string) string { // TODO return s } func LineSeparator() string { return "\r\n" } func GetToolLocation(file string) string { const name = "v2ray.location.tool" toolPath := EnvFlag{Name: name, AltName: NormalizeEnvName(name)}.GetValue(getExecutableDir) return filepath.Join(toolPath, file+".exe") } // GetAssetLocation search for `file` in the excutable dir func GetAssetLocation(file string) string { const name = "v2ray.location.asset" assetPath := NewEnvFlag(name).GetValue(getExecutableDir) return filepath.Join(assetPath, file) } ================================================ FILE: common/protocol/account.go ================================================ package protocol // Account is a user identity used for authentication. type Account interface { Equals(Account) bool } // AsAccount is an object can be converted into account. type AsAccount interface { AsAccount() (Account, error) } ================================================ FILE: common/protocol/address.go ================================================ package protocol import ( "io" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" ) type AddressOption func(*option) func PortThenAddress() AddressOption { return func(p *option) { p.portFirst = true } } func AddressFamilyByte(b byte, f net.AddressFamily) AddressOption { if b >= 16 { panic("address family byte too big") } return func(p *option) { p.addrTypeMap[b] = f p.addrByteMap[f] = b } } type AddressTypeParser func(byte) byte func WithAddressTypeParser(atp AddressTypeParser) AddressOption { return func(p *option) { p.typeParser = atp } } type AddressSerializer interface { ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error } const afInvalid = 255 type option struct { addrTypeMap [16]net.AddressFamily addrByteMap [16]byte portFirst bool typeParser AddressTypeParser } // NewAddressParser creates a new AddressParser func NewAddressParser(options ...AddressOption) AddressSerializer { var o option for i := range o.addrByteMap { o.addrByteMap[i] = afInvalid } for i := range o.addrTypeMap { o.addrTypeMap[i] = net.AddressFamily(afInvalid) } for _, opt := range options { opt(&o) } ap := &addressParser{ addrByteMap: o.addrByteMap, addrTypeMap: o.addrTypeMap, } if o.typeParser != nil { ap.typeParser = o.typeParser } if o.portFirst { return portFirstAddressParser{ap: ap} } return portLastAddressParser{ap: ap} } type portFirstAddressParser struct { ap *addressParser } func (p portFirstAddressParser) ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) { if buffer == nil { buffer = buf.New() defer buffer.Release() } port, err := readPort(buffer, input) if err != nil { return nil, 0, err } addr, err := p.ap.readAddress(buffer, input) if err != nil { return nil, 0, err } return addr, port, nil } func (p portFirstAddressParser) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error { if err := writePort(writer, port); err != nil { return err } return p.ap.writeAddress(writer, addr) } type portLastAddressParser struct { ap *addressParser } func (p portLastAddressParser) ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) { if buffer == nil { buffer = buf.New() defer buffer.Release() } addr, err := p.ap.readAddress(buffer, input) if err != nil { return nil, 0, err } port, err := readPort(buffer, input) if err != nil { return nil, 0, err } return addr, port, nil } func (p portLastAddressParser) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error { if err := p.ap.writeAddress(writer, addr); err != nil { return err } return writePort(writer, port) } func readPort(b *buf.Buffer, reader io.Reader) (net.Port, error) { if _, err := b.ReadFullFrom(reader, 2); err != nil { return 0, err } return net.PortFromBytes(b.BytesFrom(-2)), nil } func writePort(writer io.Writer, port net.Port) error { return common.Error2(serial.WriteUint16(writer, port.Value())) } func maybeIPPrefix(b byte) bool { return b == '[' || (b >= '0' && b <= '9') } func isValidDomain(d string) bool { for _, c := range d { if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '.' || c == '_') { return false } } return true } type addressParser struct { addrTypeMap [16]net.AddressFamily addrByteMap [16]byte typeParser AddressTypeParser } func (p *addressParser) readAddress(b *buf.Buffer, reader io.Reader) (net.Address, error) { if _, err := b.ReadFullFrom(reader, 1); err != nil { return nil, err } addrType := b.Byte(b.Len() - 1) if p.typeParser != nil { addrType = p.typeParser(addrType) } if addrType >= 16 { return nil, newError("unknown address type: ", addrType) } addrFamily := p.addrTypeMap[addrType] if addrFamily == net.AddressFamily(afInvalid) { return nil, newError("unknown address type: ", addrType) } switch addrFamily { case net.AddressFamilyIPv4: if _, err := b.ReadFullFrom(reader, 4); err != nil { return nil, err } return net.IPAddress(b.BytesFrom(-4)), nil case net.AddressFamilyIPv6: if _, err := b.ReadFullFrom(reader, 16); err != nil { return nil, err } return net.IPAddress(b.BytesFrom(-16)), nil case net.AddressFamilyDomain: if _, err := b.ReadFullFrom(reader, 1); err != nil { return nil, err } domainLength := int32(b.Byte(b.Len() - 1)) if _, err := b.ReadFullFrom(reader, domainLength); err != nil { return nil, err } domain := string(b.BytesFrom(-domainLength)) if maybeIPPrefix(domain[0]) { addr := net.ParseAddress(domain) if addr.Family().IsIP() { return addr, nil } } if !isValidDomain(domain) { return nil, newError("invalid domain name: ", domain) } return net.DomainAddress(domain), nil default: panic("impossible case") } } func (p *addressParser) writeAddress(writer io.Writer, address net.Address) error { tb := p.addrByteMap[address.Family()] if tb == afInvalid { return newError("unknown address family", address.Family()) } switch address.Family() { case net.AddressFamilyIPv4, net.AddressFamilyIPv6: if _, err := writer.Write([]byte{tb}); err != nil { return err } if _, err := writer.Write(address.IP()); err != nil { return err } case net.AddressFamilyDomain: domain := address.Domain() if isDomainTooLong(domain) { return newError("Super long domain is not supported: ", domain) } if _, err := writer.Write([]byte{tb, byte(len(domain))}); err != nil { return err } if _, err := writer.Write([]byte(domain)); err != nil { return err } default: panic("Unknown family type.") } return nil } ================================================ FILE: common/protocol/address_test.go ================================================ package protocol_test import ( "bytes" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" . "v2ray.com/core/common/protocol" ) func TestAddressReading(t *testing.T) { data := []struct { Options []AddressOption Input []byte Address net.Address Port net.Port Error bool }{ { Options: []AddressOption{}, Input: []byte{}, Error: true, }, { Options: []AddressOption{}, Input: []byte{0, 0, 0, 0, 0}, Error: true, }, { Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)}, Input: []byte{1, 0, 0, 0, 0, 0, 53}, Address: net.IPAddress([]byte{0, 0, 0, 0}), Port: net.Port(53), }, { Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4), PortThenAddress()}, Input: []byte{0, 53, 1, 0, 0, 0, 0}, Address: net.IPAddress([]byte{0, 0, 0, 0}), Port: net.Port(53), }, { Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)}, Input: []byte{1, 0, 0, 0, 0}, Error: true, }, { Options: []AddressOption{AddressFamilyByte(0x04, net.AddressFamilyIPv6)}, Input: []byte{4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 80}, Address: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), Port: net.Port(80), }, { Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)}, Input: []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0, 80}, Address: net.DomainAddress("v2ray.com"), Port: net.Port(80), }, { Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)}, Input: []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0}, Error: true, }, { Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)}, Input: []byte{3, 7, 56, 46, 56, 46, 56, 46, 56, 0, 80}, Address: net.ParseAddress("8.8.8.8"), Port: net.Port(80), }, { Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)}, Input: []byte{3, 7, 10, 46, 56, 46, 56, 46, 56, 0, 80}, Error: true, }, { Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)}, Input: append(append([]byte{3, 24}, []byte("2a00:1450:4007:816::200e")...), 0, 80), Address: net.ParseAddress("2a00:1450:4007:816::200e"), Port: net.Port(80), }, } for _, tc := range data { b := buf.New() parser := NewAddressParser(tc.Options...) addr, port, err := parser.ReadAddressPort(b, bytes.NewReader(tc.Input)) b.Release() if tc.Error { if err == nil { t.Errorf("Expect error but not: %v", tc) } } else { if err != nil { t.Errorf("Expect no error but: %s %v", err.Error(), tc) } if addr != tc.Address { t.Error("Got address ", addr.String(), " want ", tc.Address.String()) } if tc.Port != port { t.Error("Got port ", port, " want ", tc.Port) } } } } func TestAddressWriting(t *testing.T) { data := []struct { Options []AddressOption Address net.Address Port net.Port Bytes []byte Error bool }{ { Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)}, Address: net.LocalHostIP, Port: net.Port(80), Bytes: []byte{1, 127, 0, 0, 1, 0, 80}, }, } for _, tc := range data { parser := NewAddressParser(tc.Options...) b := buf.New() err := parser.WriteAddressPort(b, tc.Address, tc.Port) if tc.Error { if err == nil { t.Error("Expect error but nil") } } else { common.Must(err) if diff := cmp.Diff(tc.Bytes, b.Bytes()); diff != "" { t.Error(err) } } } } func BenchmarkAddressReadingIPv4(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x01, net.AddressFamilyIPv4)) cache := buf.New() defer cache.Release() payload := buf.New() defer payload.Release() raw := []byte{1, 0, 0, 0, 0, 0, 53} payload.Write(raw) b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := parser.ReadAddressPort(cache, payload) common.Must(err) cache.Clear() payload.Clear() payload.Extend(int32(len(raw))) } } func BenchmarkAddressReadingIPv6(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x04, net.AddressFamilyIPv6)) cache := buf.New() defer cache.Release() payload := buf.New() defer payload.Release() raw := []byte{4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 80} payload.Write(raw) b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := parser.ReadAddressPort(cache, payload) common.Must(err) cache.Clear() payload.Clear() payload.Extend(int32(len(raw))) } } func BenchmarkAddressReadingDomain(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x03, net.AddressFamilyDomain)) cache := buf.New() defer cache.Release() payload := buf.New() defer payload.Release() raw := []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0, 80} payload.Write(raw) b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := parser.ReadAddressPort(cache, payload) common.Must(err) cache.Clear() payload.Clear() payload.Extend(int32(len(raw))) } } func BenchmarkAddressWritingIPv4(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x01, net.AddressFamilyIPv4)) writer := buf.New() defer writer.Release() b.ResetTimer() for i := 0; i < b.N; i++ { common.Must(parser.WriteAddressPort(writer, net.LocalHostIP, net.Port(80))) writer.Clear() } } func BenchmarkAddressWritingIPv6(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x04, net.AddressFamilyIPv6)) writer := buf.New() defer writer.Release() b.ResetTimer() for i := 0; i < b.N; i++ { common.Must(parser.WriteAddressPort(writer, net.LocalHostIPv6, net.Port(80))) writer.Clear() } } func BenchmarkAddressWritingDomain(b *testing.B) { parser := NewAddressParser(AddressFamilyByte(0x02, net.AddressFamilyDomain)) writer := buf.New() defer writer.Release() b.ResetTimer() for i := 0; i < b.N; i++ { common.Must(parser.WriteAddressPort(writer, net.DomainAddress("www.v2ray.com"), net.Port(80))) writer.Clear() } } ================================================ FILE: common/protocol/bittorrent/bittorrent.go ================================================ package bittorrent import ( "errors" "v2ray.com/core/common" ) type SniffHeader struct { } func (h *SniffHeader) Protocol() string { return "bittorrent" } func (h *SniffHeader) Domain() string { return "" } var errNotBittorrent = errors.New("not bittorrent header") func SniffBittorrent(b []byte) (*SniffHeader, error) { if len(b) < 20 { return nil, common.ErrNoClue } if b[0] == 19 && string(b[1:20]) == "BitTorrent protocol" { return &SniffHeader{}, nil } return nil, errNotBittorrent } ================================================ FILE: common/protocol/context.go ================================================ package protocol import ( "context" ) type key int const ( requestKey key = iota ) func ContextWithRequestHeader(ctx context.Context, request *RequestHeader) context.Context { return context.WithValue(ctx, requestKey, request) } func RequestHeaderFromContext(ctx context.Context) *RequestHeader { request := ctx.Value(requestKey) if request == nil { return nil } return request.(*RequestHeader) } ================================================ FILE: common/protocol/dns/errors.generated.go ================================================ package dns import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/protocol/dns/io.go ================================================ package dns import ( "encoding/binary" "sync" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/serial" ) func PackMessage(msg *dnsmessage.Message) (*buf.Buffer, error) { buffer := buf.New() rawBytes := buffer.Extend(buf.Size) packed, err := msg.AppendPack(rawBytes[:0]) if err != nil { buffer.Release() return nil, err } buffer.Resize(0, int32(len(packed))) return buffer, nil } type MessageReader interface { ReadMessage() (*buf.Buffer, error) } type UDPReader struct { buf.Reader access sync.Mutex cache buf.MultiBuffer } func (r *UDPReader) readCache() *buf.Buffer { r.access.Lock() defer r.access.Unlock() mb, b := buf.SplitFirst(r.cache) r.cache = mb return b } func (r *UDPReader) refill() error { mb, err := r.Reader.ReadMultiBuffer() if err != nil { return err } r.access.Lock() r.cache = mb r.access.Unlock() return nil } // ReadMessage implements MessageReader. func (r *UDPReader) ReadMessage() (*buf.Buffer, error) { for { b := r.readCache() if b != nil { return b, nil } if err := r.refill(); err != nil { return nil, err } } } // Close implements common.Closable. func (r *UDPReader) Close() error { defer func() { r.access.Lock() buf.ReleaseMulti(r.cache) r.cache = nil r.access.Unlock() }() return common.Close(r.Reader) } type TCPReader struct { reader *buf.BufferedReader } func NewTCPReader(reader buf.Reader) *TCPReader { return &TCPReader{ reader: &buf.BufferedReader{ Reader: reader, }, } } func (r *TCPReader) ReadMessage() (*buf.Buffer, error) { size, err := serial.ReadUint16(r.reader) if err != nil { return nil, err } if size > buf.Size { return nil, newError("message size too large: ", size) } b := buf.New() if _, err := b.ReadFullFrom(r.reader, int32(size)); err != nil { return nil, err } return b, nil } func (r *TCPReader) Interrupt() { common.Interrupt(r.reader) } func (r *TCPReader) Close() error { return common.Close(r.reader) } type MessageWriter interface { WriteMessage(msg *buf.Buffer) error } type UDPWriter struct { buf.Writer } func (w *UDPWriter) WriteMessage(b *buf.Buffer) error { return w.WriteMultiBuffer(buf.MultiBuffer{b}) } type TCPWriter struct { buf.Writer } func (w *TCPWriter) WriteMessage(b *buf.Buffer) error { if b.IsEmpty() { return nil } mb := make(buf.MultiBuffer, 0, 2) size := buf.New() binary.BigEndian.PutUint16(size.Extend(2), uint16(b.Len())) mb = append(mb, size, b) return w.WriteMultiBuffer(mb) } ================================================ FILE: common/protocol/errors.generated.go ================================================ package protocol import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/protocol/headers.go ================================================ package protocol import ( "runtime" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/net" "v2ray.com/core/common/uuid" ) // RequestCommand is a custom command in a proxy request. type RequestCommand byte const ( RequestCommandTCP = RequestCommand(0x01) RequestCommandUDP = RequestCommand(0x02) RequestCommandMux = RequestCommand(0x03) ) func (c RequestCommand) TransferType() TransferType { switch c { case RequestCommandTCP, RequestCommandMux: return TransferTypeStream case RequestCommandUDP: return TransferTypePacket default: return TransferTypeStream } } const ( // RequestOptionChunkStream indicates request payload is chunked. Each chunk consists of length, authentication and payload. RequestOptionChunkStream bitmask.Byte = 0x01 // RequestOptionConnectionReuse indicates client side expects to reuse the connection. RequestOptionConnectionReuse bitmask.Byte = 0x02 RequestOptionChunkMasking bitmask.Byte = 0x04 RequestOptionGlobalPadding bitmask.Byte = 0x08 ) type RequestHeader struct { Version byte Command RequestCommand Option bitmask.Byte Security SecurityType Port net.Port Address net.Address User *MemoryUser } func (h *RequestHeader) Destination() net.Destination { if h.Command == RequestCommandUDP { return net.UDPDestination(h.Address, h.Port) } return net.TCPDestination(h.Address, h.Port) } const ( ResponseOptionConnectionReuse bitmask.Byte = 0x01 ) type ResponseCommand interface{} type ResponseHeader struct { Option bitmask.Byte Command ResponseCommand } type CommandSwitchAccount struct { Host net.Address Port net.Port ID uuid.UUID Level uint32 AlterIds uint16 ValidMin byte } func (sc *SecurityConfig) GetSecurityType() SecurityType { if sc == nil || sc.Type == SecurityType_AUTO { if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { return SecurityType_AES128_GCM } return SecurityType_CHACHA20_POLY1305 } return sc.Type } func isDomainTooLong(domain string) bool { return len(domain) > 256 } ================================================ FILE: common/protocol/headers.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/protocol/headers.proto package protocol import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type SecurityType int32 const ( SecurityType_UNKNOWN SecurityType = 0 SecurityType_LEGACY SecurityType = 1 SecurityType_AUTO SecurityType = 2 SecurityType_AES128_GCM SecurityType = 3 SecurityType_CHACHA20_POLY1305 SecurityType = 4 SecurityType_NONE SecurityType = 5 ) // Enum value maps for SecurityType. var ( SecurityType_name = map[int32]string{ 0: "UNKNOWN", 1: "LEGACY", 2: "AUTO", 3: "AES128_GCM", 4: "CHACHA20_POLY1305", 5: "NONE", } SecurityType_value = map[string]int32{ "UNKNOWN": 0, "LEGACY": 1, "AUTO": 2, "AES128_GCM": 3, "CHACHA20_POLY1305": 4, "NONE": 5, } ) func (x SecurityType) Enum() *SecurityType { p := new(SecurityType) *p = x return p } func (x SecurityType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SecurityType) Descriptor() protoreflect.EnumDescriptor { return file_common_protocol_headers_proto_enumTypes[0].Descriptor() } func (SecurityType) Type() protoreflect.EnumType { return &file_common_protocol_headers_proto_enumTypes[0] } func (x SecurityType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SecurityType.Descriptor instead. func (SecurityType) EnumDescriptor() ([]byte, []int) { return file_common_protocol_headers_proto_rawDescGZIP(), []int{0} } type SecurityConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Type SecurityType `protobuf:"varint,1,opt,name=type,proto3,enum=v2ray.core.common.protocol.SecurityType" json:"type,omitempty"` } func (x *SecurityConfig) Reset() { *x = SecurityConfig{} if protoimpl.UnsafeEnabled { mi := &file_common_protocol_headers_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SecurityConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SecurityConfig) ProtoMessage() {} func (x *SecurityConfig) ProtoReflect() protoreflect.Message { mi := &file_common_protocol_headers_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SecurityConfig.ProtoReflect.Descriptor instead. func (*SecurityConfig) Descriptor() ([]byte, []int) { return file_common_protocol_headers_proto_rawDescGZIP(), []int{0} } func (x *SecurityConfig) GetType() SecurityType { if x != nil { return x.Type } return SecurityType_UNKNOWN } var File_common_protocol_headers_proto protoreflect.FileDescriptor var file_common_protocol_headers_proto_rawDesc = []byte{ 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x4e, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x62, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x45, 0x53, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x05, 0x42, 0x5f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_protocol_headers_proto_rawDescOnce sync.Once file_common_protocol_headers_proto_rawDescData = file_common_protocol_headers_proto_rawDesc ) func file_common_protocol_headers_proto_rawDescGZIP() []byte { file_common_protocol_headers_proto_rawDescOnce.Do(func() { file_common_protocol_headers_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_protocol_headers_proto_rawDescData) }) return file_common_protocol_headers_proto_rawDescData } var file_common_protocol_headers_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_common_protocol_headers_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_protocol_headers_proto_goTypes = []interface{}{ (SecurityType)(0), // 0: v2ray.core.common.protocol.SecurityType (*SecurityConfig)(nil), // 1: v2ray.core.common.protocol.SecurityConfig } var file_common_protocol_headers_proto_depIdxs = []int32{ 0, // 0: v2ray.core.common.protocol.SecurityConfig.type:type_name -> v2ray.core.common.protocol.SecurityType 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_common_protocol_headers_proto_init() } func file_common_protocol_headers_proto_init() { if File_common_protocol_headers_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_protocol_headers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SecurityConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_protocol_headers_proto_rawDesc, NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_protocol_headers_proto_goTypes, DependencyIndexes: file_common_protocol_headers_proto_depIdxs, EnumInfos: file_common_protocol_headers_proto_enumTypes, MessageInfos: file_common_protocol_headers_proto_msgTypes, }.Build() File_common_protocol_headers_proto = out.File file_common_protocol_headers_proto_rawDesc = nil file_common_protocol_headers_proto_goTypes = nil file_common_protocol_headers_proto_depIdxs = nil } ================================================ FILE: common/protocol/headers.proto ================================================ syntax = "proto3"; package v2ray.core.common.protocol; option csharp_namespace = "V2Ray.Core.Common.Protocol"; option go_package = "v2ray.com/core/common/protocol"; option java_package = "com.v2ray.core.common.protocol"; option java_multiple_files = true; enum SecurityType { UNKNOWN = 0; LEGACY = 1; AUTO = 2; AES128_GCM = 3; CHACHA20_POLY1305 = 4; NONE = 5; } message SecurityConfig { SecurityType type = 1; } ================================================ FILE: common/protocol/http/headers.go ================================================ package http import ( "net/http" "strconv" "strings" "v2ray.com/core/common/net" ) // ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it. func ParseXForwardedFor(header http.Header) []net.Address { xff := header.Get("X-Forwarded-For") if xff == "" { return nil } list := strings.Split(xff, ",") addrs := make([]net.Address, 0, len(list)) for _, proxy := range list { addrs = append(addrs, net.ParseAddress(proxy)) } return addrs } // RemoveHopByHopHeaders remove hop by hop headers in http header list. func RemoveHopByHopHeaders(header http.Header) { // Strip hop-by-hop header based on RFC: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do header.Del("Proxy-Connection") header.Del("Proxy-Authenticate") header.Del("Proxy-Authorization") header.Del("TE") header.Del("Trailers") header.Del("Transfer-Encoding") header.Del("Upgrade") connections := header.Get("Connection") header.Del("Connection") if connections == "" { return } for _, h := range strings.Split(connections, ",") { header.Del(strings.TrimSpace(h)) } } // ParseHost splits host and port from a raw string. Default port is used when raw string doesn't contain port. func ParseHost(rawHost string, defaultPort net.Port) (net.Destination, error) { port := defaultPort host, rawPort, err := net.SplitHostPort(rawHost) if err != nil { if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") { host = rawHost } else { return net.Destination{}, err } } else if len(rawPort) > 0 { intPort, err := strconv.Atoi(rawPort) if err != nil { return net.Destination{}, err } port = net.Port(intPort) } return net.TCPDestination(net.ParseAddress(host), port), nil } ================================================ FILE: common/protocol/http/headers_test.go ================================================ package http_test import ( "bufio" "net/http" "strings" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/net" . "v2ray.com/core/common/protocol/http" ) func TestParseXForwardedFor(t *testing.T) { header := http.Header{} header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103") addrs := ParseXForwardedFor(header) if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" { t.Error(r) } } func TestHopByHopHeadersRemoving(t *testing.T) { rawRequest := `GET /pkg/net/http/ HTTP/1.1 Host: golang.org Connection: keep-alive,Foo, Bar Foo: foo Bar: bar Proxy-Connection: keep-alive Proxy-Authenticate: abc Accept-Encoding: gzip Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7 Cache-Control: no-cache Accept-Language: de,en;q=0.7,en-us;q=0.3 ` b := bufio.NewReader(strings.NewReader(rawRequest)) req, err := http.ReadRequest(b) common.Must(err) headers := []struct { Key string Value string }{ { Key: "Foo", Value: "foo", }, { Key: "Bar", Value: "bar", }, { Key: "Connection", Value: "keep-alive,Foo, Bar", }, { Key: "Proxy-Connection", Value: "keep-alive", }, { Key: "Proxy-Authenticate", Value: "abc", }, } for _, header := range headers { if v := req.Header.Get(header.Key); v != header.Value { t.Error("header ", header.Key, " = ", v, " want ", header.Value) } } RemoveHopByHopHeaders(req.Header) for _, header := range []string{"Connection", "Foo", "Bar", "Proxy-Connection", "Proxy-Authenticate"} { if v := req.Header.Get(header); v != "" { t.Error("header ", header, " = ", v) } } } func TestParseHost(t *testing.T) { testCases := []struct { RawHost string DefaultPort net.Port Destination net.Destination Error bool }{ { RawHost: "v2ray.com:80", DefaultPort: 443, Destination: net.TCPDestination(net.DomainAddress("v2ray.com"), 80), }, { RawHost: "tls.v2ray.com", DefaultPort: 443, Destination: net.TCPDestination(net.DomainAddress("tls.v2ray.com"), 443), }, { RawHost: "[2401:1bc0:51f0:ec08::1]:80", DefaultPort: 443, Destination: net.TCPDestination(net.ParseAddress("[2401:1bc0:51f0:ec08::1]"), 80), }, } for _, testCase := range testCases { dest, err := ParseHost(testCase.RawHost, testCase.DefaultPort) if testCase.Error { if err == nil { t.Error("for test case: ", testCase.RawHost, " expected error, but actually nil") } } else { if dest != testCase.Destination { t.Error("for test case: ", testCase.RawHost, " expected host: ", testCase.Destination.String(), " but got ", dest.String()) } } } } ================================================ FILE: common/protocol/http/sniff.go ================================================ package http import ( "bytes" "errors" "strings" "v2ray.com/core/common" "v2ray.com/core/common/net" ) type version byte const ( HTTP1 version = iota HTTP2 ) type SniffHeader struct { version version host string } func (h *SniffHeader) Protocol() string { switch h.version { case HTTP1: return "http1" case HTTP2: return "http2" default: return "unknown" } } func (h *SniffHeader) Domain() string { return h.host } var ( methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"} errNotHTTPMethod = errors.New("not an HTTP method") ) func beginWithHTTPMethod(b []byte) error { for _, m := range &methods { if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) { return nil } if len(b) < len(m) { return common.ErrNoClue } } return errNotHTTPMethod } func SniffHTTP(b []byte) (*SniffHeader, error) { if err := beginWithHTTPMethod(b); err != nil { return nil, err } sh := &SniffHeader{ version: HTTP1, } headers := bytes.Split(b, []byte{'\n'}) for i := 1; i < len(headers); i++ { header := headers[i] if len(header) == 0 { break } parts := bytes.SplitN(header, []byte{':'}, 2) if len(parts) != 2 { continue } key := strings.ToLower(string(parts[0])) if key == "host" { rawHost := strings.ToLower(string(bytes.TrimSpace(parts[1]))) dest, err := ParseHost(rawHost, net.Port(80)) if err != nil { return nil, err } sh.host = dest.Address.String() } } if len(sh.host) > 0 { return sh, nil } return nil, common.ErrNoClue } ================================================ FILE: common/protocol/http/sniff_test.go ================================================ package http_test import ( "testing" . "v2ray.com/core/common/protocol/http" ) func TestHTTPHeaders(t *testing.T) { cases := []struct { input string domain string err bool }{ { input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 Host: net.tutsplus.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120 Pragma: no-cache Cache-Control: no-cache`, domain: "net.tutsplus.com", }, { input: `POST /foo.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/test.php Content-Type: application/x-www-form-urlencoded Content-Length: 43 first_name=John&last_name=Doe&action=Submit`, domain: "localhost", }, { input: `X /foo.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/test.php Content-Type: application/x-www-form-urlencoded Content-Length: 43 first_name=John&last_name=Doe&action=Submit`, domain: "", err: true, }, { input: `GET /foo.php HTTP/1.1 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/test.php Content-Type: application/x-www-form-urlencoded Content-Length: 43 Host: localhost first_name=John&last_name=Doe&action=Submit`, domain: "", err: true, }, { input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`, domain: "", err: true, }, } for _, test := range cases { header, err := SniffHTTP([]byte(test.input)) if test.err { if err == nil { t.Errorf("Expect error but nil, in test: %v", test) } } else { if err != nil { t.Errorf("Expect no error but actually %s in test %v", err.Error(), test) } if header.Domain() != test.domain { t.Error("expected domain ", test.domain, " but got ", header.Domain()) } } } } ================================================ FILE: common/protocol/id.go ================================================ package protocol import ( "crypto/hmac" "crypto/md5" "hash" "v2ray.com/core/common" "v2ray.com/core/common/uuid" ) const ( IDBytesLen = 16 ) type IDHash func(key []byte) hash.Hash func DefaultIDHash(key []byte) hash.Hash { return hmac.New(md5.New, key) } // The ID of en entity, in the form of a UUID. type ID struct { uuid uuid.UUID cmdKey [IDBytesLen]byte } // Equals returns true if this ID equals to the other one. func (id *ID) Equals(another *ID) bool { return id.uuid.Equals(&(another.uuid)) } func (id *ID) Bytes() []byte { return id.uuid.Bytes() } func (id *ID) String() string { return id.uuid.String() } func (id *ID) UUID() uuid.UUID { return id.uuid } func (id ID) CmdKey() []byte { return id.cmdKey[:] } // NewID returns an ID with given UUID. func NewID(uuid uuid.UUID) *ID { id := &ID{uuid: uuid} md5hash := md5.New() common.Must2(md5hash.Write(uuid.Bytes())) common.Must2(md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))) md5hash.Sum(id.cmdKey[:0]) return id } func nextID(u *uuid.UUID) uuid.UUID { md5hash := md5.New() common.Must2(md5hash.Write(u.Bytes())) common.Must2(md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))) var newid uuid.UUID for { md5hash.Sum(newid[:0]) if !newid.Equals(u) { return newid } common.Must2(md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))) } } func NewAlterIDs(primary *ID, alterIDCount uint16) []*ID { alterIDs := make([]*ID, alterIDCount) prevID := primary.UUID() for idx := range alterIDs { newid := nextID(&prevID) alterIDs[idx] = NewID(newid) prevID = newid } return alterIDs } ================================================ FILE: common/protocol/id_test.go ================================================ package protocol_test import ( "testing" . "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" ) func TestIdEquals(t *testing.T) { id1 := NewID(uuid.New()) id2 := NewID(id1.UUID()) if !id1.Equals(id2) { t.Error("expected id1 to equal id2, but actually not") } if id1.String() != id2.String() { t.Error(id1.String(), " != ", id2.String()) } } ================================================ FILE: common/protocol/payload.go ================================================ package protocol type TransferType byte const ( TransferTypeStream TransferType = 0 TransferTypePacket TransferType = 1 ) type AddressType byte const ( AddressTypeIPv4 AddressType = 1 AddressTypeDomain AddressType = 2 AddressTypeIPv6 AddressType = 3 ) ================================================ FILE: common/protocol/protocol.go ================================================ package protocol // import "v2ray.com/core/common/protocol" //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: common/protocol/server_picker.go ================================================ package protocol import ( "sync" ) type ServerList struct { sync.RWMutex servers []*ServerSpec } func NewServerList() *ServerList { return &ServerList{} } func (sl *ServerList) AddServer(server *ServerSpec) { sl.Lock() defer sl.Unlock() sl.servers = append(sl.servers, server) } func (sl *ServerList) Size() uint32 { sl.RLock() defer sl.RUnlock() return uint32(len(sl.servers)) } func (sl *ServerList) GetServer(idx uint32) *ServerSpec { sl.Lock() defer sl.Unlock() for { if idx >= uint32(len(sl.servers)) { return nil } server := sl.servers[idx] if !server.IsValid() { sl.removeServer(idx) continue } return server } } func (sl *ServerList) removeServer(idx uint32) { n := len(sl.servers) sl.servers[idx] = sl.servers[n-1] sl.servers = sl.servers[:n-1] } type ServerPicker interface { PickServer() *ServerSpec } type RoundRobinServerPicker struct { sync.Mutex serverlist *ServerList nextIndex uint32 } func NewRoundRobinServerPicker(serverlist *ServerList) *RoundRobinServerPicker { return &RoundRobinServerPicker{ serverlist: serverlist, nextIndex: 0, } } func (p *RoundRobinServerPicker) PickServer() *ServerSpec { p.Lock() defer p.Unlock() next := p.nextIndex server := p.serverlist.GetServer(next) if server == nil { next = 0 server = p.serverlist.GetServer(0) } next++ if next >= p.serverlist.Size() { next = 0 } p.nextIndex = next return server } ================================================ FILE: common/protocol/server_picker_test.go ================================================ package protocol_test import ( "testing" "time" "v2ray.com/core/common/net" . "v2ray.com/core/common/protocol" ) func TestServerList(t *testing.T) { list := NewServerList() list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid())) if list.Size() != 1 { t.Error("list size: ", list.Size()) } list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second)))) if list.Size() != 2 { t.Error("list.size: ", list.Size()) } server := list.GetServer(1) if server.Destination().Port != 2 { t.Error("server: ", server.Destination()) } time.Sleep(2 * time.Second) server = list.GetServer(1) if server != nil { t.Error("server: ", server) } server = list.GetServer(0) if server.Destination().Port != 1 { t.Error("server: ", server.Destination()) } } func TestServerPicker(t *testing.T) { list := NewServerList() list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid())) list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second)))) list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(3)), BeforeTime(time.Now().Add(time.Second)))) picker := NewRoundRobinServerPicker(list) server := picker.PickServer() if server.Destination().Port != 1 { t.Error("server: ", server.Destination()) } server = picker.PickServer() if server.Destination().Port != 2 { t.Error("server: ", server.Destination()) } server = picker.PickServer() if server.Destination().Port != 3 { t.Error("server: ", server.Destination()) } server = picker.PickServer() if server.Destination().Port != 1 { t.Error("server: ", server.Destination()) } time.Sleep(2 * time.Second) server = picker.PickServer() if server.Destination().Port != 1 { t.Error("server: ", server.Destination()) } server = picker.PickServer() if server.Destination().Port != 1 { t.Error("server: ", server.Destination()) } } ================================================ FILE: common/protocol/server_spec.go ================================================ package protocol import ( "sync" "time" "v2ray.com/core/common/dice" "v2ray.com/core/common/net" ) type ValidationStrategy interface { IsValid() bool Invalidate() } type alwaysValidStrategy struct{} func AlwaysValid() ValidationStrategy { return alwaysValidStrategy{} } func (alwaysValidStrategy) IsValid() bool { return true } func (alwaysValidStrategy) Invalidate() {} type timeoutValidStrategy struct { until time.Time } func BeforeTime(t time.Time) ValidationStrategy { return &timeoutValidStrategy{ until: t, } } func (s *timeoutValidStrategy) IsValid() bool { return s.until.After(time.Now()) } func (s *timeoutValidStrategy) Invalidate() { s.until = time.Time{} } type ServerSpec struct { sync.RWMutex dest net.Destination users []*MemoryUser valid ValidationStrategy } func NewServerSpec(dest net.Destination, valid ValidationStrategy, users ...*MemoryUser) *ServerSpec { return &ServerSpec{ dest: dest, users: users, valid: valid, } } func NewServerSpecFromPB(spec *ServerEndpoint) (*ServerSpec, error) { dest := net.TCPDestination(spec.Address.AsAddress(), net.Port(spec.Port)) mUsers := make([]*MemoryUser, len(spec.User)) for idx, u := range spec.User { mUser, err := u.ToMemoryUser() if err != nil { return nil, err } mUsers[idx] = mUser } return NewServerSpec(dest, AlwaysValid(), mUsers...), nil } func (s *ServerSpec) Destination() net.Destination { return s.dest } func (s *ServerSpec) HasUser(user *MemoryUser) bool { s.RLock() defer s.RUnlock() for _, u := range s.users { if u.Account.Equals(user.Account) { return true } } return false } func (s *ServerSpec) AddUser(user *MemoryUser) { if s.HasUser(user) { return } s.Lock() defer s.Unlock() s.users = append(s.users, user) } func (s *ServerSpec) PickUser() *MemoryUser { s.RLock() defer s.RUnlock() userCount := len(s.users) switch userCount { case 0: return nil case 1: return s.users[0] default: return s.users[dice.Roll(userCount)] } } func (s *ServerSpec) IsValid() bool { return s.valid.IsValid() } func (s *ServerSpec) Invalidate() { s.valid.Invalidate() } ================================================ FILE: common/protocol/server_spec.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/protocol/server_spec.proto package protocol import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type ServerEndpoint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` User []*User `protobuf:"bytes,3,rep,name=user,proto3" json:"user,omitempty"` } func (x *ServerEndpoint) Reset() { *x = ServerEndpoint{} if protoimpl.UnsafeEnabled { mi := &file_common_protocol_server_spec_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerEndpoint) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerEndpoint) ProtoMessage() {} func (x *ServerEndpoint) ProtoReflect() protoreflect.Message { mi := &file_common_protocol_server_spec_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerEndpoint.ProtoReflect.Descriptor instead. func (*ServerEndpoint) Descriptor() ([]byte, []int) { return file_common_protocol_server_spec_proto_rawDescGZIP(), []int{0} } func (x *ServerEndpoint) GetAddress() *net.IPOrDomain { if x != nil { return x.Address } return nil } func (x *ServerEndpoint) GetPort() uint32 { if x != nil { return x.Port } return 0 } func (x *ServerEndpoint) GetUser() []*User { if x != nil { return x.User } return nil } var File_common_protocol_server_spec_proto protoreflect.FileDescriptor var file_common_protocol_server_spec_proto_rawDesc = []byte{ 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x42, 0x5f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_protocol_server_spec_proto_rawDescOnce sync.Once file_common_protocol_server_spec_proto_rawDescData = file_common_protocol_server_spec_proto_rawDesc ) func file_common_protocol_server_spec_proto_rawDescGZIP() []byte { file_common_protocol_server_spec_proto_rawDescOnce.Do(func() { file_common_protocol_server_spec_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_protocol_server_spec_proto_rawDescData) }) return file_common_protocol_server_spec_proto_rawDescData } var file_common_protocol_server_spec_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_protocol_server_spec_proto_goTypes = []interface{}{ (*ServerEndpoint)(nil), // 0: v2ray.core.common.protocol.ServerEndpoint (*net.IPOrDomain)(nil), // 1: v2ray.core.common.net.IPOrDomain (*User)(nil), // 2: v2ray.core.common.protocol.User } var file_common_protocol_server_spec_proto_depIdxs = []int32{ 1, // 0: v2ray.core.common.protocol.ServerEndpoint.address:type_name -> v2ray.core.common.net.IPOrDomain 2, // 1: v2ray.core.common.protocol.ServerEndpoint.user:type_name -> v2ray.core.common.protocol.User 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_common_protocol_server_spec_proto_init() } func file_common_protocol_server_spec_proto_init() { if File_common_protocol_server_spec_proto != nil { return } file_common_protocol_user_proto_init() if !protoimpl.UnsafeEnabled { file_common_protocol_server_spec_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerEndpoint); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_protocol_server_spec_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_protocol_server_spec_proto_goTypes, DependencyIndexes: file_common_protocol_server_spec_proto_depIdxs, MessageInfos: file_common_protocol_server_spec_proto_msgTypes, }.Build() File_common_protocol_server_spec_proto = out.File file_common_protocol_server_spec_proto_rawDesc = nil file_common_protocol_server_spec_proto_goTypes = nil file_common_protocol_server_spec_proto_depIdxs = nil } ================================================ FILE: common/protocol/server_spec.proto ================================================ syntax = "proto3"; package v2ray.core.common.protocol; option csharp_namespace = "V2Ray.Core.Common.Protocol"; option go_package = "v2ray.com/core/common/protocol"; option java_package = "com.v2ray.core.common.protocol"; option java_multiple_files = true; import "common/net/address.proto"; import "common/protocol/user.proto"; message ServerEndpoint { v2ray.core.common.net.IPOrDomain address = 1; uint32 port = 2; repeated v2ray.core.common.protocol.User user = 3; } ================================================ FILE: common/protocol/server_spec_test.go ================================================ package protocol_test import ( "strings" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/net" . "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/vmess" ) func TestAlwaysValidStrategy(t *testing.T) { strategy := AlwaysValid() if !strategy.IsValid() { t.Error("strategy not valid") } strategy.Invalidate() if !strategy.IsValid() { t.Error("strategy not valid") } } func TestTimeoutValidStrategy(t *testing.T) { strategy := BeforeTime(time.Now().Add(2 * time.Second)) if !strategy.IsValid() { t.Error("strategy not valid") } time.Sleep(3 * time.Second) if strategy.IsValid() { t.Error("strategy is valid") } strategy = BeforeTime(time.Now().Add(2 * time.Second)) strategy.Invalidate() if strategy.IsValid() { t.Error("strategy is valid") } } func TestUserInServerSpec(t *testing.T) { uuid1 := uuid.New() uuid2 := uuid.New() toAccount := func(a *vmess.Account) Account { account, err := a.AsAccount() common.Must(err) return account } spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{ Email: "test1@v2ray.com", Account: toAccount(&vmess.Account{Id: uuid1.String()}), }) if spec.HasUser(&MemoryUser{ Email: "test1@v2ray.com", Account: toAccount(&vmess.Account{Id: uuid2.String()}), }) { t.Error("has user: ", uuid2) } spec.AddUser(&MemoryUser{Email: "test2@v2ray.com"}) if !spec.HasUser(&MemoryUser{ Email: "test1@v2ray.com", Account: toAccount(&vmess.Account{Id: uuid1.String()}), }) { t.Error("not having user: ", uuid1) } } func TestPickUser(t *testing.T) { spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{Email: "test1@v2ray.com"}, &MemoryUser{Email: "test2@v2ray.com"}, &MemoryUser{Email: "test3@v2ray.com"}) user := spec.PickUser() if !strings.HasSuffix(user.Email, "@v2ray.com") { t.Error("user: ", user.Email) } } ================================================ FILE: common/protocol/time.go ================================================ package protocol import ( "time" "v2ray.com/core/common/dice" ) type Timestamp int64 type TimestampGenerator func() Timestamp func NowTime() Timestamp { return Timestamp(time.Now().Unix()) } func NewTimestampGenerator(base Timestamp, delta int) TimestampGenerator { return func() Timestamp { rangeInDelta := dice.Roll(delta*2) - delta return base + Timestamp(rangeInDelta) } } ================================================ FILE: common/protocol/time_test.go ================================================ package protocol_test import ( "testing" "time" . "v2ray.com/core/common/protocol" ) func TestGenerateRandomInt64InRange(t *testing.T) { base := time.Now().Unix() delta := 100 generator := NewTimestampGenerator(Timestamp(base), delta) for i := 0; i < 100; i++ { val := int64(generator()) if val > base+int64(delta) || val < base-int64(delta) { t.Error(val, " not between ", base-int64(delta), " and ", base+int64(delta)) } } } ================================================ FILE: common/protocol/tls/cert/.gitignore ================================================ *.pem ================================================ FILE: common/protocol/tls/cert/cert.go ================================================ package cert import ( "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/asn1" "encoding/pem" "math/big" "time" "v2ray.com/core/common" ) //go:generate go run v2ray.com/core/common/errors/errorgen type Certificate struct { // Cerificate in ASN.1 DER format Certificate []byte // Private key in ASN.1 DER format PrivateKey []byte } func ParseCertificate(certPEM []byte, keyPEM []byte) (*Certificate, error) { certBlock, _ := pem.Decode(certPEM) if certBlock == nil { return nil, newError("failed to decode certificate") } keyBlock, _ := pem.Decode(keyPEM) if keyBlock == nil { return nil, newError("failed to decode key") } return &Certificate{ Certificate: certBlock.Bytes, PrivateKey: keyBlock.Bytes, }, nil } func (c *Certificate) ToPEM() ([]byte, []byte) { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Certificate}), pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: c.PrivateKey}) } type Option func(*x509.Certificate) func Authority(isCA bool) Option { return func(cert *x509.Certificate) { cert.IsCA = isCA } } func NotBefore(t time.Time) Option { return func(c *x509.Certificate) { c.NotBefore = t } } func NotAfter(t time.Time) Option { return func(c *x509.Certificate) { c.NotAfter = t } } func DNSNames(names ...string) Option { return func(c *x509.Certificate) { c.DNSNames = names } } func CommonName(name string) Option { return func(c *x509.Certificate) { c.Subject.CommonName = name } } func KeyUsage(usage x509.KeyUsage) Option { return func(c *x509.Certificate) { c.KeyUsage = usage } } func Organization(org string) Option { return func(c *x509.Certificate) { c.Subject.Organization = []string{org} } } func MustGenerate(parent *Certificate, opts ...Option) *Certificate { cert, err := Generate(parent, opts...) common.Must(err) return cert } func publicKey(priv interface{}) interface{} { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey case *ecdsa.PrivateKey: return &k.PublicKey case ed25519.PrivateKey: return k.Public().(ed25519.PublicKey) default: return nil } } func Generate(parent *Certificate, opts ...Option) (*Certificate, error) { var ( pKey interface{} parentKey interface{} err error ) // higher signing performance than RSA2048 selfKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, newError("failed to generate self private key").Base(err) } parentKey = selfKey if parent != nil { if _, e := asn1.Unmarshal(parent.PrivateKey, &ecPrivateKey{}); e == nil { pKey, err = x509.ParseECPrivateKey(parent.PrivateKey) } else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs8{}); e == nil { pKey, err = x509.ParsePKCS8PrivateKey(parent.PrivateKey) } else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs1PrivateKey{}); e == nil { pKey, err = x509.ParsePKCS1PrivateKey(parent.PrivateKey) } if err != nil { return nil, newError("failed to parse parent private key").Base(err) } parentKey = pKey } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return nil, newError("failed to generate serial number").Base(err) } template := &x509.Certificate{ SerialNumber: serialNumber, NotBefore: time.Now().Add(time.Hour * -1), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } for _, opt := range opts { opt(template) } parentCert := template if parent != nil { pCert, err := x509.ParseCertificate(parent.Certificate) if err != nil { return nil, newError("failed to parse parent certificate").Base(err) } parentCert = pCert } derBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, publicKey(selfKey), parentKey) if err != nil { return nil, newError("failed to create certificate").Base(err) } privateKey, err := x509.MarshalPKCS8PrivateKey(selfKey) if err != nil { return nil, newError("Unable to marshal private key").Base(err) } return &Certificate{ Certificate: derBytes, PrivateKey: privateKey, }, nil } ================================================ FILE: common/protocol/tls/cert/cert_test.go ================================================ package cert import ( "context" "crypto/x509" "encoding/json" "os" "strings" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/task" ) func TestGenerate(t *testing.T) { err := generate(nil, true, true, "ca") if err != nil { t.Fatal(err) } } func generate(domainNames []string, isCA bool, jsonOutput bool, fileOutput string) error { commonName := "V2Ray Root CA" organization := "V2Ray Inc" expire := time.Hour * 3 var opts []Option if isCA { opts = append(opts, Authority(isCA)) opts = append(opts, KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature)) } opts = append(opts, NotAfter(time.Now().Add(expire))) opts = append(opts, CommonName(commonName)) if len(domainNames) > 0 { opts = append(opts, DNSNames(domainNames...)) } opts = append(opts, Organization(organization)) cert, err := Generate(nil, opts...) if err != nil { return newError("failed to generate TLS certificate").Base(err) } if jsonOutput { printJSON(cert) } if len(fileOutput) > 0 { if err := printFile(cert, fileOutput); err != nil { return err } } return nil } type jsonCert struct { Certificate []string `json:"certificate"` Key []string `json:"key"` } func printJSON(certificate *Certificate) { certPEM, keyPEM := certificate.ToPEM() jCert := &jsonCert{ Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"), Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"), } content, err := json.MarshalIndent(jCert, "", " ") common.Must(err) os.Stdout.Write(content) os.Stdout.WriteString("\n") } func printFile(certificate *Certificate, name string) error { certPEM, keyPEM := certificate.ToPEM() return task.Run(context.Background(), func() error { return writeFile(certPEM, name+"_cert.pem") }, func() error { return writeFile(keyPEM, name+"_key.pem") }) } func writeFile(content []byte, name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() return common.Error2(f.Write(content)) } ================================================ FILE: common/protocol/tls/cert/errors.generated.go ================================================ package cert import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/protocol/tls/cert/privateKey.go ================================================ package cert import ( "crypto/x509/pkix" "encoding/asn1" "math/big" ) type ecPrivateKey struct { Version int PrivateKey []byte NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` } type pkcs8 struct { Version int Algo pkix.AlgorithmIdentifier PrivateKey []byte // optional attributes omitted. } type pkcs1AdditionalRSAPrime struct { Prime *big.Int // We ignore these values because rsa will calculate them. Exp *big.Int Coeff *big.Int } type pkcs1PrivateKey struct { Version int N *big.Int E int D *big.Int P *big.Int Q *big.Int // We ignore these values, if present, because rsa will calculate them. Dp *big.Int `asn1:"optional"` Dq *big.Int `asn1:"optional"` Qinv *big.Int `asn1:"optional"` AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"` } ================================================ FILE: common/protocol/tls/sniff.go ================================================ package tls import ( "encoding/binary" "errors" "strings" "v2ray.com/core/common" ) type SniffHeader struct { domain string } func (h *SniffHeader) Protocol() string { return "tls" } func (h *SniffHeader) Domain() string { return h.domain } var errNotTLS = errors.New("not TLS header") var errNotClientHello = errors.New("not client hello") func IsValidTLSVersion(major, minor byte) bool { return major == 3 } // ReadClientHello returns server name (if any) from TLS client hello message. // https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300 func ReadClientHello(data []byte, h *SniffHeader) error { if len(data) < 42 { return common.ErrNoClue } sessionIDLen := int(data[38]) if sessionIDLen > 32 || len(data) < 39+sessionIDLen { return common.ErrNoClue } data = data[39+sessionIDLen:] if len(data) < 2 { return common.ErrNoClue } // cipherSuiteLen is the number of bytes of cipher suite numbers. Since // they are uint16s, the number must be even. cipherSuiteLen := int(data[0])<<8 | int(data[1]) if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen { return errNotClientHello } data = data[2+cipherSuiteLen:] if len(data) < 1 { return common.ErrNoClue } compressionMethodsLen := int(data[0]) if len(data) < 1+compressionMethodsLen { return common.ErrNoClue } data = data[1+compressionMethodsLen:] if len(data) == 0 { return errNotClientHello } if len(data) < 2 { return errNotClientHello } extensionsLength := int(data[0])<<8 | int(data[1]) data = data[2:] if extensionsLength != len(data) { return errNotClientHello } for len(data) != 0 { if len(data) < 4 { return errNotClientHello } extension := uint16(data[0])<<8 | uint16(data[1]) length := int(data[2])<<8 | int(data[3]) data = data[4:] if len(data) < length { return errNotClientHello } if extension == 0x00 { /* extensionServerName */ d := data[:length] if len(d) < 2 { return errNotClientHello } namesLen := int(d[0])<<8 | int(d[1]) d = d[2:] if len(d) != namesLen { return errNotClientHello } for len(d) > 0 { if len(d) < 3 { return errNotClientHello } nameType := d[0] nameLen := int(d[1])<<8 | int(d[2]) d = d[3:] if len(d) < nameLen { return errNotClientHello } if nameType == 0 { serverName := string(d[:nameLen]) // An SNI value may not include a // trailing dot. See // https://tools.ietf.org/html/rfc6066#section-3. if strings.HasSuffix(serverName, ".") { return errNotClientHello } h.domain = serverName return nil } d = d[nameLen:] } } data = data[length:] } return errNotTLS } func SniffTLS(b []byte) (*SniffHeader, error) { if len(b) < 5 { return nil, common.ErrNoClue } if b[0] != 0x16 /* TLS Handshake */ { return nil, errNotTLS } if !IsValidTLSVersion(b[1], b[2]) { return nil, errNotTLS } headerLen := int(binary.BigEndian.Uint16(b[3:5])) if 5+headerLen > len(b) { return nil, common.ErrNoClue } h := &SniffHeader{} err := ReadClientHello(b[5:5+headerLen], h) if err == nil { return h, nil } return nil, err } ================================================ FILE: common/protocol/tls/sniff_test.go ================================================ package tls_test import ( "testing" . "v2ray.com/core/common/protocol/tls" ) func TestTLSHeaders(t *testing.T) { cases := []struct { input []byte domain string err bool }{ { input: []byte{ 0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00, 0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe, 0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4, 0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36, 0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43, 0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0xaa, 0xaa, 0x00, 0x01, 0x00, }, domain: "c.s-microsoft.com", err: false, }, { input: []byte{ 0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00, 0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca, 0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5, 0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e, 0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca, 0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00, 0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74, 0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85, 0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea, 0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30, 0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74, 0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a, 0x00, 0x01, 0x00, }, domain: "www07.clicktale.net", err: false, }, { input: []byte{ 0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1, 0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6, 0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d, 0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84, 0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08, 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e, 0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c, 0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01, 0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72, }, domain: "dogfish", err: false, }, { input: []byte{ 0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00, 0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee, 0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14, 0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62, 0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45, 0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f, 0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e, 0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f, 0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d, 0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36, }, domain: "10.42.0.243", err: false, }, } for _, test := range cases { header, err := SniffTLS(test.input) if test.err { if err == nil { t.Errorf("Exepct error but nil in test %v", test) } } else { if err != nil { t.Errorf("Expect no error but actually %s in test %v", err.Error(), test) } if header.Domain() != test.domain { t.Error("expect domain ", test.domain, " but got ", header.Domain()) } } } } ================================================ FILE: common/protocol/udp/packet.go ================================================ package udp import ( "v2ray.com/core/common/buf" "v2ray.com/core/common/net" ) // Packet is a UDP packet together with its source and destination address. type Packet struct { Payload *buf.Buffer Source net.Destination Target net.Destination } ================================================ FILE: common/protocol/udp/udp.go ================================================ package udp ================================================ FILE: common/protocol/user.go ================================================ package protocol func (u *User) GetTypedAccount() (Account, error) { if u.GetAccount() == nil { return nil, newError("Account missing").AtWarning() } rawAccount, err := u.Account.GetInstance() if err != nil { return nil, err } if asAccount, ok := rawAccount.(AsAccount); ok { return asAccount.AsAccount() } if account, ok := rawAccount.(Account); ok { return account, nil } return nil, newError("Unknown account type: ", u.Account.Type) } func (u *User) ToMemoryUser() (*MemoryUser, error) { account, err := u.GetTypedAccount() if err != nil { return nil, err } return &MemoryUser{ Account: account, Email: u.Email, Level: u.Level, }, nil } // MemoryUser is a parsed form of User, to reduce number of parsing of Account proto. type MemoryUser struct { // Account is the parsed account of the protocol. Account Account Email string Level uint32 } ================================================ FILE: common/protocol/user.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/protocol/user.proto package protocol import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // User is a generic user for all procotols. type User struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Level uint32 `protobuf:"varint,1,opt,name=level,proto3" json:"level,omitempty"` Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` // Protocol specific account information. Must be the account proto in one of // the proxies. Account *serial.TypedMessage `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` } func (x *User) Reset() { *x = User{} if protoimpl.UnsafeEnabled { mi := &file_common_protocol_user_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *User) String() string { return protoimpl.X.MessageStringOf(x) } func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { mi := &file_common_protocol_user_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { return file_common_protocol_user_proto_rawDescGZIP(), []int{0} } func (x *User) GetLevel() uint32 { if x != nil { return x.Level } return 0 } func (x *User) GetEmail() string { if x != nil { return x.Email } return "" } func (x *User) GetAccount() *serial.TypedMessage { if x != nil { return x.Account } return nil } var File_common_protocol_user_proto protoreflect.FileDescriptor var file_common_protocol_user_proto_rawDesc = []byte{ 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x40, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x5f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_protocol_user_proto_rawDescOnce sync.Once file_common_protocol_user_proto_rawDescData = file_common_protocol_user_proto_rawDesc ) func file_common_protocol_user_proto_rawDescGZIP() []byte { file_common_protocol_user_proto_rawDescOnce.Do(func() { file_common_protocol_user_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_protocol_user_proto_rawDescData) }) return file_common_protocol_user_proto_rawDescData } var file_common_protocol_user_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_protocol_user_proto_goTypes = []interface{}{ (*User)(nil), // 0: v2ray.core.common.protocol.User (*serial.TypedMessage)(nil), // 1: v2ray.core.common.serial.TypedMessage } var file_common_protocol_user_proto_depIdxs = []int32{ 1, // 0: v2ray.core.common.protocol.User.account:type_name -> v2ray.core.common.serial.TypedMessage 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_common_protocol_user_proto_init() } func file_common_protocol_user_proto_init() { if File_common_protocol_user_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_protocol_user_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*User); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_protocol_user_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_protocol_user_proto_goTypes, DependencyIndexes: file_common_protocol_user_proto_depIdxs, MessageInfos: file_common_protocol_user_proto_msgTypes, }.Build() File_common_protocol_user_proto = out.File file_common_protocol_user_proto_rawDesc = nil file_common_protocol_user_proto_goTypes = nil file_common_protocol_user_proto_depIdxs = nil } ================================================ FILE: common/protocol/user.proto ================================================ syntax = "proto3"; package v2ray.core.common.protocol; option csharp_namespace = "V2Ray.Core.Common.Protocol"; option go_package = "v2ray.com/core/common/protocol"; option java_package = "com.v2ray.core.common.protocol"; option java_multiple_files = true; import "common/serial/typed_message.proto"; // User is a generic user for all procotols. message User { uint32 level = 1; string email = 2; // Protocol specific account information. Must be the account proto in one of // the proxies. v2ray.core.common.serial.TypedMessage account = 3; } ================================================ FILE: common/retry/errors.generated.go ================================================ package retry import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: common/retry/retry.go ================================================ package retry // import "v2ray.com/core/common/retry" //go:generate go run v2ray.com/core/common/errors/errorgen import ( "time" ) var ( ErrRetryFailed = newError("all retry attempts failed") ) // Strategy is a way to retry on a specific function. type Strategy interface { // On performs a retry on a specific function, until it doesn't return any error. On(func() error) error } type retryer struct { totalAttempt int nextDelay func() uint32 } // On implements Strategy.On. func (r *retryer) On(method func() error) error { attempt := 0 accumulatedError := make([]error, 0, r.totalAttempt) for attempt < r.totalAttempt { err := method() if err == nil { return nil } numErrors := len(accumulatedError) if numErrors == 0 || err.Error() != accumulatedError[numErrors-1].Error() { accumulatedError = append(accumulatedError, err) } delay := r.nextDelay() time.Sleep(time.Duration(delay) * time.Millisecond) attempt++ } return newError(accumulatedError).Base(ErrRetryFailed) } // Timed returns a retry strategy with fixed interval. func Timed(attempts int, delay uint32) Strategy { return &retryer{ totalAttempt: attempts, nextDelay: func() uint32 { return delay }, } } func ExponentialBackoff(attempts int, delay uint32) Strategy { nextDelay := uint32(0) return &retryer{ totalAttempt: attempts, nextDelay: func() uint32 { r := nextDelay nextDelay += delay return r }, } } ================================================ FILE: common/retry/retry_test.go ================================================ package retry_test import ( "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/errors" . "v2ray.com/core/common/retry" ) var ( errorTestOnly = errors.New("This is a fake error.") ) func TestNoRetry(t *testing.T) { startTime := time.Now().Unix() err := Timed(10, 100000).On(func() error { return nil }) endTime := time.Now().Unix() common.Must(err) if endTime < startTime { t.Error("endTime < startTime: ", startTime, " -> ", endTime) } } func TestRetryOnce(t *testing.T) { startTime := time.Now() called := 0 err := Timed(10, 1000).On(func() error { if called == 0 { called++ return errorTestOnly } return nil }) duration := time.Since(startTime) common.Must(err) if v := int64(duration / time.Millisecond); v < 900 { t.Error("duration: ", v) } } func TestRetryMultiple(t *testing.T) { startTime := time.Now() called := 0 err := Timed(10, 1000).On(func() error { if called < 5 { called++ return errorTestOnly } return nil }) duration := time.Since(startTime) common.Must(err) if v := int64(duration / time.Millisecond); v < 4900 { t.Error("duration: ", v) } } func TestRetryExhausted(t *testing.T) { startTime := time.Now() called := 0 err := Timed(2, 1000).On(func() error { called++ return errorTestOnly }) duration := time.Since(startTime) if errors.Cause(err) != ErrRetryFailed { t.Error("cause: ", err) } if v := int64(duration / time.Millisecond); v < 1900 { t.Error("duration: ", v) } } func TestExponentialBackoff(t *testing.T) { startTime := time.Now() called := 0 err := ExponentialBackoff(10, 100).On(func() error { called++ return errorTestOnly }) duration := time.Since(startTime) if errors.Cause(err) != ErrRetryFailed { t.Error("cause: ", err) } if v := int64(duration / time.Millisecond); v < 4000 { t.Error("duration: ", v) } } ================================================ FILE: common/serial/serial.go ================================================ package serial import ( "encoding/binary" "io" ) // ReadUint16 reads first two bytes from the reader, and then coverts them to an uint16 value. func ReadUint16(reader io.Reader) (uint16, error) { var b [2]byte if _, err := io.ReadFull(reader, b[:]); err != nil { return 0, err } return binary.BigEndian.Uint16(b[:]), nil } // WriteUint16 writes an uint16 value into writer. func WriteUint16(writer io.Writer, value uint16) (int, error) { var b [2]byte binary.BigEndian.PutUint16(b[:], value) return writer.Write(b[:]) } // WriteUint64 writes an uint64 value into writer. func WriteUint64(writer io.Writer, value uint64) (int, error) { var b [8]byte binary.BigEndian.PutUint64(b[:], value) return writer.Write(b[:]) } ================================================ FILE: common/serial/serial_test.go ================================================ package serial_test import ( "bytes" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/serial" ) func TestUint16Serial(t *testing.T) { b := buf.New() defer b.Release() n, err := serial.WriteUint16(b, 10) common.Must(err) if n != 2 { t.Error("expect 2 bytes writtng, but actually ", n) } if diff := cmp.Diff(b.Bytes(), []byte{0, 10}); diff != "" { t.Error(diff) } } func TestUint64Serial(t *testing.T) { b := buf.New() defer b.Release() n, err := serial.WriteUint64(b, 10) common.Must(err) if n != 8 { t.Error("expect 8 bytes writtng, but actually ", n) } if diff := cmp.Diff(b.Bytes(), []byte{0, 0, 0, 0, 0, 0, 0, 10}); diff != "" { t.Error(diff) } } func TestReadUint16(t *testing.T) { testCases := []struct { Input []byte Output uint16 }{ { Input: []byte{0, 1}, Output: 1, }, } for _, testCase := range testCases { v, err := serial.ReadUint16(bytes.NewReader(testCase.Input)) common.Must(err) if v != testCase.Output { t.Error("for input ", testCase.Input, " expect output ", testCase.Output, " but got ", v) } } } func BenchmarkReadUint16(b *testing.B) { reader := buf.New() defer reader.Release() common.Must2(reader.Write([]byte{0, 1})) b.ResetTimer() for i := 0; i < b.N; i++ { _, err := serial.ReadUint16(reader) common.Must(err) reader.Clear() reader.Extend(2) } } func BenchmarkWriteUint64(b *testing.B) { writer := buf.New() defer writer.Release() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := serial.WriteUint64(writer, 8) common.Must(err) writer.Clear() } } ================================================ FILE: common/serial/string.go ================================================ package serial import ( "fmt" "strings" ) // ToString serialize an arbitrary value into string. func ToString(v interface{}) string { if v == nil { return " " } switch value := v.(type) { case string: return value case *string: return *value case fmt.Stringer: return value.String() case error: return value.Error() default: return fmt.Sprintf("%+v", value) } } // Concat concatenates all input into a single string. func Concat(v ...interface{}) string { builder := strings.Builder{} for _, value := range v { builder.WriteString(ToString(value)) } return builder.String() } ================================================ FILE: common/serial/string_test.go ================================================ package serial_test import ( "errors" "testing" "github.com/google/go-cmp/cmp" . "v2ray.com/core/common/serial" ) func TestToString(t *testing.T) { s := "a" data := []struct { Value interface{} String string }{ {Value: s, String: s}, {Value: &s, String: s}, {Value: errors.New("t"), String: "t"}, {Value: []byte{'b', 'c'}, String: "[98 99]"}, } for _, c := range data { if r := cmp.Diff(ToString(c.Value), c.String); r != "" { t.Error(r) } } } func TestConcat(t *testing.T) { testCases := []struct { Input []interface{} Output string }{ { Input: []interface{}{ "a", "b", }, Output: "ab", }, } for _, testCase := range testCases { actual := Concat(testCase.Input...) if actual != testCase.Output { t.Error("Unexpected output: ", actual, " but want: ", testCase.Output) } } } func BenchmarkConcat(b *testing.B) { input := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"} b.ReportAllocs() for i := 0; i < b.N; i++ { _ = Concat(input...) } } ================================================ FILE: common/serial/typed_message.go ================================================ package serial import ( "errors" "reflect" "github.com/golang/protobuf/proto" ) // ToTypedMessage converts a proto Message into TypedMessage. func ToTypedMessage(message proto.Message) *TypedMessage { if message == nil { return nil } settings, _ := proto.Marshal(message) return &TypedMessage{ Type: GetMessageType(message), Value: settings, } } // GetMessageType returns the name of this proto Message. func GetMessageType(message proto.Message) string { return proto.MessageName(message) } // GetInstance creates a new instance of the message with messageType. func GetInstance(messageType string) (interface{}, error) { mType := proto.MessageType(messageType) if mType == nil || mType.Elem() == nil { return nil, errors.New("Serial: Unknown type: " + messageType) } return reflect.New(mType.Elem()).Interface(), nil } // GetInstance converts current TypedMessage into a proto Message. func (v *TypedMessage) GetInstance() (proto.Message, error) { instance, err := GetInstance(v.Type) if err != nil { return nil, err } protoMessage := instance.(proto.Message) if err := proto.Unmarshal(v.Value, protoMessage); err != nil { return nil, err } return protoMessage, nil } ================================================ FILE: common/serial/typed_message.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: common/serial/typed_message.proto package serial import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // TypedMessage is a serialized proto message along with its type name. type TypedMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The name of the message type, retrieved from protobuf API. Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Serialized proto message. Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *TypedMessage) Reset() { *x = TypedMessage{} if protoimpl.UnsafeEnabled { mi := &file_common_serial_typed_message_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TypedMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*TypedMessage) ProtoMessage() {} func (x *TypedMessage) ProtoReflect() protoreflect.Message { mi := &file_common_serial_typed_message_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TypedMessage.ProtoReflect.Descriptor instead. func (*TypedMessage) Descriptor() ([]byte, []int) { return file_common_serial_typed_message_proto_rawDescGZIP(), []int{0} } func (x *TypedMessage) GetType() string { if x != nil { return x.Type } return "" } func (x *TypedMessage) GetValue() []byte { if x != nil { return x.Value } return nil } var File_common_serial_typed_message_proto protoreflect.FileDescriptor var file_common_serial_typed_message_proto_rawDesc = []byte{ 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x22, 0x38, 0x0a, 0x0c, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x59, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x50, 0x01, 0x5a, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_common_serial_typed_message_proto_rawDescOnce sync.Once file_common_serial_typed_message_proto_rawDescData = file_common_serial_typed_message_proto_rawDesc ) func file_common_serial_typed_message_proto_rawDescGZIP() []byte { file_common_serial_typed_message_proto_rawDescOnce.Do(func() { file_common_serial_typed_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_serial_typed_message_proto_rawDescData) }) return file_common_serial_typed_message_proto_rawDescData } var file_common_serial_typed_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_common_serial_typed_message_proto_goTypes = []interface{}{ (*TypedMessage)(nil), // 0: v2ray.core.common.serial.TypedMessage } var file_common_serial_typed_message_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_common_serial_typed_message_proto_init() } func file_common_serial_typed_message_proto_init() { if File_common_serial_typed_message_proto != nil { return } if !protoimpl.UnsafeEnabled { file_common_serial_typed_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TypedMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_common_serial_typed_message_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_common_serial_typed_message_proto_goTypes, DependencyIndexes: file_common_serial_typed_message_proto_depIdxs, MessageInfos: file_common_serial_typed_message_proto_msgTypes, }.Build() File_common_serial_typed_message_proto = out.File file_common_serial_typed_message_proto_rawDesc = nil file_common_serial_typed_message_proto_goTypes = nil file_common_serial_typed_message_proto_depIdxs = nil } ================================================ FILE: common/serial/typed_message.proto ================================================ syntax = "proto3"; package v2ray.core.common.serial; option csharp_namespace = "V2Ray.Core.Common.Serial"; option go_package = "v2ray.com/core/common/serial"; option java_package = "com.v2ray.core.common.serial"; option java_multiple_files = true; // TypedMessage is a serialized proto message along with its type name. message TypedMessage { // The name of the message type, retrieved from protobuf API. string type = 1; // Serialized proto message. bytes value = 2; } ================================================ FILE: common/serial/typed_message_test.go ================================================ package serial_test import ( "testing" . "v2ray.com/core/common/serial" ) func TestGetInstance(t *testing.T) { p, err := GetInstance("") if p != nil { t.Error("expected nil instance, but got ", p) } if err == nil { t.Error("expect non-nil error, but got nil") } } func TestConvertingNilMessage(t *testing.T) { x := ToTypedMessage(nil) if x != nil { t.Error("expect nil, but actually not") } } ================================================ FILE: common/session/context.go ================================================ package session import "context" type sessionKey int const ( idSessionKey sessionKey = iota inboundSessionKey outboundSessionKey contentSessionKey muxPreferedSessionKey sockoptSessionKey ) // ContextWithID returns a new context with the given ID. func ContextWithID(ctx context.Context, id ID) context.Context { return context.WithValue(ctx, idSessionKey, id) } // IDFromContext returns ID in this context, or 0 if not contained. func IDFromContext(ctx context.Context) ID { if id, ok := ctx.Value(idSessionKey).(ID); ok { return id } return 0 } func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { return context.WithValue(ctx, inboundSessionKey, inbound) } func InboundFromContext(ctx context.Context) *Inbound { if inbound, ok := ctx.Value(inboundSessionKey).(*Inbound); ok { return inbound } return nil } func ContextWithOutbound(ctx context.Context, outbound *Outbound) context.Context { return context.WithValue(ctx, outboundSessionKey, outbound) } func OutboundFromContext(ctx context.Context) *Outbound { if outbound, ok := ctx.Value(outboundSessionKey).(*Outbound); ok { return outbound } return nil } func ContextWithContent(ctx context.Context, content *Content) context.Context { return context.WithValue(ctx, contentSessionKey, content) } func ContentFromContext(ctx context.Context) *Content { if content, ok := ctx.Value(contentSessionKey).(*Content); ok { return content } return nil } // ContextWithMuxPrefered returns a new context with the given bool func ContextWithMuxPrefered(ctx context.Context, forced bool) context.Context { return context.WithValue(ctx, muxPreferedSessionKey, forced) } // MuxPreferedFromContext returns value in this context, or false if not contained. func MuxPreferedFromContext(ctx context.Context) bool { if val, ok := ctx.Value(muxPreferedSessionKey).(bool); ok { return val } return false } // ContextWithSockopt returns a new context with Socket configs included func ContextWithSockopt(ctx context.Context, s *Sockopt) context.Context { return context.WithValue(ctx, sockoptSessionKey, s) } // SockoptFromContext returns Socket configs in this context, or nil if not contained. func SockoptFromContext(ctx context.Context) *Sockopt { if sockopt, ok := ctx.Value(sockoptSessionKey).(*Sockopt); ok { return sockopt } return nil } ================================================ FILE: common/session/session.go ================================================ // Package session provides functions for sessions of incoming requests. package session // import "v2ray.com/core/common/session" import ( "context" "math/rand" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) // ID of a session. type ID uint32 // NewID generates a new ID. The generated ID is high likely to be unique, but not cryptographically secure. // The generated ID will never be 0. func NewID() ID { for { id := ID(rand.Uint32()) if id != 0 { return id } } } // ExportIDToError transfers session.ID into an error object, for logging purpose. // This can be used with error.WriteToLog(). func ExportIDToError(ctx context.Context) errors.ExportOption { id := IDFromContext(ctx) return func(h *errors.ExportOptionHolder) { h.SessionID = uint32(id) } } // Inbound is the metadata of an inbound connection. type Inbound struct { // Source address of the inbound connection. Source net.Destination // Getaway address Gateway net.Destination // Tag of the inbound proxy that handles the connection. Tag string // User is the user that authencates for the inbound. May be nil if the protocol allows anounymous traffic. User *protocol.MemoryUser } // Outbound is the metadata of an outbound connection. type Outbound struct { // Target address of the outbound connection. Target net.Destination // Gateway address Gateway net.Address } // SniffingRequest controls the behavior of content sniffing. type SniffingRequest struct { OverrideDestinationForProtocol []string Enabled bool } // Content is the metadata of the connection content. type Content struct { // Protocol of current content. Protocol string SniffingRequest SniffingRequest Attributes map[string]string SkipRoutePick bool } // Sockopt is the settings for socket connection. type Sockopt struct { // Mark of the socket connection. Mark int32 } // SetAttribute attachs additional string attributes to content. func (c *Content) SetAttribute(name string, value string) { if c.Attributes == nil { c.Attributes = make(map[string]string) } c.Attributes[name] = value } // Attribute retrieves additional string attributes from content. func (c *Content) Attribute(name string) string { if c.Attributes == nil { return "" } return c.Attributes[name] } ================================================ FILE: common/signal/done/done.go ================================================ package done import ( "sync" ) // Instance is a utility for notifications of something being done. type Instance struct { access sync.Mutex c chan struct{} closed bool } // New returns a new Done. func New() *Instance { return &Instance{ c: make(chan struct{}), } } // Done returns true if Close() is called. func (d *Instance) Done() bool { select { case <-d.Wait(): return true default: return false } } // Wait returns a channel for waiting for done. func (d *Instance) Wait() <-chan struct{} { return d.c } // Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status. func (d *Instance) Close() error { d.access.Lock() defer d.access.Unlock() if d.closed { return nil } d.closed = true close(d.c) return nil } ================================================ FILE: common/signal/notifier.go ================================================ package signal // Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously. type Notifier struct { c chan struct{} } // NewNotifier creates a new Notifier. func NewNotifier() *Notifier { return &Notifier{ c: make(chan struct{}, 1), } } // Signal signals a change, usually by producer. This method never blocks. func (n *Notifier) Signal() { select { case n.c <- struct{}{}: default: } } // Wait returns a channel for waiting for changes. The returned channel never gets closed. func (n *Notifier) Wait() <-chan struct{} { return n.c } ================================================ FILE: common/signal/notifier_test.go ================================================ package signal_test import ( "testing" . "v2ray.com/core/common/signal" ) func TestNotifierSignal(t *testing.T) { n := NewNotifier() w := n.Wait() n.Signal() select { case <-w: default: t.Fail() } } ================================================ FILE: common/signal/pubsub/pubsub.go ================================================ package pubsub import ( "errors" "sync" "time" "v2ray.com/core/common" "v2ray.com/core/common/signal/done" "v2ray.com/core/common/task" ) type Subscriber struct { buffer chan interface{} done *done.Instance } func (s *Subscriber) push(msg interface{}) { select { case s.buffer <- msg: default: } } func (s *Subscriber) Wait() <-chan interface{} { return s.buffer } func (s *Subscriber) Close() error { return s.done.Close() } func (s *Subscriber) IsClosed() bool { return s.done.Done() } type Service struct { sync.RWMutex subs map[string][]*Subscriber ctask *task.Periodic } func NewService() *Service { s := &Service{ subs: make(map[string][]*Subscriber), } s.ctask = &task.Periodic{ Execute: s.Cleanup, Interval: time.Second * 30, } return s } // Cleanup cleans up internal caches of subscribers. // Visible for testing only. func (s *Service) Cleanup() error { s.Lock() defer s.Unlock() if len(s.subs) == 0 { return errors.New("nothing to do") } for name, subs := range s.subs { newSub := make([]*Subscriber, 0, len(s.subs)) for _, sub := range subs { if !sub.IsClosed() { newSub = append(newSub, sub) } } if len(newSub) == 0 { delete(s.subs, name) } else { s.subs[name] = newSub } } if len(s.subs) == 0 { s.subs = make(map[string][]*Subscriber) } return nil } func (s *Service) Subscribe(name string) *Subscriber { sub := &Subscriber{ buffer: make(chan interface{}, 16), done: done.New(), } s.Lock() subs := append(s.subs[name], sub) s.subs[name] = subs s.Unlock() common.Must(s.ctask.Start()) return sub } func (s *Service) Publish(name string, message interface{}) { s.RLock() defer s.RUnlock() for _, sub := range s.subs[name] { if !sub.IsClosed() { sub.push(message) } } } ================================================ FILE: common/signal/pubsub/pubsub_test.go ================================================ package pubsub_test import ( "testing" . "v2ray.com/core/common/signal/pubsub" ) func TestPubsub(t *testing.T) { service := NewService() sub := service.Subscribe("a") service.Publish("a", 1) select { case v := <-sub.Wait(): if v != 1 { t.Error("expected subscribed value 1, but got ", v) } default: t.Fail() } sub.Close() service.Publish("a", 2) select { case <-sub.Wait(): t.Fail() default: } service.Cleanup() } ================================================ FILE: common/signal/semaphore/semaphore.go ================================================ package semaphore // Instance is an implementation of semaphore. type Instance struct { token chan struct{} } // New create a new Semaphore with n permits. func New(n int) *Instance { s := &Instance{ token: make(chan struct{}, n), } for i := 0; i < n; i++ { s.token <- struct{}{} } return s } // Wait returns a channel for acquiring a permit. func (s *Instance) Wait() <-chan struct{} { return s.token } // Signal releases a permit into the semaphore. func (s *Instance) Signal() { s.token <- struct{}{} } ================================================ FILE: common/signal/timer.go ================================================ package signal import ( "context" "sync" "time" "v2ray.com/core/common" "v2ray.com/core/common/task" ) type ActivityUpdater interface { Update() } type ActivityTimer struct { sync.RWMutex updated chan struct{} checkTask *task.Periodic onTimeout func() } func (t *ActivityTimer) Update() { select { case t.updated <- struct{}{}: default: } } func (t *ActivityTimer) check() error { select { case <-t.updated: default: t.finish() } return nil } func (t *ActivityTimer) finish() { t.Lock() defer t.Unlock() if t.onTimeout != nil { t.onTimeout() t.onTimeout = nil } if t.checkTask != nil { t.checkTask.Close() // nolint: errcheck t.checkTask = nil } } func (t *ActivityTimer) SetTimeout(timeout time.Duration) { if timeout == 0 { t.finish() return } checkTask := &task.Periodic{ Interval: timeout, Execute: t.check, } t.Lock() if t.checkTask != nil { t.checkTask.Close() // nolint: errcheck } t.checkTask = checkTask t.Unlock() t.Update() common.Must(checkTask.Start()) } func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer { timer := &ActivityTimer{ updated: make(chan struct{}, 1), onTimeout: cancel, } timer.SetTimeout(timeout) return timer } ================================================ FILE: common/signal/timer_test.go ================================================ package signal_test import ( "context" "runtime" "testing" "time" . "v2ray.com/core/common/signal" ) func TestActivityTimer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) timer := CancelAfterInactivity(ctx, cancel, time.Second*4) time.Sleep(time.Second * 6) if ctx.Err() == nil { t.Error("expected some error, but got nil") } runtime.KeepAlive(timer) } func TestActivityTimerUpdate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) timer := CancelAfterInactivity(ctx, cancel, time.Second*10) time.Sleep(time.Second * 3) if ctx.Err() != nil { t.Error("expected nil, but got ", ctx.Err().Error()) } timer.SetTimeout(time.Second * 1) time.Sleep(time.Second * 2) if ctx.Err() == nil { t.Error("expcted some error, but got nil") } runtime.KeepAlive(timer) } func TestActivityTimerNonBlocking(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) timer := CancelAfterInactivity(ctx, cancel, 0) time.Sleep(time.Second * 1) select { case <-ctx.Done(): default: t.Error("context not done") } timer.SetTimeout(0) timer.SetTimeout(1) timer.SetTimeout(2) } func TestActivityTimerZeroTimeout(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) timer := CancelAfterInactivity(ctx, cancel, 0) select { case <-ctx.Done(): default: t.Error("context not done") } runtime.KeepAlive(timer) } ================================================ FILE: common/stack/bytes.go ================================================ package stack // TwoBytes is a [2]byte which is always allocated on stack. // //go:notinheap type TwoBytes [2]byte // EightBytes is a [8]byte which is always allocated on stack. // //go:notinheap type EightBytes [8]byte ================================================ FILE: common/strmatcher/benchmark_test.go ================================================ package strmatcher_test import ( "strconv" "testing" "v2ray.com/core/common" . "v2ray.com/core/common/strmatcher" ) func BenchmarkDomainMatcherGroup(b *testing.B) { g := new(DomainMatcherGroup) for i := 1; i <= 1024; i++ { g.Add(strconv.Itoa(i)+".v2ray.com", uint32(i)) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = g.Match("0.v2ray.com") } } func BenchmarkFullMatcherGroup(b *testing.B) { g := new(FullMatcherGroup) for i := 1; i <= 1024; i++ { g.Add(strconv.Itoa(i)+".v2ray.com", uint32(i)) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = g.Match("0.v2ray.com") } } func BenchmarkMarchGroup(b *testing.B) { g := new(MatcherGroup) for i := 1; i <= 1024; i++ { m, err := Domain.New(strconv.Itoa(i) + ".v2ray.com") common.Must(err) g.Add(m) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = g.Match("0.v2ray.com") } } ================================================ FILE: common/strmatcher/domain_matcher.go ================================================ package strmatcher import "strings" func breakDomain(domain string) []string { return strings.Split(domain, ".") } type node struct { values []uint32 sub map[string]*node } // DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers. // Visible for testing only. type DomainMatcherGroup struct { root *node } func (g *DomainMatcherGroup) Add(domain string, value uint32) { if g.root == nil { g.root = new(node) } current := g.root parts := breakDomain(domain) for i := len(parts) - 1; i >= 0; i-- { part := parts[i] if current.sub == nil { current.sub = make(map[string]*node) } next := current.sub[part] if next == nil { next = new(node) current.sub[part] = next } current = next } current.values = append(current.values, value) } func (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) { g.Add(string(m), value) } func (g *DomainMatcherGroup) Match(domain string) []uint32 { if domain == "" { return nil } current := g.root if current == nil { return nil } nextPart := func(idx int) int { for i := idx - 1; i >= 0; i-- { if domain[i] == '.' { return i } } return -1 } matches := [][]uint32{} idx := len(domain) for { if idx == -1 || current.sub == nil { break } nidx := nextPart(idx) part := domain[nidx+1 : idx] next := current.sub[part] if next == nil { break } current = next idx = nidx if len(current.values) > 0 { matches = append(matches, current.values) } } switch len(matches) { case 0: return nil case 1: return matches[0] default: result := []uint32{} for idx := range matches { // Insert reversely, the subdomain that matches further ranks higher result = append(result, matches[len(matches)-1-idx]...) } return result } } ================================================ FILE: common/strmatcher/domain_matcher_test.go ================================================ package strmatcher_test import ( "reflect" "testing" . "v2ray.com/core/common/strmatcher" ) func TestDomainMatcherGroup(t *testing.T) { g := new(DomainMatcherGroup) g.Add("v2ray.com", 1) g.Add("google.com", 2) g.Add("x.a.com", 3) g.Add("a.b.com", 4) g.Add("c.a.b.com", 5) g.Add("x.y.com", 4) g.Add("x.y.com", 6) testCases := []struct { Domain string Result []uint32 }{ { Domain: "x.v2ray.com", Result: []uint32{1}, }, { Domain: "y.com", Result: nil, }, { Domain: "a.b.com", Result: []uint32{4}, }, { // Matches [c.a.b.com, a.b.com] Domain: "c.a.b.com", Result: []uint32{5, 4}, }, { Domain: "c.a..b.com", Result: nil, }, { Domain: ".com", Result: nil, }, { Domain: "com", Result: nil, }, { Domain: "", Result: nil, }, { Domain: "x.y.com", Result: []uint32{4, 6}, }, } for _, testCase := range testCases { r := g.Match(testCase.Domain) if !reflect.DeepEqual(r, testCase.Result) { t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r) } } } func TestEmptyDomainMatcherGroup(t *testing.T) { g := new(DomainMatcherGroup) r := g.Match("v2ray.com") if len(r) != 0 { t.Error("Expect [], but ", r) } } ================================================ FILE: common/strmatcher/full_matcher.go ================================================ package strmatcher type FullMatcherGroup struct { matchers map[string][]uint32 } func (g *FullMatcherGroup) Add(domain string, value uint32) { if g.matchers == nil { g.matchers = make(map[string][]uint32) } g.matchers[domain] = append(g.matchers[domain], value) } func (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) { g.Add(string(m), value) } func (g *FullMatcherGroup) Match(str string) []uint32 { if g.matchers == nil { return nil } return g.matchers[str] } ================================================ FILE: common/strmatcher/full_matcher_test.go ================================================ package strmatcher_test import ( "reflect" "testing" . "v2ray.com/core/common/strmatcher" ) func TestFullMatcherGroup(t *testing.T) { g := new(FullMatcherGroup) g.Add("v2ray.com", 1) g.Add("google.com", 2) g.Add("x.a.com", 3) g.Add("x.y.com", 4) g.Add("x.y.com", 6) testCases := []struct { Domain string Result []uint32 }{ { Domain: "v2ray.com", Result: []uint32{1}, }, { Domain: "y.com", Result: nil, }, { Domain: "x.y.com", Result: []uint32{4, 6}, }, } for _, testCase := range testCases { r := g.Match(testCase.Domain) if !reflect.DeepEqual(r, testCase.Result) { t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r) } } } func TestEmptyFullMatcherGroup(t *testing.T) { g := new(FullMatcherGroup) r := g.Match("v2ray.com") if len(r) != 0 { t.Error("Expect [], but ", r) } } ================================================ FILE: common/strmatcher/matchers.go ================================================ package strmatcher import ( "regexp" "strings" ) type fullMatcher string func (m fullMatcher) Match(s string) bool { return string(m) == s } func (m fullMatcher) String() string { return "full:" + string(m) } type substrMatcher string func (m substrMatcher) Match(s string) bool { return strings.Contains(s, string(m)) } func (m substrMatcher) String() string { return "keyword:" + string(m) } type domainMatcher string func (m domainMatcher) Match(s string) bool { pattern := string(m) if !strings.HasSuffix(s, pattern) { return false } return len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.' } func (m domainMatcher) String() string { return "domain:" + string(m) } type regexMatcher struct { pattern *regexp.Regexp } func (m *regexMatcher) Match(s string) bool { return m.pattern.MatchString(s) } func (m *regexMatcher) String() string { return "regexp:" + m.pattern.String() } ================================================ FILE: common/strmatcher/matchers_test.go ================================================ package strmatcher_test import ( "testing" "v2ray.com/core/common" . "v2ray.com/core/common/strmatcher" ) func TestMatcher(t *testing.T) { cases := []struct { pattern string mType Type input string output bool }{ { pattern: "v2ray.com", mType: Domain, input: "www.v2ray.com", output: true, }, { pattern: "v2ray.com", mType: Domain, input: "v2ray.com", output: true, }, { pattern: "v2ray.com", mType: Domain, input: "www.v3ray.com", output: false, }, { pattern: "v2ray.com", mType: Domain, input: "2ray.com", output: false, }, { pattern: "v2ray.com", mType: Domain, input: "xv2ray.com", output: false, }, { pattern: "v2ray.com", mType: Full, input: "v2ray.com", output: true, }, { pattern: "v2ray.com", mType: Full, input: "xv2ray.com", output: false, }, { pattern: "v2ray.com", mType: Regex, input: "v2rayxcom", output: true, }, } for _, test := range cases { matcher, err := test.mType.New(test.pattern) common.Must(err) if m := matcher.Match(test.input); m != test.output { t.Error("unexpected output: ", m, " for test case ", test) } } } ================================================ FILE: common/strmatcher/strmatcher.go ================================================ package strmatcher import ( "regexp" ) // Matcher is the interface to determine a string matches a pattern. type Matcher interface { // Match returns true if the given string matches a predefined pattern. Match(string) bool String() string } // Type is the type of the matcher. type Type byte const ( // Full is the type of matcher that the input string must exactly equal to the pattern. Full Type = iota // Substr is the type of matcher that the input string must contain the pattern as a sub-string. Substr // Domain is the type of matcher that the input string must be a sub-domain or itself of the pattern. Domain // Regex is the type of matcher that the input string must matches the regular-expression pattern. Regex ) // New creates a new Matcher based on the given pattern. func (t Type) New(pattern string) (Matcher, error) { switch t { case Full: return fullMatcher(pattern), nil case Substr: return substrMatcher(pattern), nil case Domain: return domainMatcher(pattern), nil case Regex: r, err := regexp.Compile(pattern) if err != nil { return nil, err } return ®exMatcher{ pattern: r, }, nil default: panic("Unknown type") } } // IndexMatcher is the interface for matching with a group of matchers. type IndexMatcher interface { // Match returns the index of a matcher that matches the input. It returns empty array if no such matcher exists. Match(input string) []uint32 } type matcherEntry struct { m Matcher id uint32 } // MatcherGroup is an implementation of IndexMatcher. // Empty initialization works. type MatcherGroup struct { count uint32 fullMatcher FullMatcherGroup domainMatcher DomainMatcherGroup otherMatchers []matcherEntry } // Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0. func (g *MatcherGroup) Add(m Matcher) uint32 { g.count++ c := g.count switch tm := m.(type) { case fullMatcher: g.fullMatcher.addMatcher(tm, c) case domainMatcher: g.domainMatcher.addMatcher(tm, c) default: g.otherMatchers = append(g.otherMatchers, matcherEntry{ m: m, id: c, }) } return c } // Match implements IndexMatcher.Match. func (g *MatcherGroup) Match(pattern string) []uint32 { result := []uint32{} result = append(result, g.fullMatcher.Match(pattern)...) result = append(result, g.domainMatcher.Match(pattern)...) for _, e := range g.otherMatchers { if e.m.Match(pattern) { result = append(result, e.id) } } return result } // Size returns the number of matchers in the MatcherGroup. func (g *MatcherGroup) Size() uint32 { return g.count } ================================================ FILE: common/strmatcher/strmatcher_test.go ================================================ package strmatcher_test import ( "reflect" "testing" "v2ray.com/core/common" . "v2ray.com/core/common/strmatcher" ) // See https://github.com/v2fly/v2ray-core/issues/92#issuecomment-673238489 func TestMatcherGroup(t *testing.T) { rules := []struct { Type Type Domain string }{ { Type: Regex, Domain: "apis\\.us$", }, { Type: Substr, Domain: "apis", }, { Type: Domain, Domain: "googleapis.com", }, { Type: Domain, Domain: "com", }, { Type: Full, Domain: "www.baidu.com", }, { Type: Substr, Domain: "apis", }, { Type: Domain, Domain: "googleapis.com", }, { Type: Full, Domain: "fonts.googleapis.com", }, { Type: Full, Domain: "www.baidu.com", }, { Type: Domain, Domain: "example.com", }, } cases := []struct { Input string Output []uint32 }{ { Input: "www.baidu.com", Output: []uint32{5, 9, 4}, }, { Input: "fonts.googleapis.com", Output: []uint32{8, 3, 7, 4, 2, 6}, }, { Input: "example.googleapis.com", Output: []uint32{3, 7, 4, 2, 6}, }, { Input: "testapis.us", Output: []uint32{1, 2, 6}, }, { Input: "example.com", Output: []uint32{10, 4}, }, } matcherGroup := &MatcherGroup{} for _, rule := range rules { matcher, err := rule.Type.New(rule.Domain) common.Must(err) matcherGroup.Add(matcher) } for _, test := range cases { if m := matcherGroup.Match(test.Input); !reflect.DeepEqual(m, test.Output) { t.Error("unexpected output: ", m, " for test case ", test) } } } ================================================ FILE: common/task/common.go ================================================ package task import "v2ray.com/core/common" // Close returns a func() that closes v. func Close(v interface{}) func() error { return func() error { return common.Close(v) } } ================================================ FILE: common/task/periodic.go ================================================ package task import ( "sync" "time" ) // Periodic is a task that runs periodically. type Periodic struct { // Interval of the task being run Interval time.Duration // Execute is the task function Execute func() error access sync.Mutex timer *time.Timer running bool } func (t *Periodic) hasClosed() bool { t.access.Lock() defer t.access.Unlock() return !t.running } func (t *Periodic) checkedExecute() error { if t.hasClosed() { return nil } if err := t.Execute(); err != nil { t.access.Lock() t.running = false t.access.Unlock() return err } t.access.Lock() defer t.access.Unlock() if !t.running { return nil } t.timer = time.AfterFunc(t.Interval, func() { t.checkedExecute() // nolint: errcheck }) return nil } // Start implements common.Runnable. func (t *Periodic) Start() error { t.access.Lock() if t.running { t.access.Unlock() return nil } t.running = true t.access.Unlock() if err := t.checkedExecute(); err != nil { t.access.Lock() t.running = false t.access.Unlock() return err } return nil } // Close implements common.Closable. func (t *Periodic) Close() error { t.access.Lock() defer t.access.Unlock() t.running = false if t.timer != nil { t.timer.Stop() t.timer = nil } return nil } ================================================ FILE: common/task/periodic_test.go ================================================ package task_test import ( "testing" "time" "v2ray.com/core/common" . "v2ray.com/core/common/task" ) func TestPeriodicTaskStop(t *testing.T) { value := 0 task := &Periodic{ Interval: time.Second * 2, Execute: func() error { value++ return nil }, } common.Must(task.Start()) time.Sleep(time.Second * 5) common.Must(task.Close()) if value != 3 { t.Fatal("expected 3, but got ", value) } time.Sleep(time.Second * 4) if value != 3 { t.Fatal("expected 3, but got ", value) } common.Must(task.Start()) time.Sleep(time.Second * 3) if value != 5 { t.Fatal("Expected 5, but ", value) } common.Must(task.Close()) } ================================================ FILE: common/task/task.go ================================================ package task import ( "context" "v2ray.com/core/common/signal/semaphore" ) // OnSuccess executes g() after f() returns nil. func OnSuccess(f func() error, g func() error) func() error { return func() error { if err := f(); err != nil { return err } return g() } } // Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass. func Run(ctx context.Context, tasks ...func() error) error { n := len(tasks) s := semaphore.New(n) done := make(chan error, 1) for _, task := range tasks { <-s.Wait() go func(f func() error) { err := f() if err == nil { s.Signal() return } select { case done <- err: default: } }(task) } for i := 0; i < n; i++ { select { case err := <-done: return err case <-ctx.Done(): return ctx.Err() case <-s.Wait(): } } return nil } ================================================ FILE: common/task/task_test.go ================================================ package task_test import ( "context" "errors" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/common/task" ) func TestExecuteParallel(t *testing.T) { err := Run(context.Background(), func() error { time.Sleep(time.Millisecond * 200) return errors.New("test") }, func() error { time.Sleep(time.Millisecond * 500) return errors.New("test2") }) if r := cmp.Diff(err.Error(), "test"); r != "" { t.Error(r) } } func TestExecuteParallelContextCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) err := Run(ctx, func() error { time.Sleep(time.Millisecond * 2000) return errors.New("test") }, func() error { time.Sleep(time.Millisecond * 5000) return errors.New("test2") }, func() error { cancel() return nil }) errStr := err.Error() if !strings.Contains(errStr, "canceled") { t.Error("expected error string to contain 'canceled', but actually not: ", errStr) } } func BenchmarkExecuteOne(b *testing.B) { noop := func() error { return nil } for i := 0; i < b.N; i++ { common.Must(Run(context.Background(), noop)) } } func BenchmarkExecuteTwo(b *testing.B) { noop := func() error { return nil } for i := 0; i < b.N; i++ { common.Must(Run(context.Background(), noop, noop)) } } ================================================ FILE: common/type.go ================================================ package common import ( "context" "reflect" ) // ConfigCreator is a function to create an object by a config. type ConfigCreator func(ctx context.Context, config interface{}) (interface{}, error) var ( typeCreatorRegistry = make(map[reflect.Type]ConfigCreator) ) // RegisterConfig registers a global config creator. The config can be nil but must have a type. func RegisterConfig(config interface{}, configCreator ConfigCreator) error { configType := reflect.TypeOf(config) if _, found := typeCreatorRegistry[configType]; found { return newError(configType.Name() + " is already registered").AtError() } typeCreatorRegistry[configType] = configCreator return nil } // CreateObject creates an object by its config. The config type must be registered through RegisterConfig(). func CreateObject(ctx context.Context, config interface{}) (interface{}, error) { configType := reflect.TypeOf(config) creator, found := typeCreatorRegistry[configType] if !found { return nil, newError(configType.String() + " is not registered").AtError() } return creator(ctx, config) } ================================================ FILE: common/type_test.go ================================================ package common_test import ( "context" "testing" . "v2ray.com/core/common" ) type TConfig struct { value int } type YConfig struct { value string } func TestObjectCreation(t *testing.T) { var f = func(ctx context.Context, t interface{}) (interface{}, error) { return func() int { return t.(*TConfig).value }, nil } Must(RegisterConfig((*TConfig)(nil), f)) err := RegisterConfig((*TConfig)(nil), f) if err == nil { t.Error("expect non-nil error, but got nil") } g, err := CreateObject(context.Background(), &TConfig{value: 2}) Must(err) if v := g.(func() int)(); v != 2 { t.Error("expect return value 2, but got ", v) } _, err = CreateObject(context.Background(), &YConfig{value: "T"}) if err == nil { t.Error("expect non-nil error, but got nil") } } ================================================ FILE: common/uuid/uuid.go ================================================ package uuid // import "v2ray.com/core/common/uuid" import ( "bytes" "crypto/rand" "encoding/hex" "v2ray.com/core/common" "v2ray.com/core/common/errors" ) var ( byteGroups = []int{8, 4, 4, 4, 12} ) type UUID [16]byte // String returns the string representation of this UUID. func (u *UUID) String() string { bytes := u.Bytes() result := hex.EncodeToString(bytes[0 : byteGroups[0]/2]) start := byteGroups[0] / 2 for i := 1; i < len(byteGroups); i++ { nBytes := byteGroups[i] / 2 result += "-" result += hex.EncodeToString(bytes[start : start+nBytes]) start += nBytes } return result } // Bytes returns the bytes representation of this UUID. func (u *UUID) Bytes() []byte { return u[:] } // Equals returns true if this UUID equals another UUID by value. func (u *UUID) Equals(another *UUID) bool { if u == nil && another == nil { return true } if u == nil || another == nil { return false } return bytes.Equal(u.Bytes(), another.Bytes()) } // New creates a UUID with random value. func New() UUID { var uuid UUID common.Must2(rand.Read(uuid.Bytes())) return uuid } // ParseBytes converts a UUID in byte form to object. func ParseBytes(b []byte) (UUID, error) { var uuid UUID if len(b) != 16 { return uuid, errors.New("invalid UUID: ", b) } copy(uuid[:], b) return uuid, nil } // ParseString converts a UUID in string form to object. func ParseString(str string) (UUID, error) { var uuid UUID text := []byte(str) if len(text) < 32 { return uuid, errors.New("invalid UUID: ", str) } b := uuid.Bytes() for _, byteGroup := range byteGroups { if text[0] == '-' { text = text[1:] } if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil { return uuid, err } text = text[byteGroup:] b = b[byteGroup/2:] } return uuid, nil } ================================================ FILE: common/uuid/uuid_test.go ================================================ package uuid_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/common/uuid" ) func TestParseBytes(t *testing.T) { str := "2418d087-648d-4990-86e8-19dca1d006d3" bytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3} uuid, err := ParseBytes(bytes) common.Must(err) if diff := cmp.Diff(uuid.String(), str); diff != "" { t.Error(diff) } _, err = ParseBytes([]byte{1, 3, 2, 4}) if err == nil { t.Fatal("Expect error but nil") } } func TestParseString(t *testing.T) { str := "2418d087-648d-4990-86e8-19dca1d006d3" expectedBytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3} uuid, err := ParseString(str) common.Must(err) if r := cmp.Diff(expectedBytes, uuid.Bytes()); r != "" { t.Fatal(r) } _, err = ParseString("2418d087") if err == nil { t.Fatal("Expect error but nil") } _, err = ParseString("2418d087-648k-4990-86e8-19dca1d006d3") if err == nil { t.Fatal("Expect error but nil") } } func TestNewUUID(t *testing.T) { uuid := New() uuid2, err := ParseString(uuid.String()) common.Must(err) if uuid.String() != uuid2.String() { t.Error("uuid string: ", uuid.String(), " != ", uuid2.String()) } if r := cmp.Diff(uuid.Bytes(), uuid2.Bytes()); r != "" { t.Error(r) } } func TestRandom(t *testing.T) { uuid := New() uuid2 := New() if uuid.String() == uuid2.String() { t.Error("duplicated uuid") } } func TestEquals(t *testing.T) { var uuid *UUID var uuid2 *UUID if !uuid.Equals(uuid2) { t.Error("empty uuid should equal") } uuid3 := New() if uuid.Equals(&uuid3) { t.Error("nil uuid equals non-nil uuid") } } ================================================ FILE: config.go ================================================ // +build !confonly package core import ( "io" "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/cmdarg" "v2ray.com/core/main/confloader" ) // ConfigFormat is a configurable format of V2Ray config file. type ConfigFormat struct { Name string Extension []string Loader ConfigLoader } // ConfigLoader is a utility to load V2Ray config from external source. type ConfigLoader func(input interface{}) (*Config, error) var ( configLoaderByName = make(map[string]*ConfigFormat) configLoaderByExt = make(map[string]*ConfigFormat) ) // RegisterConfigLoader add a new ConfigLoader. func RegisterConfigLoader(format *ConfigFormat) error { name := strings.ToLower(format.Name) if _, found := configLoaderByName[name]; found { return newError(format.Name, " already registered.") } configLoaderByName[name] = format for _, ext := range format.Extension { lext := strings.ToLower(ext) if f, found := configLoaderByExt[lext]; found { return newError(ext, " already registered to ", f.Name) } configLoaderByExt[lext] = format } return nil } func getExtension(filename string) string { idx := strings.LastIndexByte(filename, '.') if idx == -1 { return "" } return filename[idx+1:] } // LoadConfig loads config with given format from given source. // input accepts 2 different types: // * []string slice of multiple filename/url(s) to open to read // * io.Reader that reads a config content (the original way) func LoadConfig(formatName string, filename string, input interface{}) (*Config, error) { ext := getExtension(filename) if len(ext) > 0 { if f, found := configLoaderByExt[ext]; found { return f.Loader(input) } } if f, found := configLoaderByName[formatName]; found { return f.Loader(input) } return nil, newError("Unable to load config in ", formatName).AtWarning() } func loadProtobufConfig(data []byte) (*Config, error) { config := new(Config) if err := proto.Unmarshal(data, config); err != nil { return nil, err } return config, nil } func init() { common.Must(RegisterConfigLoader(&ConfigFormat{ Name: "Protobuf", Extension: []string{"pb"}, Loader: func(input interface{}) (*Config, error) { switch v := input.(type) { case cmdarg.Arg: r, err := confloader.LoadConfig(v[0]) common.Must(err) data, err := buf.ReadAllToBytes(r) common.Must(err) return loadProtobufConfig(data) case io.Reader: data, err := buf.ReadAllToBytes(v) common.Must(err) return loadProtobufConfig(data) default: return nil, newError("unknow type") } }, })) } ================================================ FILE: config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: config.proto package core import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" transport "v2ray.com/core/transport" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Config is the master config of V2Ray. V2Ray takes this config as input and // functions accordingly. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Inbound handler configurations. Must have at least one item. Inbound []*InboundHandlerConfig `protobuf:"bytes,1,rep,name=inbound,proto3" json:"inbound,omitempty"` // Outbound handler configurations. Must have at least one item. The first // item is used as default for routing. Outbound []*OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound,proto3" json:"outbound,omitempty"` // App is for configurations of all features in V2Ray. A feature must // implement the Feature interface, and its config type must be registered // through common.RegisterConfig. App []*serial.TypedMessage `protobuf:"bytes,4,rep,name=app,proto3" json:"app,omitempty"` // Transport settings. // Deprecated. Each inbound and outbound should choose their own transport // config. Date to remove: 2020-01-13 // // Deprecated: Do not use. Transport *transport.Config `protobuf:"bytes,5,opt,name=transport,proto3" json:"transport,omitempty"` // Configuration for extensions. The config may not work if corresponding // extension is not loaded into V2Ray. V2Ray will ignore such config during // initialization. Extension []*serial.TypedMessage `protobuf:"bytes,6,rep,name=extension,proto3" json:"extension,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetInbound() []*InboundHandlerConfig { if x != nil { return x.Inbound } return nil } func (x *Config) GetOutbound() []*OutboundHandlerConfig { if x != nil { return x.Outbound } return nil } func (x *Config) GetApp() []*serial.TypedMessage { if x != nil { return x.App } return nil } // Deprecated: Do not use. func (x *Config) GetTransport() *transport.Config { if x != nil { return x.Transport } return nil } func (x *Config) GetExtension() []*serial.TypedMessage { if x != nil { return x.Extension } return nil } // InboundHandlerConfig is the configuration for inbound handler. type InboundHandlerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Tag of the inbound handler. The tag must be unique among all inbound // handlers Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` // Settings for how this inbound proxy is handled. ReceiverSettings *serial.TypedMessage `protobuf:"bytes,2,opt,name=receiver_settings,json=receiverSettings,proto3" json:"receiver_settings,omitempty"` // Settings for inbound proxy. Must be one of the inbound proxies. ProxySettings *serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"` } func (x *InboundHandlerConfig) Reset() { *x = InboundHandlerConfig{} if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InboundHandlerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*InboundHandlerConfig) ProtoMessage() {} func (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message { mi := &file_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InboundHandlerConfig.ProtoReflect.Descriptor instead. func (*InboundHandlerConfig) Descriptor() ([]byte, []int) { return file_config_proto_rawDescGZIP(), []int{1} } func (x *InboundHandlerConfig) GetTag() string { if x != nil { return x.Tag } return "" } func (x *InboundHandlerConfig) GetReceiverSettings() *serial.TypedMessage { if x != nil { return x.ReceiverSettings } return nil } func (x *InboundHandlerConfig) GetProxySettings() *serial.TypedMessage { if x != nil { return x.ProxySettings } return nil } // OutboundHandlerConfig is the configuration for outbound handler. type OutboundHandlerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Tag of this outbound handler. Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` // Settings for how to dial connection for this outbound handler. SenderSettings *serial.TypedMessage `protobuf:"bytes,2,opt,name=sender_settings,json=senderSettings,proto3" json:"sender_settings,omitempty"` // Settings for this outbound proxy. Must be one of the outbound proxies. ProxySettings *serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"` // If not zero, this outbound will be expired in seconds. Not used for now. Expire int64 `protobuf:"varint,4,opt,name=expire,proto3" json:"expire,omitempty"` // Comment of this outbound handler. Not used for now. Comment string `protobuf:"bytes,5,opt,name=comment,proto3" json:"comment,omitempty"` } func (x *OutboundHandlerConfig) Reset() { *x = OutboundHandlerConfig{} if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OutboundHandlerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*OutboundHandlerConfig) ProtoMessage() {} func (x *OutboundHandlerConfig) ProtoReflect() protoreflect.Message { mi := &file_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OutboundHandlerConfig.ProtoReflect.Descriptor instead. func (*OutboundHandlerConfig) Descriptor() ([]byte, []int) { return file_config_proto_rawDescGZIP(), []int{2} } func (x *OutboundHandlerConfig) GetTag() string { if x != nil { return x.Tag } return "" } func (x *OutboundHandlerConfig) GetSenderSettings() *serial.TypedMessage { if x != nil { return x.SenderSettings } return nil } func (x *OutboundHandlerConfig) GetProxySettings() *serial.TypedMessage { if x != nil { return x.ProxySettings } return nil } func (x *OutboundHandlerConfig) GetExpire() int64 { if x != nil { return x.Expire } return 0 } func (x *OutboundHandlerConfig) GetComment() string { if x != nil { return x.Comment } return "" } var File_config_proto protoreflect.FileDescriptor var file_config_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc9, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3d, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x38, 0x0a, 0x03, 0x61, 0x70, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x03, 0x61, 0x70, 0x70, 0x12, 0x3e, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x44, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0xcc, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x53, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xfb, 0x01, 0x0a, 0x15, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x2f, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x50, 0x01, 0x5a, 0x0e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0xaa, 0x02, 0x0a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_config_proto_rawDescOnce sync.Once file_config_proto_rawDescData = file_config_proto_rawDesc ) func file_config_proto_rawDescGZIP() []byte { file_config_proto_rawDescOnce.Do(func() { file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData) }) return file_config_proto_rawDescData } var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.Config (*InboundHandlerConfig)(nil), // 1: v2ray.core.InboundHandlerConfig (*OutboundHandlerConfig)(nil), // 2: v2ray.core.OutboundHandlerConfig (*serial.TypedMessage)(nil), // 3: v2ray.core.common.serial.TypedMessage (*transport.Config)(nil), // 4: v2ray.core.transport.Config } var file_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.Config.inbound:type_name -> v2ray.core.InboundHandlerConfig 2, // 1: v2ray.core.Config.outbound:type_name -> v2ray.core.OutboundHandlerConfig 3, // 2: v2ray.core.Config.app:type_name -> v2ray.core.common.serial.TypedMessage 4, // 3: v2ray.core.Config.transport:type_name -> v2ray.core.transport.Config 3, // 4: v2ray.core.Config.extension:type_name -> v2ray.core.common.serial.TypedMessage 3, // 5: v2ray.core.InboundHandlerConfig.receiver_settings:type_name -> v2ray.core.common.serial.TypedMessage 3, // 6: v2ray.core.InboundHandlerConfig.proxy_settings:type_name -> v2ray.core.common.serial.TypedMessage 3, // 7: v2ray.core.OutboundHandlerConfig.sender_settings:type_name -> v2ray.core.common.serial.TypedMessage 3, // 8: v2ray.core.OutboundHandlerConfig.proxy_settings:type_name -> v2ray.core.common.serial.TypedMessage 9, // [9:9] is the sub-list for method output_type 9, // [9:9] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name } func init() { file_config_proto_init() } func file_config_proto_init() { if File_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InboundHandlerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OutboundHandlerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_config_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_config_proto_goTypes, DependencyIndexes: file_config_proto_depIdxs, MessageInfos: file_config_proto_msgTypes, }.Build() File_config_proto = out.File file_config_proto_rawDesc = nil file_config_proto_goTypes = nil file_config_proto_depIdxs = nil } ================================================ FILE: config.proto ================================================ syntax = "proto3"; package v2ray.core; option csharp_namespace = "V2Ray.Core"; option go_package = "v2ray.com/core"; option java_package = "com.v2ray.core"; option java_multiple_files = true; import "common/serial/typed_message.proto"; import "transport/config.proto"; // Config is the master config of V2Ray. V2Ray takes this config as input and // functions accordingly. message Config { // Inbound handler configurations. Must have at least one item. repeated InboundHandlerConfig inbound = 1; // Outbound handler configurations. Must have at least one item. The first // item is used as default for routing. repeated OutboundHandlerConfig outbound = 2; reserved 3; // App is for configurations of all features in V2Ray. A feature must // implement the Feature interface, and its config type must be registered // through common.RegisterConfig. repeated v2ray.core.common.serial.TypedMessage app = 4; // Transport settings. // Deprecated. Each inbound and outbound should choose their own transport // config. Date to remove: 2020-01-13 v2ray.core.transport.Config transport = 5 [deprecated = true]; // Configuration for extensions. The config may not work if corresponding // extension is not loaded into V2Ray. V2Ray will ignore such config during // initialization. repeated v2ray.core.common.serial.TypedMessage extension = 6; } // InboundHandlerConfig is the configuration for inbound handler. message InboundHandlerConfig { // Tag of the inbound handler. The tag must be unique among all inbound // handlers string tag = 1; // Settings for how this inbound proxy is handled. v2ray.core.common.serial.TypedMessage receiver_settings = 2; // Settings for inbound proxy. Must be one of the inbound proxies. v2ray.core.common.serial.TypedMessage proxy_settings = 3; } // OutboundHandlerConfig is the configuration for outbound handler. message OutboundHandlerConfig { // Tag of this outbound handler. string tag = 1; // Settings for how to dial connection for this outbound handler. v2ray.core.common.serial.TypedMessage sender_settings = 2; // Settings for this outbound proxy. Must be one of the outbound proxies. v2ray.core.common.serial.TypedMessage proxy_settings = 3; // If not zero, this outbound will be expired in seconds. Not used for now. int64 expire = 4; // Comment of this outbound handler. Not used for now. string comment = 5; } ================================================ FILE: context.go ================================================ // +build !confonly package core import ( "context" ) // V2rayKey is the key type of Instance in Context, exported for test. type V2rayKey int const v2rayKey V2rayKey = 1 // FromContext returns an Instance from the given context, or nil if the context doesn't contain one. func FromContext(ctx context.Context) *Instance { if s, ok := ctx.Value(v2rayKey).(*Instance); ok { return s } return nil } // MustFromContext returns an Instance from the given context, or panics if not present. func MustFromContext(ctx context.Context) *Instance { v := FromContext(ctx) if v == nil { panic("V is not in context.") } return v } ================================================ FILE: context_test.go ================================================ package core_test import ( "context" "testing" . "v2ray.com/core" ) func TestContextPanic(t *testing.T) { defer func() { r := recover() if r == nil { t.Error("expect panic, but nil") } }() MustFromContext(context.Background()) } ================================================ FILE: core.go ================================================ // Package core provides an entry point to use V2Ray core functionalities. // // V2Ray makes it possible to accept incoming network connections with certain // protocol, process the data, and send them through another connection with // the same or a difference protocol on demand. // // It may be configured to work with multiple protocols at the same time, and // uses the internal router to tunnel through different inbound and outbound // connections. package core //go:generate go run v2ray.com/core/common/errors/errorgen import ( "runtime" "v2ray.com/core/common/serial" ) var ( version = "4.31.0" build = "Custom" codename = "V2Fly, a community-driven edition of V2Ray." intro = "A unified platform for anti-censorship." ) // Version returns V2Ray's version as a string, in the form of "x.y.z" where x, y and z are numbers. // ".z" part may be omitted in regular releases. func Version() string { return version } // VersionStatement returns a list of strings representing the full version info. func VersionStatement() []string { return []string{ serial.Concat("V2Ray ", Version(), " (", codename, ") ", build, " (", runtime.Version(), " ", runtime.GOOS, "/", runtime.GOARCH, ")"), intro, } } /* ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::c:::::::::::::::::::::::ccc:::cccc::::::c:::::cccc::::cc::::::::::::::::::::::::ccccccc::cccccccccc::cc:::;:::::::::::::::::::::::::::ccc:::cccc:ccc::::::c:::::::::::::::::::::::::::cccc:cccccccccccccccccccccc:;;::::::::::::::::::c::::ccc::::::ccc:::ccc::cccccc::::cc:cc:::ccccccccccccccccccccccccccccccccccccccc :::;::::::::::::::::::::::::::::::::::::::::::::::cc:::::::::::::::::::::::::::::::::::::ccc::cc:::::c:::::::::::ccc::ccc:::::ccc::::::cc:::::cccc:::::::::::::::::::::cccccccccccccccccccccccccc:::::::::::::::::::::::::cc::ccc::::::::::::::c::c::::::::::::c::::::::c::::::cccccccccccccccccccccccccc:;:::::::::::::::ccc::cccccccc::c:cccc::cc:::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::cc::::::::cc::::::::::::ccccc:::::ccccccccc::cccc::ccccccc:::c::::::::c:c:::::cccccccccccccccccccccccccc::::::::::::::::::::::::cc:::::ccc:::c::::::::::::::::c::::::::::::::::::::::ccccccccccccccccccccccccccc:::::::::::::::::::::cccccc:ccccc::ccccccccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::;;::::::::::::::::::::::::::::::::::::::::::::::::::::::::c:::::::::::::::::::::cc::::::::::::::::c:::cc::::c::::::::cccccc::::cccccccccc::ccccccc::c::::::::c::::::::ccccccccccccccccccccccccc:::::::::::::::::::::::::c::::cccc:::cc:::cc:::::::::::::::::::::::::::c::::::ccccccccccccccccccccccccccc::::::::::::::::::::cccc::cccccc:ccc:cccccccccccccccc:cccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::::::::::::::::::::::::::::::cc:::::::::::::::ccc:::::c:::cc::::::::::::::::::::::cc:::c::cccc::::::::::cccccccccccc:ccc::c:::::::ccc:::::::cccccccccccccccccccccccccc:::::::::::::::::::cccccccccccccc::::::::ccc::c::cc:::cc::::::::::::::cc:::::cccccccccccccccccccccccccccc::::::::::::::::::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::::::::::::::::::::::::::::::::::::::::::c::::::::::::::::::::c::::::::::::::::::::ccccc::::::::::::ccc::cc:::::cccc::::cccccccccccc::::cccccc:::ccccc:::::cccccccccccccccccccccccccc::::::::::::::::::::c:cc:::cc::::::::::::cc::cc:::::::cccc::::::c:::::cc::::::ccccccccccccccccccccccccccc:::::::::ccc::::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::cc:::::::::::::::ccccccc:::::ccccc::ccc::ccccccccccccccccccccccccccccccc:cccc::::cccccc::::cccccccccccccccccccccccccc::::::::::::::::c:::::cc::::::::cc:::cccc::::cc::::ccc:ccc::c:::::::::cccc::::ccccccccccccccccccccccccccc::::::::::cc::::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::::::::::::::::::::::::::::::::::::::::c::::::::cc::::::::::::::cc:::::::ccccc:::c:::::ccc::cccc:::cccccc:ccccccccccccccccccccccccccccccccccc::::ccccccc:::cccccccccccccccccccccccccc:::::::::cc:::::::::::::cc:::cc::cccccccc::cccccc::ccccccccc:c::::::::ccc:::::cccccccccccccccccccccccccccc:::::::::ccc:::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::::::::::::::::::::::::::::::::cc:::::::::ccc:::::c::::::cc:::cccc:::c:::ccccc::::ccc::cccccccccc::cccccc::ccccccccccccccccccccccccccccccccccc:ccccccccc::ccccccccccccccccccccccccccc:::::::::::::::::::::::::cc:cccc::cccccccccccc:ccc::cccccc:ccc::::::::cccc:::::ccccccccccccccccccccccccccc:::::::::ccc:::::c:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::::::::c:::::::c:::::c:::::cc::::::::::::cccccc::::ccccccccc:::::::ccccccccccccccccccccccc:cccccccccccccccccccccccllccccccccccccccccccccc::cccccccccccccccccccccccccccc:::::::::c:::::::::::ccccccccccccccccccccccc::cc:::::cccccccc:::cc:::ccccc::::ccccccccccccccccccccccccccc:::::::::cccc::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::cc::cc:::::::c:::::::::::::c:::cc:::::::cccccc::::cccccccc:cc:::ccccccccccccccccccccccccccccccccccccccccccccccoxk00Okxddoolllllloodxkkxdoloolccccccccccccccccccccccccc:::::::::c:::::::::cccccccc::cc::ccccc:::cccccc:ccc:ccccccccc:ccccc:::cc:cc:::ccccccccccccccccccccccccccc:::::::::ccccc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::::::c::::::::ccc:::cc:::::::c::::cc::c::::c:::ccc:::ccc::::::ccc:::ccccccccccccccccccccccccccccccccccccccccccccloxxkOO0OkkkkOKXK00000000KKK0KKXXNNWWWWNXKKK0kdoolclloooddollccccccccc:::::::::cc:::::cccccccccccc:ccccccccc::cccccccccccccc:cccccc:ccccc:::cc:cc:::cccccccccccccccccccccccccccc:::::::cccccc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::::::::::::::::::::c::ccc:::cc::cc::ccc:::::cc:::cc::::ccc::cccc:cccccccccccccccccccccccccccccccccccccccdkO0KKK0000OOkkOKXXXXXXXXXNNXKXNNNNNNNNNNNXXXXXKKKOO0KKKKKKK0Oxxoolllcc:::::::::cc::::ccc::cccc:cccccccc:ccccccccccccccccccccccccccccccc:cc:::cc:::::ccccccccccccccccccccccccccccc::::::::c::c:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::::::::::ccc::::c:::c:ccc::c::ccccc:ccc::::ccccccccc:cc:cccccccccccccc:ccccccccccccccccccccccccccccccccccccccclxOOOO00KKKKKKK00KKXXNNNNXNNNNXXXXXNNNNNNNNNXXKKXXXXXXXXXXXKKXXNXXK00KOdl::::::::ccc:::::cccccccccccc:cccc::ccccccccccccccccccccccccccccc::cc:::ccc:cc::ccccccccccccccccccccccccccccc:::::::cccc::::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::::::ccccccccc:cc::cccc::ccc:ccccc:ccc:::ccccccccccccccccccccccccc:ccccccccccccccccccccccccccccccccccclok000O0XNNNNNXK00KKXK0K0xxk000KXXXXXNNNNNNNXNNNNNNXXXXXNNXXXXXKX0dodxxxxddxOOxl::::::::cccc::::ccccccccccccccccccccc::cccccccccccccccccccccccccccccc::cccccc:::cccccccccccccccccccccccccccc::::::::ccccc::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :::::::::::::c::::cccc:::c::ccccccccc::cc:ccccc:ccc::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclx0XNNNXNXXXNNNXKKKKXXX0OOxooddoodkKXKKKKXNXXNXXXXKKKKXX00KXKKXXNNXK0kko:;;::cccccc::::::::ccc::::ccccccccccccccccccccccccccccccccccccccccccccccccccccc::cccccc:::cccccccccccccccccccccccccccc::::::::cccccc:::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::::::::::::::cc:ccc::ccccc:cccccccccc::cccccccccccccccccccccccccccccc:cccccccccccccccccccccccccccccccccccccccclxKNNNNXOO0KOkO0KXXNXXXXK0kololllol:lO0kkxkOKXXXXK0kkxddxO000OOkOO0KKXNNNXOxl:::coolc::::::::ccc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccc:::cccccc:::ccccccccccccccccccccccccccccc::::ccccccccc::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc :ccc::::::::::cc:c:::cccccccccccccccccccccccccccc:ccccccccccccccccccccccccccccccccccccccccc:ccccccccccccccccccoOXNWWNN0dcllldO0KKXXXXXXXXKxllldkkkxxdlc;;lOK00OOOd:;;;:cdk0OxkxoodxxkOOOOO0Okl,cO0kl::::::::ccc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccc::cccccc::ccccccccccccccccccccccccccccc:::cc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ::cc:::::::::c::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccldO00KNNNXKOddk0KXXXXXXXXXXKK0OOOOO0KKxc;,,.;oxxddxkOx:;,;loddxxolddlc::ccccc:;cooox0KKkl:::::ccccc:::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::ccccccccccccccccccccccccccccc:::::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc c:cc:::c::cccccc::ccccccccccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccldkkxdxkKK000kxkkOO0000OOOOOkdloOKKO0K0dc,...;ccc:lxO0dccclllccloolllc:;;;:cc:;.,:cok0Okkkl::::cccccc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::ccccccccccccccccccccccccccccc:::::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ccc::::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclx0KK0OxdxOOOxl::codxxdoooolccoxO0K00kdoodl;''',;;;:lxOOdollccc::cc:;;;;,'',;;;;'','';:coxxl::::cccccc:::cccccccccccccccccccccccccccccccccccccccccccccccccccccccc:cccccc::cccccccccccccccccccccccccccccc::c::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllccccccccccccccccccccc cc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccldxOKXNXKOxxdl:;;,,,,,,,ckOx;:k00Okkxolc;;;:;,''',,'',:ok0kocc::,.',,;;;;,''',;,;,...',;:ccc::::::ccccc:::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::ccccclcccclcccccccccccccccccc::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllcc ccc::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccooloOKKKK0Oo:;,''..'.. ;xO0OxxOkxdddddoll:;,..,,,,;'.'',:ll:,,,,'..'''',,;;,,,,;;;,',,;;::cc:cccc:ccccc::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::ccccllccccccclcclllcccccccclcc::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclllccccccccccccc cccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclollxOOOO0Oxl:;'''',;;;cdxodxdoolllldk00Od:,'..,;,;;;,,'.....'',,,,,'''.''''.....,,,''',::ccc:cccc::ccccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:::cccclcccllcclcccllcccccccclcc:::ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllcclcclcccccccccccccccc ccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclxkxdddxO0Oxdoc;'',;:ldxdlc:coolcc::ldxolc;;::,..,,;;;;;;'...'';cccc::;,,,,,,,'....,',;:oolccccccccccccccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::cclcccccllccccclccclcllllclccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccclllccccclllcccccccccccccccccccccl cc::cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclxOOxddxkkkdoool:;,.';ccc:::::;::::::cc;;;:cdddo:'''',;;;;;;,,;:codddolc:;;,,,;,,''',,,,;:llllc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclllclllcccccccclllllllllllcccccccccccccccccccccccccccccccccccccccccccccccccccllccllccccccllccccccccccccllcccccccllcccccc ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclxkkkkO00koc;,'.';;,...;;::;::;,,;:::::::;;;::::::;'''',,,,;,;;:loxkkOkxdolc:;;;,,,'...',:lllclccccc::cccccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:cclllllllclllcccclllllllllllcccccccccccccccccccccccccccccccccccccccccccccccccccllcclllcccccccccccccccccllclccllccclccccccc ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccldddkOOO00OOOOxl,.';;'...,;;;;;;,;;;;:::;;::;,'',;;,',,,,,,,,;;:loodxxxxxxxddoolc:;;,...;cdkkdlllc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllllcllllllccclllllllclllllcccccccccccccccccccccccllccccccccccclccclcccllccccllcccccccccccccccllcclllccclccllccccccccccc cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccloodOOkkkOOOOOOkxc,;:,. ..',;;;;;;'',,,,,,,;,...,;,,;;;,,,;;;:clllllcc::clodxxddolc;'',;:ccodlccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:cclllllllllllllllllllllllllllc:ccccccccccccccccccccllccclllcccccccccllcccccccccccccccccclccccccllcccccccclccccccccccccccc ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclollxOkxxxxxxxddddoc;'.... ..,;;;,'....';,.','.',,;::::;;;;;::cloddddooc:;::cclddolc:;:::::cllllc:cccccccclcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclccccc:ccllllllllllllllllllllllllcllc:cccccccccccccccccccccccccllccccccccccccccccccccllccccccllllcccccccccccccccccccccccccccccll ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclccdkkxdooollccc:::;'.......... ..,,,;,''',;,.''',,,;:ccc:::::::::::cccclccc::::::cloolllc:ccccccllccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclccccc::cllllllllllllllllllllllllllllc:cccccccccccccccccccccccccccccccccccccccclllllccccclllccccccccccccccccccccccccccllllllllll cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccoxdxkdl::::::cc;,,''.. .....',,,;;,,,,,'',;;:cloodollc::;;;;:;;,,;;;;::::::c:looooolcclllllllcccccccccccccccccccccccccclllcccccccccccccccccccccccccccclccclccccccccccccccccccccccllllllllllllllllllllllllllllc:cccccccccccccccccccccccccccccclllcclllllllllllcccccccccccccccccccccccccclllllllllllllllll ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllllloooc:;,'',;;,,;:..... ....'',,,,,,,,;;;:ccooddxxxxxdol:;;;:ccc::;;;,;;;;;;clollllllllllllllcccccccccccccccccccclccccclccccccccccccccccccccccclcccccccccllcccclcccccccccccccccccllllllllllllllllllllllllllllccccccccccclcccccccccccclllclllllllllllccclllccccccccccccccccccccccllllllllllllllllllllllll cccccccccccccccclccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::;,,;:;.. ';;';c;'';;'. ....'',,,;;;;;;:clodddxxxxkkkkkkxdoddxxdol:,,,,;;,;:oxdlllllllllllllllcccccccccccccccclcccccccclccccclllcccccccccllcccclcccccccclllccccccccccccccccccccccclllllllllllllllllllllllllllcccccccccccccccllcccccccccccclcccccccccccccccccccccccccccccclllllllllllllllllllllllllllllll ccc::cccccccccccccccccccclccccllcccccccccccclcccccccccccccccccccccccccccccccccccccccccccccccccclccol:;,,''''....','';:;,:;. ...',,,,;;;;;:ccloddxxxxxkkOOOO000Okxddool;'',,;;:loxOkoclllllllllllllcccccccccclccccccccccccccccclccllllllcccccccclcccccccccccclllcccccccccccccccllccccccllllllllllllllllllllllllllllccccccccccccccllccccccccccccccccccccccccccccccccccllllllllllllllllllllllllllllllllllllllll ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclcccclllccccccccccccccccclllllo:;,,,',,,,,,,,'';::::,. ..',,;;;::::cccooddxxxxkkkOOO00000Okxolllc;;:::coxkO00dllllllllllllllccccccccccccccccccccccllllcclcclllllllcccccclllccccccccccclcccclcccccccccccccccccccclllllllllllllllllllllllllllllcccccccccllccccccccccccccccccccccccccccclllllllllllllllllllllllllllllllllllllllllllllllll ccccccllccllccccccccccccccccccccccccccccccccccccccccclccccccccccccccccccccclllcclccccccllcccclcclccc:;;;,,,,,,;;;::;;:c::;'. .....',;;:::::ccllloddxxxxxkkkkOOO000000OkxdolooddxkOOO00Oolllllllllllllcccccccccccccccllllcclllllcllcclllllllcllccllllcccccccccccccccccccccccccccccclllcccccllllllllllllllllllllllllllllc:cccccccccccccccccccccccccccccccllllllllllllllllllllllllllllllllllllllllllllllllllllllll ccccccccccccccclccllccllllllcccclccccccccccccccccccccccccccccccccccccccclllllllllcccllcclllllllcllcc:::::;;,',;;;:cl::lc:;;,,,,'...,;:::ccclollllooddddddxxxxkkkkOO00000OOOOOOO00OOOOO00xlllllllllllllccccccccccclcccclllcclllclccccclccccccccccccccccccccccccccccccccccccccccllcllllllccccllllllllllllllllllllllllllllc:cccccccccccccccccccccllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ccccccccccccccccccccclcccllcccclllclllclllllccccccccccccccccccccccccccccccccccccccccccccccccccccccccc:::;''...,;;;::;;:clcc:::::;;,;:::cccloooooooooooodddddddxxxkkOOOOO0000000KKKK00O0KOollllllllllllccccccccccccccccccccccccccccccccccccccccccccccccccllllllllllllllllllllcccccclcccccccclllllllllllllllllllllllllllllc:ccccccccccclllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll cccccccccccccccccccccccccccccccccccccccccclllclccclcccllllllllcclllccccccccccccccccccccccccccccccccccc:;,.....,;;;:;;,,;;::clllllcc::ccllllllloooooooooooddddddxxkkkOOOOOOOOOOOOOO00K0KKKxllllllllllllcccccccccccccccccccccccccllllcclllllllllllcclllllllllllllcccllccllllllclccccccccccccccllllllllllllllllllllllllllllccllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll cccccccccccccccccccccccccccccccccccccccccccccccccccccccllcccccccccccllcccccclllccllllllllllllllllllllcc:;;,..';:::;;,,'.'',,;::cc:::clloooolloddodddoooooddddddxxxkkOOOOOOOkxodxkO00KKKXXOolllllllllllccccccclccclllllllllllcclllcllllcllllllllllllcccccllllcccccccccccccccllcccccccccccc::clllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllool llllllllllllccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllllclllllllcc:;,..,;;;,,,,,''...',,;;;;;:loooooddddddddddddoooddddddxxxkkkkkkkkkxddOKXXXXKKXXXKxllllllllllllcccccccccccccccllcclllcllccclccccccccccccccccccccccccccccccccccccccccccccccccccccccccllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll llllllllllllllllllllllllllllcllccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccllccc:,'';:;,,,,,,;,'...',,,,;;;clooddddoodddddddooddooodddddxxxxkkkkkkkOkxkkxdxxkkxxkOkolllllllllllccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllolllllllllllllllllllllllllllooloooloo llllllllllllllllllllllllllllllllllllllllllllllcllcccccccccccccccccccccccccccccccccccccccccccccccccccccc:,.,cllc:;;;;;;;,,'',;;;;;;:llooddddodddddddddddooooodddddxxxkkkkkO0KOdc;..'',:cccoollllllllllllccccccccccccccccccccccccccccccccccccccccccccccccllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllloollllllllllllllollllolooooooooo llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllcccccccccccccccccccccccccccccccccc::;;cllol:;;;:::;,'',;;;;;;:clloooodddooddddddoooooodddddddxxxkkOOO00K0ko:;;;:;clllllllllllllllllc:ccccccccccccccccccccclccclllcllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllollllllllllllloooolllllloooooooooollolll llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllcllollcccc:;,'',;;,,,,;;;;;:::ccllloooooooooooooooooooodddddddxxkkkkkOO0KK0kdlclolllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllollllllllllllllllllllllllllllllllllllllllllllllllllllllllloooollllloollooolllloooooooollooooooloolllllcc llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllollool::;,;;;:;'..',,,,;;;::::::ccclllllllooooooooooooooddddddddxxxxxdddxO0000Oxllollllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllolllllllllllllllllllllllllllllllllllllllllllllllllllllllllllollllllloollloollloooooooooollooollolllllcccc:::::: lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllldo:;;:cllll:'...;;;::ccccccccccccccllllllllloooooooooooodddddddddolclooollloollllllllllllllllllllllollllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllooolllloooollllllloooollloooloooooolooooooooolllllcccc:::::::::::::: llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc:clooooollcc:;..';::clllllolcccccccccccllllllllllllloooooooooddddxkkkxdxxxxoclllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllloollllloollllllolllllllllllllllollllllllloooolllooooooolllllllloolloolloooloooolllllcccc::::;::::::::::::::::: lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc:::::::c:::;,',;;:clooooddolcccccccccccccccllllllllloooooooddddxkOOOkdlloddooloollllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllloollllllloolloollllollllllllllllllollooollllllllllloooloooooooolllloollllllloooooolllllllccc::::::;::::::::::::::::cccccccc lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc:::::::::;;,'''.,:cloodddddolcccccccccccllcclccclllllloooooddxxkOOOOkdoccclooooolollllllllllolllllllllllllllllollllllllllllllllllllllllllllllllllllllllllllllllllllllllllloolllllllllllllollooooooooooooolllloooooollllooooollllloooollooooooooooooooooollllllccccc::::::::::::::::::::::::ccccccclllllllo lllllllllllllllllllllllllllllllllllllllllolllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc::;:::;;::cc:;'.,:coodddxxddolc:cccllllllllllllclllllllllooodxxkO000K00kdoooolllllllllollllllllolllllllllolllloolloolllllllllllllllllllllllolllllllllllllllloolllllllllllllloollllooooolloooooooooooooooooooooooooolllloollooooooooooollooooooollllllcccc:::::::;:::::::::::::::::ccccccclllllllllllllllll ooollllllllloolllllllllllllllllllllllllooollllllllllllllllllllllllllllllllllllllllllllllllllllllllloolc:;;;:::cclllllc:clodddxxxxxdolc::cccllllllllllllllllllllllooddxkO00KKKKK0kdooollllooloooooollooollllllllloolllllllllllllllllllllllllllllllllllllllllllooolllollllllllllllllllolloollooolooloolooooooooooooooooooooooolooollooooooollllllllccccc::::::::;:::::::::::::::ccccccccllllllllloolllllllllccc::: lloooolllollloollllllllllllllllllllllllllooollllllllolllllllllllloolllllllllllolloolllllllllllllllllollc:;;:::::::::cclllodddxxxxxxddlc::::ccccccclllllllllllllllllloodxxkOOOOkkxdolooooooooooooolllooolllllllllooollloolllllllloolllooloollloolllllloollllooooollllollloollloolooloooooolloollooooooloooooooooooooooooooooooooollllllccccc:::::::;::::::::::::::::::::ccccccclllllllllloollllllllcccc::::cccccl ooooolllllllllollllllolllllllolllooolllllloooollllllllllllollllllllllolllllllllllllllllllllllllloolllllccc:c::::::;;::lloodddxxxxkkxxdolcc::ccccccclcclllllllllllllllllloooddddoddoooooooooooooooooooooooooooooooloooooollllllllooollllllooollooolllooooooooolllloooooolllllloolllooooolooooollooooooooooooooooooooooooooooolc:::;;;;;;:;;;::::::::::::::::cccccccclllllllllllloollllllllcccc:::::ccccclllllllll lllllllloooooolllooooollollooolllooooooolllooooolllllllllloooooooollooooooollooooollllooooolllooolloooolcccccc:::::::clooodddxxxkkkkkxdddoollllcccccclllllllllllloooollllloooooooooooooooooooooooooooooooooooooooollloooooooolllooolllllllllloooooooooooooooollooloooooooooooollloooooolooooooooooooooooooooooooooooooooooool:;,,;;;;;::::::::::cccccccccclllllllllloollolllllllccccc:::::ccccclllllllooolloollo :::c:cccccclllllllooooooooooooolloooooollooooooolooolllooooollooolllooooooolooooooolooooollooooooloooooolc::::::;;;:clooooodddxxxxkkkkkkxxxdddolllcccccclllllllloooooooooooddddoooooooooooooooooooooooooooooooooooooooooooooooolooooolooolloooooooooooooooooooooooooooooooooooolloooooooollllllloooooooooooooooooooooooooooolc:;;;;:::::cccccccllllllllllloollllllllllccccc::cc:cccccclllllllloooooooooooooooolo ::::::::::::::::::cccccccclllllllllllooooooooooooooollloooooolooooooooooooooooooooooooooolooooooooooooool:::::::;;;cllooooodddxxxxkkkkkkkkkxxxddoolllcccllllloooooooodddddddddoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolllllllllllcccccc::::::cloooooooooooooooooooooooooooolcccccccllllllllllllooolllllllllccccc::::cccccclllllllllooooooooooooooooooooooolooo :::::::::::::::::::::::::::::::::::cccccccllllllllllllllllloooooooooooooooooooooooooooooooooooooooooooollc::c::::;:loooooooddddxxxkkkkkkkkkkxxxdddooollccccllllllooodddooooooooooooooooooooooooooooooooooooooooodxxkkOOOkkkkkxdooooooooooooooooooooooolollllllllllccccccc:::::::::::::::::::::::loooooooooooooooooooooooooooolllllllllllllolllllllccccccccccccccccclllllllooollooooolooooooooooooooooloooooooooo lllllccccccccc:c:::::::::::::::::::::::::::::::::::::::cccccccccccllllllllllooooooooooooooooooooooooooooolc:::::::clooooooooddddxxxkkkkkkkkkkxxxxxddddoollcclllloooooooooooooooooooooooooooooooooooooooooooodxkO0KKK000OOOOOOOOkkkxolllllllllccccccccc::::::::::::::::::::::::::::::::::::::::::loooooooooooooooooooooooooooollllllllllllool::::::cccccccllllllllooooooooloooooooooooooooooooooooooooooooooooooo loolooollollllllllllllccccccccc:::::::::::::::::::::::::::::::::::::::::::::cccccccccccccclllcllllllllllllc::::::cloooooooooddddxxxkkkkkkkkkxxxxxxxxxxxxxddollllooloooooooooooooooooooooooooooooooooooooolloO0OO0KNNNNNXK0OOOOOOOOkkxlc::::::::::::::::::::::::::::::::::::::cccccccccccllllllcclooooooooooooooooooooooooooooolllllllllollolccclllllllooolloooooollloolloooooolooooooooooooooooooooooooooooooooo llllooooooooooooooooooooolllllllllllllcccccccccccccccc::c::::::::::::::::::::::::::::::::::::::::::::::::::::::;;:loodddoooooddddxxxkkkkkkkxxxxxxxxxxxxxxxxdolllolcllccccclooooooooooooooooooooooooooooooloxkxdxkO0KXXNNNNX0OxxkOOOOO0ko::::::::::::::ccccccccccccccccccllllllllllllllllolooollllloooooooooooooooooooooooooooolllllllloollollllllloooloooooooooooooooooolooooooooooooooooooooooooooooooooooooooo ::::ccccccllllllllllllloooloooooooooooloooollllllllllllllllllcccccccccccccccccccccccccccc::::::::::::::::::::::::cloooddoooooddddxxxkkkkkkkxxxxxdxxxxxxxxddddoollccll:::::cooooooooooooooooooooooooooooodddollllodxkkO0KXXNNNK0xddxkO00Odccccccccclllllllllllllllllloooolloooooooolllollllloollllloooooooooooooooooooooooooooollllllllollooolllllloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo llccccccccccc::cccccccccccllllllllloooooooooooooooooooooooooooooooolllllllllllllllllllllllllcclccccccccccccccc:::clooddddoooooddddxxxkkkkkkxxxddddxxdddddddddooollclolc:::coooooooooooooooooooooooooooxkkxlllllllllooxkO00KXXNNX0xooddddxdollllllooooooooooooooooooooolloolllllllcclcccclllloollllooooooooooooooooooooooooooooolllloloooooooollooloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo lllloooolllllllllllccccccccccccccccccccccclllllllllllllooooooooooooooooooooooooooooooooooooooooooooollllllllollcccooddddddooooodddxxxkkkkkkkxxxxxxxxxdddddddoooollllodollclooooooooooooooooooooooooodxxxxollc:;,;:clllodxkO0KXXNNNKOxdoooxdoolooooooooollllllllllllcccccccc::cc:ccccccccloolllllllooooooooooooooooooooooooooooolllollooooooolllooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooooooooooooooolllllllllllllcccccccccccccccccccccccccllllllllllllllllooooooooooooooooooooooooooooooooooloodddddddddoooddddxxkkkkkkkkkkkkkkkxxxddooooooolllldxdollloooooooooooooooooooooodddlclll:;:;,'''',;cclllodxO0KXNNNNXKOdoddlclccccccccccccccccccccccccccclllllllllllllllloooooollloooooooooooooooooooooooooooooollllooolooooollooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooolllollllllllllllccccccccccccccccccccccccccccccclllllllllllllllllllllloddddddddddddddddddxxkkkkkkkkkkkkkkkkkxxddoooooolllodkxolllooooooooooooooooooooddol::::::::cccc:;,'''',:llloxOO00KKXXXXKOxolcclcclclllllllllllllloooooooooooooooolllllllloooooollllooooooooooooooooooooooooooooolloooooooooooolooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolllllllllllllllllllcccclccccccccccccccccldxxddxxxdddddddddddxxkkkkkkkkkkkxxxxxkkkxxddddooooodxkkdlllooooooooooooooooooddolc:ccc:::lxkOkkkxddoc:;,;codk000OOOKKKKXXKOdooooooooooooooooooooooooooooooooooooooooooollooooooolllooooooooooooooooooooooooooooolllooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxxxxxxxxxxdddxddddddxkkOOOkkkxxxxxxxxxxxxxddxxddddddxkkdooodxxdooooooooooooddolcllllccccloxkkkkOO00KKKKK00KKK00000OOKKKKKXXX0kdoooooooooooooooooooooooooooooooooooooooollloooooolllooooooooooooooooooooooooooooolllooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooxkkkkkxxxxxxxxxxxxddddxkOOOOOkkxxxdddddddddddxxxxxddxxkkOOOkdxO00OOkxxdooooooolllllllllccclloddddxxxkOKKKXXXK0OOO0KKK00KXXKKXXXXKOxoooooooooooooooooooooooooooooooooooooollloooooollloooooooooooooooooooooooooooooollooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkOkkkkkkkkkkkkkkkxxdddxkOO0OOkxxddddddddooddxkkkkkxxkkkkOO00xloxkO0KKK0Okxollllllllllllcccclodooooooodxxxxk0KK0OOO00KK00KKKKKKXXXXX0kdoooooooooooooooooooooooooooooooooooolloooooollloooooooooooooooooooooooooooooollooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkOOOOOkkkkkkkOkkkkkkxxdddxOO00OOxxdddooooooddkkkOOOkkkkkOOOOO0Oolooddxkkxxddoollooollllllcc:::clllccclloolccldk0KK00000000O0KKKKKKKKKXX0xooooooooooooooooooooooooooooooooooollooooooollooooooooooooooooddoooooooooooollooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkOO00000OOOOOOOOOOOOOOkkxxddxkO000OkxddoooooddxkOOOOOOOOOOOOOOOO00d:cooooooooddooooooolllllllccc:::::,,,'':olccccldk00000O000OO00KKKKKKKKKX0xooooooooooooooooooooooooooooooooooolooooooolllooodoooooooodddddooooooooooooolooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooddxkOO00000000000OOOO0000000OOkkxxxxkO000OkdddddddxkkO00000OOOOOOO00OO000xl:cloddddddddoooollllllllllccc:cclcclodxxollllllodxO0000000OO0KKKKKKKXXXKKOdooooooooooooooooooooooooooooooooolooooooolllodoodoooooooddoooooooddddooooolloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkkO00KKKKKKKKKKK00000000000KKK00OkkkxxkO0KK0kxdddxxkOO00KKK00OOOO0000000KK0kdc::coddddddooooolllllllllllccccoddxkkkxxxdoodddddoxO0000000O0KKKKKKKXXXXXK0kdoooooooooooooooooooooooooooooooloooooooollooooddoodddddddddddoodoooooooolloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooood ooooooooooooooooooooooooooooooooodooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkkO0KKKKKKKKKKKKKKKKKKKKKKKKKKKKKXKKK00OOkkxk0KKKOOkkOOO000KKXXKK0OO00KKKKK0KKXK0kol::clooooooolllllllllllllllllllodddoooddxdodddxxdddkO00000000KKKKKXXXXXXKXKOdooooooooooooooooooooooooooooooooooooooollooooodoodddddddddddoodooooodoolloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo oooooooooooooooooooooooodoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodxkO0KKKKKKKKKKKKKKKKKKKKXKKXXKKKKKKKXXXXXXXK00OkkkOKXK00000KKKKKXXXXKKKKKXXXXXXXXXXXXX0kdlccccllllllllllllllllllllllllllcclllllloddxxxdxdddddkO0000000KKKKKXXKXXXKKK0xoooooooooooooooooooooooooooooooooooooollooddddoodddddddddddddddddddddoolodooooooooooooooooooooooooooooooooooooooooooooooooooooooooooodddoooooooooooo ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooddddddoodkO0KKKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXXXNNXNNNXKK00OkO0KXKKKXXXXXXXXNNXXXXNNNNNNNNXXXNNNNNX0kddocccccccccclllllllllllllloddoooollllllooxkxxxdddddxkO00000000KKKKKKKKKKKKOdoooooooooooooooooooooooooooooooooooooolooddddddddddooddddddddoddooddooloooooooooooooooooooooooooooooooooooooooooooooooooooooddddooodddooddooooooood oooooooooooooooooooooooooooooooooodooooooooooooooooooooooodddooododddddooddooddxkOKXXKXKKKKKKKKKKKKKXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNXXXKKKXXXXXXNNXXXXNNNNNNNNNNNNNNNNNNNNNNNNNXK0kdol:;;;:::::ccccccllcccldkkOkxddooooooodxkkkxxxxxxxxkOOOOO00000KKKKKKKKKKOdooooooooooooooooooooooooooooooooooooolloddddddodddooddddddddddddddddooooooooooooooooooooooooooooooooooooooooodddooodddoooddddddddddddddddddooooodd oooooodddddoooddoooooddooooooooooooooooodddoooddoooooooooooooddddddoooddxxkkO0KXXXXXXXXXXXXXXKXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNXXXNNNNNNNNNNNNNNNNNNNWWWNNNNNNNNNNNNNXX0Okdolc;;;;;;::::::ccccccldxxxdoooodddddddxxxxxxxxxxxdxxkkOOOOOOO00000KKKKK0kdoooooooooooooooooooooooooooooooooooolloddddddddddddddddddddddddodddooooooooooooodoooooooooooooddooddoooooooooodooodddooodddddddddddddddddddoodddo dooooddoodddddddoooooooodddddooooooooooooooooodoodddoodoodddddoooddxkO0KXXXXXXXXXXXXXXXXXXKKKK00KK00KKKKKXXXXXXXXXXXNNNNNNNNNNWWWWWWNNNNNNNNNNNNNWWNNNNNNNNNNWWNWWWWWWWNNWNNNNNNXK0kxdolc:;;;;;;;;;;:::ccc:codoooollooooddddddddddddddddddxxkkkkkkOOOOO000KKK0xoooooooooooooooooooooooooooooooodooooloddddddddddddddddddddddddddddoolodoooooddddooodoooooooooddoooooddoodoooddddodddddddddddddoddddddddddddddddo ddoooooddddddddddddddddddddddddddddddddddoooooddddddooddddddodxkO0KXXXXXXXXXXXXXXXXXXXXXXKKK0Okkkxxk00KKXXXXXXXXNNNNNNNNNWWNNNNWWWWWWWWWWWWWWWNWWWWWNNNNNWWWWNNNWWWWWWWWWWWNNNNNXK0Okxdoolc:;;,,,,,,;;:clldO00OdoooolllllllllllooooooooodoodddxxxxxkkkkOOO0KK0Oxoddooooooooooooooooooooooooooooooooolodddddddddddddddddddddddddddddoooddddddooddooddooooooooooooddodddodddddddddooddddddddddddodddddoddddddddddd odddddddddddddddddddddddddddddddddddoodddddddddddddddddddodxO0KXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKK0000KKKXXXXXXXXNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNNWWWWWWWWWWWWWWNNXXXK0Okxxddolc:;;,,,,;cdkO0KXXNX0xoooolllllllllllllllllllollllooddddxxxkkOO000000xoodddooooooooooooooooooooooodddddoolodddddddddddddddddddddddddddddoooddooooooddoooodooddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddoddddddddddddddoodddoddddddddddddddddddddk0KXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKKKXXXXXXXXXNNNNNNNNNNNNNNNNWWWWWWWWWWWNNNNWWWWWWWNNNNNNNNNNNNNNNWWWWWWNNNNNXXXK00Okkxxddolc:;;;;;:okOO0KXXXXKkdooollllllccccccccccccccclllllooddddxxkOOO000KKkdoddooodddoooodddooooooooooddooooolooddddddddddddddddddddddddddddoooddoooooddddooodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddoddddddddddddddddddddddk0KKKKXXKXXXXXXKXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKXXXXXXXXXXNNNNNNNNNNNNNNNNNWWWNNNNNNNNNNNNNNNNNNNNNNNXXXXXXNNNNNNNNNNNNNNNNXXXK00OOkkxxxdolc:::::lxkO0KKXXXXX0kdooolllllllccccccccccccccclllloooodddxkkOOO0KKOdoddoooddooooooooooooodoooodoodooooodddddddddddddddddddddddddddddooododddddddddoddddddddodddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddddddddxO0KKKKKKKKKKKKKKXXXXXXXXXKKXXXXXXXXKKXXXXXXXXKKXXXXXXXXNNNNNNNNNNNNNNNNWNNNWWWWWWWWWWWNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWNNNNXXKK000OOkkkxxddolcc:codxO0KKKXKXXKKkdoooollllllcccclccccccccccccclllloodxxkOOOOOOkdodddoddddoooodddooooooooodddddoooodddddddddddddddddddddddddddddooodddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddddddddxO000KKKKKKKKKKKKKKKKKKKKKKKKXXXKKXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWWWWWWNNNNNWWWWWWWWWWWNWWWWWWNNNNXXXKK000OOOOkkxxxddolclodxO00KKKKKKKK0kooooolllllcccccccccccccccc:ccllllodxxxxdoodxxdddooddddooddddddddoooooodddddoolodddddddddddddddddddddddddddddooodddddddddddoddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddddddxO0000KKKKKKKKKKKKKKKKKKKKKKKKKKKKKXKKXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWWWWWWWWWWWNWWWWWWWWWWWWWWWWNNNNNNNXXXK0000OOOOkkkkxxddolloxkO0000KKKKKK0Oxdoloollllcccccccccccccc::::cccllllolllccclxxdododddooddddddddooddoooodddddolodddddddddddddddddddddddddddddoooddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddddddxkOO00000KKKKKKKKKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNWWWWWWWWNNNNNNNXXXKK0000OOOOkkkkxxddooddxkO00000KKK00Okxxddolllllllllllcccccccccccclllcccc::cccccdkxddddddooodddddddddddoooddddddooodddddddddddddddddddddddddddddolodddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddddxkO000000KKKKKKKKKKKKKKK0KKKKKKKKKKKXXXXXXXXNNXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWWNNNNNNNNNWWWWWWWWNNNNNNNNNNNNNNNNNNNNNNNNNNXXXXKK000OOOOOkkkkkxddoddxkOO00000KKKOkxxkOOkkxdoollllllccccccccccccccc::;;::ccllldxkxddddddodddddddddddddooddddddoooddddddddddddddddddddddddddddddooddddddddddddodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddddxkOO00000KKKKKKKKKKK0000000KKKKKKKXXXXXXXXXXXXXXXXXXNXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNWWNNNNNNNNNNNNNNNNNNNXXXXXXXXXXXXXXNNNNNNNNNNXXXXKKK0000OOOOOkkkkxdddddxkkOO00000KK0kxxkOO000KK0Oxdlllllcccc::::::::;;;;;;;:clooodxxddddddddddddddddddddooddddddoooodddddddddddddddddddddddddddddooddddddddddddodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddddxkOOO000000000000000000000000000KKXXXXXXXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNXXXXKKKKKKKKKKKKKKKKXXXXXNNNNNNNNNXXXXXXKKK000OOOOOOkkkxxddddxxkkOO000KKKKOxxkO00KKKKKXXK0kdlllcccc::::;;;;;;;;;;:cloodddxxdddddddddddddddddddooddddddoooodddddddddddddddddddddddddddddooddddddddddddodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddxkkOOO00000000000000000000000000KKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNXXXKK00O000000KKKXXXXXNNNNNNNNNNXXXXXXXKKK00000OOOkkkkxxxddddxxkOO0000KKK0kkO0KKKKKKXXXXXXKxllccc:::::::;;;;;;;;;:clodddxkkddddddddddddddddddooddddddooooddddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddxkOOOOO000000000000000000000000KKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXNXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNXKKK000KKXXXXNNNNNNNNNNNNNNXXXXXXKKKKK0000OOOOkkkxxxxxdddxkOOO00K000K0O0KKKKKKKXXXXXXXKxllccc::::::::;;;;;;;::coddxxkOkdddddddddddddddddoodddddddoooddddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddxkkOOOOOOO0000000000000000000KKKKKKKKXXXXXXKKKKKKKXXXXXXXXXXXXNNXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWWWNNXXXXXXXXXXXNNNNNNNNNNNNXXXXXXXXKKKK0000OOOOkkkkxxxxdddxxkOO000000KK00KKKKKKKKKXXXXXNKxlccc:::::::::;;;;;;;:clodxxkkkkdddddddddddddddddoddddddddooddddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddxkkkOOOOO0000000000000000000000000KKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWWWWNNNNNNXXXXXXXXXXNNNNNXXNNNXXXXXXXXKKKK0000OOOOkkkkkxxxxxxxxkOOO0000000000KKKKKKKKKXXXXXXKxllc:::::::::;;;:;;;;:codxkkkkkkddddddddddddddddoodddddddooddddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddx dddddddddddddddddddddddddddddddddddddddddddddxxkkkkOOOOO000000000000000OO000000000000000KKKKKKKKKKKKXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNXXKKKKXXXXXXXXLOVEXYOUXFOREVERK0000OOOOkkkkxxxxxxxxxkkOOOO000000000000KKKKKKXXXXXX0dllc:::::::::;;;;;;;;:lodxkkkkkkxddddddddddddddoodddddddooddddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddxxkkkkOOOOO000000000O0OOOOOOOOOOOOOOOOOO00000000000KKKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNXXNNNXKK00KKKXXXXXXXXXXXXXXXXKKKKKKKK0000OOOkkkkkxxxxxxxxkkOOOOO00000O0000000KKKKKKXXXXKxllcc::::;;;;::::;;;;::codxxxxkOkddddddddddddddoodddddddooddddddddddddddddddddddddddddddooddddddddddddddddddddddddddddddddddddddddddddddddddddddxxdddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddxxkkkOOOOO000000000OOOOOOOOkkkxxkkkkkkkkOOOOOOO000000KKKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNXNNNNXKK0KKKXXXXXXXXXXXXXXXXKKKKKKKK00000OOOOkkkkxxxxxxxxkkkOOOOOOOOkkOOOOO0000KKKKKKKKKxllccc::::;;:::;;;;;;;::cloodxxkkxddddddddddddddddddddddooodddddddddddddddddddddddddddddoodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddxxxkkOOOOOO0000000OOOOOOOOOkkxdddxxxxddxxkkkOOOOO000000KKKKKKKXXXXXKXXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNXXXXXXXXXXXXXXXXXXXXXXKKKKKKKKK0000OOOOkkkxxxxxxxxxxxkOOkkOkkkddxkkkkOO000000KKKKKklllccc::::::::;;;;;;;;;;:cloddxkkxddddddddddddddddddddddoodddddddddddddddddddddddddxddxdodddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddddddddddddddddddddxxkkkkOOOOOO000OOOOOOOOOOkkxollllooolodxxxxkkOOOOO000000KKKKKKKKXKKKKKXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNNNNNXXXXXXXXXXXXXXXXXXXKKKKKKKKK0000OOOOOkkkxxxxxdlccoxkkkkkkxxdlldxxxxkkOOO0000000Kkollccc:::::::::::;;;;;;;;;:codxkkkxddddddddddddoddddddddoodddddddddddddddddddddddddxxxxdooddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddxxkkkkOOOOOOOOO000000OOOOkkdlcccllccloddxxxkkkkOOO0000000KKKKKKKKKKKKKXXXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNNNXXKKKXXXXXXXXXKKKKKKKKKKKKKKK0000OOOOkkkkxxxxdol:codxkkkkxdo:;clodddxxkkOO0000KKK0dlllcc:::;;:::::::;;;;;;;;;:cldxkOkxdddddddddddoddddddddoodddddddddddddddddddddddddxxxxdooddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddxxddddddddddddddddddddddddddddddddddxxxxkkkkOOOOOO000000OOOOOOOkdcccccccloodddxxxkkkkOOOOOO00000KKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXNNNNNNNNNNNNNNNNNNXXKKKKKKKKXKKKKKKKKKKKKKKKK0000000OOOkkkxxxxxddollodxxxxxdl:',;cclooddxxkkOO000KKKkollccc:::::;;;;::;;;;;;;,;;;cldxkOOkdddddddddddddddddddoodddddddddddddddddddddddddddxxdooddddddddddddddddddddddddddddddxxxxddddddddddddddddddddddddddddddddxxxxx ddddddddddddddxxdddddddddddxdddddddxdddddxdooddddxxxkkkkkOOOO000000OOO000OOkocccccclooddddxxxxkkkkkkOOOOO00KKKKKXKKKKKKKKKKKKKXXXXXXXXXXXXXXXXXXNNNNNNNNNNNNXXXKKKKKKKKKKKKKKKKKKKKK000000000OOOOkkkkxxxddxdoooddxxxxdl;'',;;:cclooodxxkkOO000K0dllcccc::::::::;;;;;;;;;;;;;;:ldkO00kddddddddddddddddddoodddxxdxdddddddddddddxxxdddddxdoodxdddddddddxddddddddddddddddddddddddddddddddddddddddddddxxddxxxxxxxxxxx dddddddddddddxxdddddddddddddddddddxxdddddddoooodddxxxkkkkkOOOO0000000000000Oxoccc:clllooodddxxxkkkkkkkkkkkkO0KK000000KKKKKKKKKKKKXXXXXXXXXXXXXXXXXXXXXNNXXXNNXKK000KKKKKKKKKKKKKKKK00000000OOOOOkkkkxxddddddoooddddddl;'.',,;::ccllooodxxkkkOO00koccccc::::::::;;;:;;;;;;;;;;;:ldkO00Oxddddddddddddddddooddddxxddddddxxddxddxxxxxxxxdxddodxxdddddddddddddddddddxddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxx xdddxdddxxddddddddddddddddddddxxxxxxdddddxdooooddddxxxxkkkOOOO00000000KKKK00Oxl::ccllllooooodddxxxxxxxkxxxdooooxkkOOO000000KKKKKKKKKKKKXXXXXXXXXXXXXXXXXXXXNNXXK0000000KKKKKKKKKKK00000OOOOOOOOkkkxxxdddddooooooodddl'...',;;;::cccllloodddxxkkOOdlcccc::::::;;;;:::;;;;;;;;;;;:ldkOO0Oxddddddddddddddddodxxdxxdxxxxxxxxxxxxxxxdxxxxxxddodxxddddddddddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxdddxxxxxdddddddddddddddxxxddddddddddddooooodddddxxxkkOOOOO00000KKKKKKK00koc::ccllllllllooodddddxxxxxdoc::ldkOOO000000000KKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXNXXXK00000000000000000000OOOOOOkkkkkkxxdddddoooooooodool,....',;;;;::cccccclllooddxxkxocccc::::::;;;;::;;;;;;;;;;;;;:lxkOOOOkdddddddddddddddoodxdxxxxxdxxxxxxxxxxxxxxxxxxxxdodxxdddddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ddddddxxxxxxxxxxxxdxxxxxxxxxxxxxxxxxxxxddddooooooodddxxxkkkkOOOO000KKKKKKKKK00xl:;:ccllccccclllooodddddxxxdlcldkOOO0O00OOOO000000000KKKKKKKKKKKKKKKKKKKKKKXXNNNXKK000OO00000000000000OOOOkkkkkkkxxxdddooooooooooooo:',;''',;;;;:::ccccccccllloddxxocccc::::::::;;:;::;;;;;;;;;;;;:lxkkkOOkxdddddddddddddoodxxxxxxdddxxxxxxxxxxxxxxxxxxxdooxxddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx dddddxxxddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdolloooooddxxxkkkkkOOO000KKKKKKKKKK0kd:,;:ccccccccccclloooodddddddodxkkkkOOOOOOOOOOOOOO000000000KKKKKKKKKKKKKKKKXXNNNNXXK00OOOOOOOO0OO000OOOOOOkkkkkxxxxdddooooollllloool;':oc;,,;;::::::cccccccccllooddocccc:::::::::;;;;;;;;;;;;;;;;;;coxxkkkOkxddddddddddddoodxdxxxxxxxxxxxxxxxxxxxxxxxxxxxddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxdxxxxxxxxxxxddxxxxxxxxxxxxxxdxxxdxxxxxxxdollooooooddxxxkkkkOOO00KKKKKKKKKKK0Oxc,',:ccccccccccccllllooooooodddxxxxkkkkkkkkkOOOOOO00000000000KKKKKKKKKKKKXNNNNNNNXXXK00OOOOOO0OOOO0000OOOOkkkkkxxxddddooooooolooodl,':ddl:;;;::::::ccccccccllllooolcccccc::::::;;;;;;;;;;;;;;;;;;;:loodxxkkkddxdddddddddoodddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxdxxxxxxxxxxxxxxxdxxxxxxxxxxxdolllooooooddxxxkkOO0000KKKKKKKKKKK0Oxl;..';::ccccccccccccllllloooooddddxxxxxxxkkkkkOOOOO0000000000KKKKKK0KKKXXNNNNNNNNNNNNNXXKKKKKKKKKKKKKK0000OOOOOkkxxddddddooooooodddc,cddddl:;:::::cccccccccclllloolcccccc::::::::;;;;;;;::;;;;;;;;;:ccloddxkxdddddddddddddxxxxxxxxxxxxxxxxxxxxxxxdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdxxxxxxxxxdollloooooooddxkOOO000KKKKKKKKKKKK00Oko:,..',;::::ccccccccccccllllooooddddxxxxxxxkkkkOOOOO0000KKKKKKKKXXXXXNNNNNWNNNNNNNNNNNNNXXXXXXXXXXXXXKKKKK0000OOOkkxxxdddddodddddxko:cdxdddoc::::cccccccccccllloddllccccc::::::::;;;;;;;::;;;;;;;;;;::cllodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxolllooodddddxkO0000KKKKKKKKKKKKKK00Oko:,...'',;;::ccccccccccccccllllooooddddxxxxxkkkOOOO000KKKKXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNXXXXXXXXXXXXKKKKKK00000OOkkkxxxxddddxxxkOklcdxdddddlc::cccccccccccclodxdlcccccc:::::::;;:;;;;;;;;;;;;;;;;;;;;:cloodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdooooddxxxkkOO000KKKKKKKKKKKK00K000Okdc,...'',,;;:cccccccccccccccccllllloodddxxxkkkOO000KKKKXXXXXXXXNNNNNNNNNNNNNNNNWWNNNNNNNNXXXXXXXXXXXXXKKKKKKK00000OOOkkkkxxxxxxxkkOOdldxdddddddlccccccccccccllodxdlccccc::::;;;;;::;;;;;;;;;;;;;;;;;;;;;:ccloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdddddxkkOO00000K0KKKKKKKKKK0K000000Oxdc,...',,,,;;::cccccccccccccccllloooddxxkkOOOO0000KKKKKKKKXXXXXNNNNNNNNNNNNNNNNNNNNNNNNNNNXNNXXXXXXXXKKKKKKK000000OOOOkkkkkkkkkxkkkkxodxxxxxxxxxdlccllccccccllodddlcccccc::::::::::;;;;;;;;;;;;;;;::;;;;;;:codxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkOOO00K00KKKKKKKKKKKK00K00000OOxdc,..''',,,;;::cccccccccclllllooodxxkkOOOO000000000KKKKKKKXXXXNNNNNNNNNWWWWNNNNNNNNXXXNXXXXNNNNNXXXXKKKKKKK000000OOOOOOkkkkkxkkxxxkkddxxxxxxxxxxxxdollllcccccllooolccccccc:::::::::;;;;;;;;;;;;;;;;::;;,,;;:ldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkOOOO000000KKKKKKKK000000000000Okxoc'..''',,,;;::cccccclllllooodddxkkOOO0000000000KKKKKKXXXXXXNNNNNNNWNNNWWNNNNXXXXXXXKXXXXXXXXXXXXXXXKKKKKKK0000000OOOOOOkkkxxxxxxxxxxdxxxxxxxxxxxxxxdollllcccclllolcccccccc::::::::::::;;;;;;;;;;;;;::;;;;;:cdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkOOOO00000000KKKKKKK0000000000000Okxo:'.''',,,;;:::ccclllooooodddxxkkOOOO000000000KKKKXXXXXXXXXXXNNNNNNNNNNNNNXXXXKKKKKKKK00KKKKKKKKKKKKKKKKKKKKK0000000OOOkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxdolllcccllllllccccccc:::::::::;;;;;;;;;;;;;;;;;::;;;;:cdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkOO00000000000KKKKKKKKK0000000000OOkxo:'.''',,,;;::cclllooooddddxxxkkkkOOOO0000KKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXKK000OOOO000000K0000KKKKKKKKKKKKKK000000OOOkkkkxxxxxxxxxkkxxxxxxxxxxxxxxxxxxdollllccllllcccccccccc:::::::;;;;;;;;;;;;;;;;:::ccloodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkOO0000000000000000K00KK000000000OOkkxl;,'''',,;;::cclloooooddddxxxxxkkkkOOOO000KKKKKKKKXXXXXKKKKKKKKKKKKKKKKKKKKKK0000OOOOO00000KK0000000K000KKKK000000OOOOkkkkkxxxxxxxxxkkxxxxxxxxxxxxxxxxxxxxddolccclccccccccccc:::::::::;;;;;;;;;;;;;;;::clodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkOO000000000000000000000000000000OOOkdl;;,,,,,,;;::ccloooooodddddxxxxxxxkkOOOOO0000000KKKKKKKKKKKKKKKKKK0KKKKKKKK0000KKKK0000KKKKKKKK000000000000000000OOOOOkkkkkkxxxxxxxxkkxxxxxxxxxxxxxxxxxxxxxxxollcccccccccccc:::::::::::::;;;;;;;;;;;;::clloxkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkOO000000000000000000000000000000OOkxdl::;;,,,;;;::cloooooooddddddddxxxxkkkkkOOOO0000000000000000000000000KKKKKKKKKKKKXXKKKKKKKKKKKKKKKK0000000000OOOOOOOOkkkkkkkkkxxxxxxkkkxxxxxxxxxxxxxxxxxxxxxxxxdolccccccccc::::::::::::::::;;;;;;;;;;;;::ccloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxddddoool xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkOOO000OOOOO00000000000000000000OOOkxdlc::;;;;;;;::clooooooooddddddddddxxkkkkkOOOO0000OOOOOOOOOOOOO000000KKKKKKKXXXXXXXXXXKKKKKKKKKKKKKKKK0000000OOOOOkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdocccccccc::cccc:::::::::;;;;;;;;;;;;;;;;:cloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxddddooollllllcccc xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxddxkkOOOOOOOOOOO0000000000000000000OOOkxdolcc::;;;;;:cllloooddddddxxxxxxxxxxxxkkkkOO0000OOOOOOkkkkkkOOO0000KKKKKKKXXXXXXXXXXXKKKKKKKKKKKKKKKKK0000000OOOOOkkOkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxolccccccccc::::::::::::;;;;;;;;;;,;;;;;;:clodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxddddoooolllllcccccccccccccl xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdddxkkkOOOOOOOOOO00000000000K000000OOOkxdxdlcc::;;;;:cclloooddddxxxxkkxxxxxxxxkkkOOOOOO000OOOOOkkkOOOO0000KKKKKKKKKKXXXXXXXKKKKKKKKKKK0000000000000000OOOOOOOOOkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdlccccccc::::::::::::;;;;;;;;;;;;;;;;;;;:coxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkxxxxxxxxxddddoooolllllllccccccccccclllllllloooo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxooodxxkkkOkkkkOOOOO00000000KK000000OOkxdxxxdocc:::;::ccclllooddxxxkkkkkkkkkkkkkkkkOOOOOOOOOOOOOOOOOO000000000KKKKKKKKKKKKKKKKKKK000000000000000000000OOOOOOkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdllccccccc:::::;;;::::;;;;;;;;;;;;;;;;;cdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkxxxxxxxdddddoooolllllccccccccccccllllllllllooooodddooooooo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxooodxxkkkkkkkkkOOOOO00000000000000OOOkxdxxxxxolc::::::ccclloodddxxkkkkkkOOOOOkkkkkkkkkkkkkkkkkkkOOOO00000000000000000KKKKKKKKKK00000000000000000000OOOOOOkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdollccc::::::::;;:;:;;;;;;;;;;;;;;;;cdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdddddooooolllllllcccccccccccllllllllooooddddddooooooooooooodddd kkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxooodxxkkkkkkkkkOOOOO00000000000000OOOkxdxxxxxxdlc:c:::cccclloodddxxxxkkkOOOOOkkxxxxxxxxxkxxxxxxxkkOOOOOOOOOOO000000000000000000OOkOOOOOOO00000000OOOOOkkkkkxxxxxxxxxxxdddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxolccc::::::::::::::;;;;;;;;;;::;cdxxxxxxxxxxxxxxxxxxxxxxxkxxxxkxxxxxxxxxxddddooooolllllllcccccccccccllllllllloooooodddddooooooooooooooodddxxxkkkkkkkk xxxxxxxkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxdoddxxkkkkkkkkkOOOOO0000000000000OOOkkxdxxxxxxxxolcc::ccllllloooodddxxxkkkOOOkkxxxddddddddddddxxxkkOOOOOOOOOOOOOOOOOOOOOO000OOkkkxxkkkkOOOOOOOOOOOOOOkkkxxxddoooodddddddoooodddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdolccc::::::::::::;;;;;;;;;;::cdxxxxxxxxxkxxxxxxxxxxxxxddddddooooollllllllccccccccccclccllllllllloooodddddddooooooooooooodddddxxxxkkkkkkkkkkkkkkkkkk llllooooooddddddddxxxxxxxxkkkkkkkkxkkkxdddxxxkkkkkkkkkOOOOOOO00000000000OOkkxdxxxxxxxxxxdocc:clllllllllooooddxxxxkkkkkxxddoooooooooddxxxkkkkkkkkkkOOOOOkkOOOOOOOOOOOkkkxxxxxxxkkkkkkOOOOOkkkkxxxdddoollooooooooollooodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdolc::::::::::::::;;;;;;;;:coxxxxxxxxddddoooooooollllllllcccccccccccclclllllllllllooooddddddooooooooooooooooddddxxxkkkkkkkkkkkkkkkkkkkkkkkkkkxxxdd llllllcccclllllllllllooooooooddddddddddddxxxkkkkkkkkkkOOOOOOOO00000000OOOkkkxdxxxxxxxxxxxxoccccllllllllllllooodddxxxxxddooolllllllooddxxxkkkkkkkkkkkkkkkkkkkkkkkOOOOOkkxxxxxxxkkkkkkkkkkkkxxxxdddoooolllllloooooooooodxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxolc:::::::::::::;;::;;;:cloooolllllllccccccccclcclcllccllllllllloooooodddddddooooooooooooooddddddxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxddooollcccc dddddooooooooollllllllllcccccllllllllooddxxxxkkkkkkkkkOOOOOOOOOO000000OOOOkxdxxxxkkkkkkkkkxoccclcccccllllllllllooooooooolllllcccclloddxxxxkkkkkkkkkkkkkkkkkkkkkkkOOOOOkkkkkkkkkkkkkOOkkkkkxxxdddddoooolllllloooooodddxkkxxxkkxxkkxkkkkxxxxxxxxxxxxxxxxxddddddddddoolcccc::::::::::;;;;;;:cccccccccllllllllllloooooooooodddddddddoooooooooooooodddddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxddddooolllccc:::::::::: ddddddddxxxxxxxdddddooooooooooooooollodddxxxxkkkkkkkkkOOOOOOOOOOOO000OOkkkxxdoodddddddddddddlcccclcclllllllllllllllllollllllccccclooddxxxxkkkkkkkkkkxkkkkkkkkOOOOOOOOOOOkkkOOOOOOOOOOOOkkkkxxxxdddooodddoooooodddddddxxxxxxxxdddddddddoooooooooooolllllllllllllllclccllcccccccccc:;;::cllooooooooooodddddddddddddooooooooodddodddddddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxddddoolllccc:::::::::::::::::::::: dddddddddddddddddoooodddddddddxxddxxddddxxxxkkkkkkkkkkkOkOOOOOOOOO000OOkkkxxolcllllllllllllllcccclllllllllllooooooooooooooolllcclloodddxxxkkkkkkkkkkkkkOOOOOOOOOOOOOOOOOOOOO000000OOOOOOOOOkkkxxddooddxxxxxdddddddddollllllllllllllllllllllccccllcccllllllllllllloolooooooooooodoooooodxxxddddooooooooooodddddddddddddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxdddoolllcccc::::::::::::::::::::::::::::::::: kkkkkkkkkkkkkxxxxxdddddddddddddddddddoddxxxxkkkkkkkkkkkkkkOOOOOOOOOOOOkkxxxddoooooooooooooollllccclllllllllooooooooddddxddddoolloooddddxxxxxxxkkkkkkkOOOO00O000000000000000KKK000000000OOOOOkkkkxxddddddxxxxddddddddollllllllllooooooooooooooooooooooooodddddddddddddddddddooooooooooooddddddddddddddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxdddooolllccccc:::::::::::::::::::::::::::::::::::::::::::: kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxdddxxxxkkkkOOOkkxxxkkkkkOOOOOOOkkkkxxddddxdddxxxxxxxxxdddolccllllllllooooooodddddddxxdddddddddddddxxxxxxxxkkkOOOO0000000000KK00KKKKKKKKKKKKKKK000000O0OOOOkkkxxddooddddddddddooddddddxxddxxxxxxxxxxddddddddoooddoooooooooodddddddddddddddddddxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxdddoooolllcccc::::::::::::::::::::::::::::::::::::::::::::::::::::::::c: xxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxdddxxxkkkOOOOOkkkxxxxxkkkkkkkkkkkkkxxddddddddddddddddddddoolccllllllloooooodddddddddddddddddddddddddxxxxxkkOO0000KKKKKKKKKKKKKKKXXKKKKKKKKKKKKK0000000OOOOkkkkxxdololloooooooloddoooooooododdddddddddddddddddddddddxdxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxdddoooollllcccccc::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ccccccc:: cllllloooodddddxxxxxkkkkkkkkkkkkkkkkxdddxxxkkOOOOOOOkkkxxxxxkkkkkkkkxxxxdddxkkkkkkkkkkxxxxxxxxxxdllllloooooooddddddddddddddoooooododdddddxxkkOO00KKKKKKXXXXXXXXXXXXXXXXKKKKKKKKKKKKK000000OOOOOkkkkxxdolllclclllllodddddddddxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxdddddooollllccccccc::::::::::::::::::::::::::::::::::::::::::::::::::::::::c:::cccccccccccccccccc::cc:::: ::::::::::::ccccccccclllloooooddddxxdoddxxkOOOOO000OOOkxxxxxxkkkkkkxxxxddddkkkkkkkkkkkkkkkkkkkkkkdolloodddxxxxxxddddddddooooooooddoodddxkkOO00KKKKKKXXXXXXXXXXXXXXXXKKKKKKKXKKKKKKKKK00000OOOOOkkkxxxddoollccccclloxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxddddoooolllllcccccccc::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::cccccccccccccccccc::::::::::ccccccccc ::::::::::::::::::::::::::::::::::cclodxxkkOO00000000OOkxxxxxxxkkkkxxxddodxkkkkkkkkkkkkkkkkkkkkkkkxoloddxxxkkkOOkkkkkkkxxxxxxxxxxxxxxkkOO00KKKKKKKKKKKKKXXXXXXXXXXKKKKKKKKKKKKKKKKKKK00000OOOOkkkkxxddoooollccllllokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxddddddoooooolllllccccccc:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ccc::ccccccccccccccccccccc::::::::cc:::cccccccccccccccc :::::::::::::::::::::::::::::::::::clddxkkOO000KKKKKK00OkxxxxxxkkkxxxddolllloolooooooodddddddddxxxxdoodxxkkkO000000000000000000OOkkkkOO000KKKKKKKKKKKKKKKKXXXXXXKKKKKKKKKKKKKKKKKKKKKK0000OOOOkkkxxxddoooolllllllloxxxxxxdddxdddddddddoooolllllllllllccccccccccc:::::::::::::::::c:c::::::::::::::::::::::::::::::::::::::::::::::::::::cccccccccccccccccccccccccccccccc:::::::::::cccccccccccccccccccccccccc::: :::::::::::::::::::::::::::::::::::clddxkO000KKKKKKKKKK0Okxxxxxxkkkxxddocc:::::::::::::::::cccccccccldxxkOOO00KKKKKKKKKXXXXXXXKK00OOOO000KKKKKKKXKKKXXXXKKKXXXXKKKKKKKKKKKKKKKKKKKKKKKKK000OOkkkxxxxxdddooollllclccccccccccc:::::cc:::::::::::::c::::::::::::::::cc::::::::::::::::::::::::::::::::::::::::::::::cc:::::::ccccc:ccccccccccccccccccccccccc::::::::ccc::ccccccccccccccccccccccccccc::::::::::::::: ccccccccccc::::::c::::::::::::::::ccodxkO0000KKKKKKXXXKK0Okxxxxxxkkxddol:::::::::::::::::::::::::cc:ldxkOO000KKKKKKXXXXXXXXXXXXXKK00000KKKKXXXXXXXXXXXXXXXXXXXXKKKK000KKXXXXXXXXXXXXXKKKKK00Okkkxxxxxxxxxddoollllc::::::ccc::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::cc::c::::cc:ccccccccccccccccccccccccccccccccccc:::ccccccccccccccccccccccccccccccccccccc::cc::::::::::::cccccccccccc ccccccccccccccccccccccccccccccccccccodkO00KKKKKXXXXXXXXKK0Okxxxxxkxxdolc:::::::::::::::::::::::::c::lxkOO000KKKKKKXXXXXXXXNNNNNXXXKKKKKKXXXXXXXXXXXXXXXXXXXXXKKKK00000KXXXXXNNNNXXXXXXXXKKK00OOkxxxxkkkkkkxxddoolc:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ccccccccccccccccccccccccccccccccccccccccccc::ccccccccccccccccccccccccccccccccccccc:cc:ccc:c::::::::cccccccccllllllllllllooo cccccc:ccccccc::cccccccccccccccccccloxO0KKKKKXXXXXXXXXXXKKOkkkkkkkxxdoccc:::::::c::c:::::::::::ccc:lxOOO00KKKKKKKXXXXXXXXXXNNNNXXXKKKXXXXXXXXXXXXXXXXNNNNNNNXXXXK0OO0KKXXXXNNNNNNXXXXXXXXKKK00OOkkkkOOOOkkkkxxddocc:::::::::::::c::::::::::::::::c::::::::cc::cccccccccccccccccccccccccccccccccccccccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccc:cc:::c::::::::ccccccccclllclllllllloooooooooooooodd cccccccccccccccccccccccccccccccccccldkO00KKKKXXXXXXXXXXXXK0kkkkkkkxxolccccccccccccccccccccccccccccokO0000KKKKKKKXXXXXXXXXXXXNNXXXXKKKKKKXXXXXXXXXXXXXXNNNNNNNNNXXXXXXXXXXXXXNNNNNNNXXXXXXXKKK00OOkkOOOOOOOOkkkxxoccccccccc:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccc::cccccccccccccccccccccccccccccccccccccclcccccccccccccc::cc::c:::cccc::ccccccccccclllllllllllllooooooooooooooddddddddddddddd ccccccccccclccllcccllcccccccccccccloxkO000KKKKXXXXXXXXXXXKK0OOOOOkkxocccccccccccccccccccccccccccldkO0000KKKKKKKKXXXXXXXXXXXXXXXXXKKKKKKKKKKKKXXXXXXXXXXXXXNNNXXXXK0OOOO0KXXXXXXXXXXXXXXXXKKKK000OOOO000000OOOkkkdlcccccccccccccccccccccccccccccccccccccccc:cccccccccccccccccccccccccccccccccccccccccllcccccccccccccccccccccccccccccccccccccccc::cccccccclccllllllllloooooooooooooodddddddddddddddddddddxxxxxxxxx cccccccccccccccccccccccccccccclcclloxkOO00KKKKXXXXNNXXXXXXXKK0000OOxlcccccccccccccccccccccccccccok0000KKKKKKKKKKXXXXXXXXXXXXXXXXXKKK0KKKKKKKKKKKKKKKXXXXXXXXXXXXXNXOddxkOKXXXXXXXXXXKXXXKKKKK000OOO0000000OOOOOOxocccccccccccccccccccccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclclllllllllllloooooooooooodddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxx llllllllllcccccccccccccccccccccccclooxkO000KKKXXXXXXXXXXXXXXXXXXK0Odlllllcccccccccccccccccccccldk0000KKKKKKKKKXXXXXXXXXXXXXXXXXXXKKKK00KKKK0000KKKKKKKKKKXXXXXNNNNNXKKXNNNNXXXXKKKKKKKKKKKKKK000OOO0000000OOOOOOkdlcccccccclcccccccccccccccccccccccccccclccccccclllcccccccccccccccccccccccccccccccccccccccccccccccllllcclllllllllloooooooooooooooooddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxk ddddddddoooooollolllllllllllllllllllloxkO00KKKKKXXXXXXXXXXXXNNXXK0OdlllllccllccllllllllllllllldO00KKKKKKKXXXXXXXXXXXXXXXXNNNNNXXXXKKKKKK000000000KKKKKKKKKKXXXXXNNNNNNNNNNXXXKKKKKKKKKKKKKKK000OOOO0000000OOOOOOOkolllllllcllllcclllccclcccccccccccccccccccccccccccccccccccccccccccccccccccccccclllllllllllllloooooooooodddooodddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkxxxxxkkkkkkkkkkkkkkkkk kkxxxxxxxxxxxxxxxxxxdddddddddddoooollldxkO000KKKXXXXXXXXXXXXXXKKKK0dllllllllllllllllllllllllldOO00KKKKKKXXXXXXXXXXXXXNXXNNNNNNNXXXXKKKKK000000000000KKKKKKKKKKXXXXXNNNXXXXXKKKKKKK0KKKKKK00000OOOOO00000000OOOOOkkdlccccccccccccccccccccccccccccccccccccccccccccclllllllllllllllllllllllloooooooooooooooddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkxxxkkkkkxkkkkxxkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkxkxkxxxxxxxxxxxxdollodxO0000KKKXXXXXXXXXXXXXXKKK0xoooooooollollllllllllllldkO000000KKKKXXXXXXXXNNNNNNNNNNNNNXXXXXXKKKKK00000000000000000KKKKKKXXXXXXXXXKKKKKKK0000000000000OOOOOO00K00000OOOOOkkxolllllllllllllllllllllllllllllllllllloooooooooooooooooooooddddddddddddddddddddddddxdddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkxxxxxkkxxkkkkkkxkkkkkkkkkxxxxkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxdooodxkOO000KKKXXXXXXXXXXXXXXKKKOxxxxxxxxddddddddddddddodxkO0000000KKKKXXXXXXXXXNNNNNNNNNNNXXXXXXXKKKKK000000000000000000KKKKKKKKKKKKKKKKK0000000000000OOOOOOOOO00KK0000OOOOOOkkdoooooooooooooooooooodddddddddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxdoodxxkkkO000KKKXXXXXXXXXXXXXXKK0OkkkkkkkkxkkxxxxxxxxxxxxkkOOO00000000KKKKKKXXXXXXXXNNNNNNXXXXXXXKKKKKK0000000000000000000000000KKKKKK0000000OOOOOO0OOOOOOOOOOO00KKKK00000OOOOOkxxxxdddddxxxxdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkxxkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkddddxxxkkkOO000KKXXXXXXXXXXXXXXXKKOkkkkkkkkkkkkkkkkkkkkkkxkkkOOOOOO000000000KKKKKXXXXXXXXXXXXXXKKKKKKKKKK0000000000000000000000000KK000000000OOOOOOOOOOOOOOOOOOO00KKKKK000000OOOkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdddxxkkkOOO0000KKKKKXXXXXXXXXXXXXK0kkkkkkkkkkkkkkkkkkkkkxxxkkkkkkkOOOOOOOOOO000KKKKKKKKKXXXXXKKKKKKKKKKKKKKKK00000000000000000000000000000OOOOOOOOOOOOOOOOOOOOOO0KKKKKKK000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxdd kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxddxkkkOOOO0000KKKKKKXXXXXXXXXXXXK0kkkkkkkkkkkkkkkkkkkkkxxxxxkkkkkkkOOOOOOOOOOO0000KKKKKKXXXKKKKKKKKKKKKKKKKK0000000000000OO00000000000000OOOOOOOOOOOOOOOOOOOOO000KKKKKK0000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxdddooooooooooo kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxddxkkOOOO00000KKKKKXXXKKXXXXXXXXXKOkkkkkkkkkkkkkkkkkkkkxddxxxkkkkkkkkkkkkkkkkOOOO00000KKKKKKKK000000000K00000000000000000O00000000000000OOOOOOOOOOOOOOOOOOO000000KKKK000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxdddooooooooooooooooddddxxxx kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxdxxkkOOO000000KKKKKKKKXKKXXXXXXXXK0OkkkkkkkkkkkkkkkkkkkxxxxxkkkkkkkkkkkkkkkkkkkkkOOOOOO000000000000000000000000000000000000000000000000OOOOOOOOOOOOOOOOOOO000000KKKK0000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxddddoooooooooooooooooddddxxxxxxxxxxkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdddxkkOOO0000KKKKKKKXXXXKKKKXXXXXXK0OkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkxxxdxxxxxxkkkOOOOOOOOOOOO00000000000000OO0000000000000000OOOOOOOOOOOOOOOOOOOO00000000KKK0000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxddddddooooooddooooooooooddddxxxxxxxxxxxxkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdodxkkOOOOOO000KKKKXXXXXXKKKKKXXKKKK0kkkkkkkkkkkkkkkkkkkkxkkkkOOOkkkkkkkkkkkkkkxxxdddodddddxxxxkkkOOOOOO00OOOO0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO00000000000000000000000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxddddddddooooodddddddddddddddddddxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk xxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkxdodxxkkOOOOO0000KKKXXXXXKKKKKKKKKKKK0OkkkkkkkkkkkkkkkkkkxxkkOOOOOOOOOOOkkkkkkkkkkkxxdoooooddxxxxxkkkkkOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO000000000000000000000000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkxxxxxxxxxxxxddddddddoooodddddddddddddddddddddxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk ddddddddddddddddddddxxxxxxxxxxxxxxdoddxkOOOOOO0000KKKKXXXKKKKKKKKKKKKK0OkkkkkkkkkkkkkkkkkkkkOOOO000000000OOOkkxxxxxxxxddooodddxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkOOOOOOOOOOOOOkkOOOOOOOOOOOOO00000000OOOOOOO0000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxkxxxxxxxxxxxxxdddddddddddddddddddddddddddddddddddxxxxxxxxxxxxxxxkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk xxxxxxxxxxxxxxxdddddddddddddddxdddooodxkOOOOOOO0000KKKKKKKKKKKKKKKKKKK0OkkkkkkkkkkkkkkkkkkkOOOO000000000000OOOkkxxxdddddddddddxxxkkxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOOOOOOO0000OOOOOOOO00000000000OOOkxkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxddddddddddddddddddddddddddddddddddddddddddxxxxxxxxxxxxxxxkxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx kkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxdoodxkkOOO0OO000000KKKKKKKKKKKKKKKK00kdddddddddddddxxxxkOOO00000000000000000000OOkkxxxxxxxxxxddxkkxxxxxxxxxxxxxkkkkkxxkxxxxkkkkxxxkkkkkkkkkkkkkkkkkkkOOOOOOOOOOkkkkOO0000000000OOOkkxxxxxxxxdxxxxdddddddddddddddddddddddddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxddddoooolloo kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdooodxkOOOOOO000000KKKKKKKKKKKKKKK000kxxxxdddddddddddxkkOOO0000000000000000KKKKKK000OOOOOkkkxxddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkxxkkkkkkkkOOOOOOOkkkkkkOO000000000OOOkkxxdddddddddddddddddddddddddddddxxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxddooooollllllloooodddxxx kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxolodxkkOOOO0000000KKKKKKKKKKKKKKK000OkkkkkkkkxkxxkxxxkkOOO0000000000000KKKKKKKKKKKKKKKK000OkxxddddxxddxxddxxxxxxddddddddxddddddxxxxxdddxxxxxxxkkkkkkkkkkkkkkkxxxkkkOOOOOOOOOOkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxdddooooooolllloooooodddxxxxkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxooodxxkkOOO0000000KKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkOOO00000000000K0000KKKKKKKKKKKKKKKKK0Okxddddddddddddddddddddddddddddddddddddddddddddxxxxxkkkkkkkkkkkkkxxxkkxxxkkkkkkxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxddddooooooooooolooooooodddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdooodxxkOOO000000KKKKKXXXXKKKKKK000OkkkkkkkkkkkkkkkkkkkOOO0000000000K00000000KKKKKKKKKKKKKKK0Okddooooooooooodddddddddddddddddddddddddddddddddddxxxxxxkkkkkkkkxxxxxdoooooddxxxxxkkxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxdddddoooooooooooooooooddddxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk dxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkdoooodxkkOOO0000KKKKXXXXXXXKKKKK00OOkkkkkkkkkkkkkkxkkkkOOO000000000KKKKK0000000KKKKKKKKKKKKKK0Okdooooooodoooooddoooodddddddoooooooooooooooooddddddxxxxxxxxxxxxxxxdollllodxkkkkkxxddxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxddddddoooooooollooooooooddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk ddddddddddddddddddddddddxxxxxxkkkkkxoooodxxkkOOO000KKKKXXXXXXXKKKKK00OkkkkkkkkkkkkkkkxxkkOOOO000000000KKKKKK00000KKKKKKKKKKKKKKKK0Okddooooooooooooooooooooooooddoooooooooooooooooooddddddxxxxxxdddddollloodxxxxxddddddxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxddddddoooooooooooolloooooooooddddxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkxxxxxxxddddddddddddddddooooddxxkkOO0000KKKKXXXKKKKKKKK00OkkkkkkkkkkkkkkkxxkkOOOO0000000000KKK0000KKKKKXXKKKKK000KKKKK00kxdoooooooooooooooolooooodkOOOOOkkxoooooooooooooooodddddddddddoolllodddddddddxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxdddddddddooooooooooooooooooooooooooodddddxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdoooodxxkkkOO0000KKKKKKKKKKKKKK0OxddddddddddddddddxkkkOOOO0000000000000000KKKKXXXXXXXXKKKKKKKKK00OxooooooolllllllllloodxkkxO00000kkxdoooooooooooloooooooooooooooodxkkkkkkkkkkkOOOOkxxxdddddddddddddoooooooooooooooooooooooooooooooooddddddddxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoooodxxxkkkOOO000KKKKKKKKKKKKK00kkkkkkxxxxxxxxxxdxxkkOOOOO0000000000000000KKKKXXXXXXXXKKKKKKK0000kxoloollllllllllooodxO00kxdodddkkO0Okxolooooollllllllloooooooxk000000O00000000O0Oxdoooooooddddddddddddxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdlooddxxxkkkkOO00KKKKKKKKKKKK00OkkkkkkkkkkkkkkOkxxkkkOOOOO000000000000000KKKKKKKKKKKKKXXKXXKK0000OkxolllllllllloodxkxdxkOkdlcccldkO0OOOxdoooollllllclllooooodxO000000000000KKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoloddxxkkkkkkOO000KKKKKKKKKK00OkkkkkkkkkkkkkkkkxxkkkOOOOOOO000000000000KK00KKKKKKKKKKKKKXXXXK0OkkOkxdlccccloddxxdoodxxk0KKKkddO0KK0OkxxxddoolllllllllllloodxO0000000000KKKKKK0000Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdloodxxkkkkkkOOO00KKKKKKKKKK00OkkkkkkkkkkkkkkkkkkkkkkOOOOOO000000000000000KKKKKKKKK0KKKKKXXXXK0kxkkkkxolcclodxkkkkkkkO0OkkkkxxO000kdodkkkxoollllllllllloodxO0000KKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxooodxkkkkkOOOOOO000KKKKKKKK0OOkkkkkkkkkkkkkkkkkkOOkkkkOOO0000000000000000KKKKKKKKKK000KKKKKXXXKkddxkxxdlclllll::ododxxdolllllodddxddxdodooollllllllllloxkOO000KKKKKKKKKKKKKKK0000Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdlllodkkkOOOOOOOO000KKKKKKK0OOkkkkkkkkkkkkkkkkkOOOOkkkOOO0000000000000000KKKKKKKKKK00KKKKKKKKXXKOxddddddoloooolloolllcccccccclllloocloodxdollllllllodxkOOO0000KKKKKKKKKKKKKKK0000Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkoloodkkOOO00000000KKKKKKKK0OkkkkkkkkkkkkkkkkkOOOOOkkkOOO0000000000000000000KKKKKKKKKKKKKKKKKXXXXKOdooooolclloolc:ccccc::;;;::ccccllooolollccclllodxkOOO00000KKKKKKKKKKKKKKKKK000Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOxloodxkO000KKKK00KKKKKKKXK0OkkkkkkkkkkkkkkkkkOOOOOkkkOOOO000000000000000000KKKKKKKKKKKKKKKKKKKXKKK0kolllccccccc:::cc:;,,,,,,,:ccclcccccclcllodxxkOOOO0000KKKKKKKKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkdoodxkOO00KKKKKKKKKKKXXXK0OkkkkkkkkkkkkkkkkkkkkkkkkkkOOOO0000000000000000000KKKKKKKKKKKKKKKKKKKKKKKOdlccccccccc:::;;,;loxxo:;::ccccc::clodxkkOkkkOO000KKKXXXKKKKKKKKKKKKKKK0000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxx kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoodxxkO00KKKKKKKKKKKKXXXKOkkkkkkkkkkkkkxxkkkkkkkkkkkkOOOO0000000000000000000KKKKKKKKKKKKKKKKKKKKKK00xl:ccccccccc:::ldOKXXK0xlc:cccc:cldxkkkkkkkkOO00KXXXXXXXXXXXKKKKKKKKK00000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxdd kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdodkkkO00KKKKKKKKKKKKXXXK0kkkkkkkkkkkkkxxxkkkkkkkkkkkkOOOO0000000000000000000KKKKKKKKKKKKKKKKKKKKKKK0kl::ccccccccclokOKXXXXXOocccccloddxxxddddkO0KKXXXXNNXXXXXXXXXXXXXKKKK0000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxddddddddddooooo kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxodkkkO00KKKKKKKKKKKKKXXXKOkkkkkkkkkkkxddxxxxkxxxxxkkkkOOOOOO00000O000000000000KKKKKKKKK00KKKKKKKKKKK0ko:;:c::clldooxO0KXXXKKkllccloodooooodkOKXXXNXXXNNNNNNXXNXXXXXXXXKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxdddddddddddddoooollllllllllooll kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdoxkkOO000KKKKKXXXKKKKXXX0OkkkkkkkkkkxdddxxxxxxxxxxxkkkOOOOOOOOOOOO000OOO00000KK00KKKKKK00KKKKKKKKKKK0Oo;,;:::ldOxldkO0XXXKKOxolllooolodxOKKKXXXNNNNNNNNNNNNNNNXXXXXXXKKKK0000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxddddddddddoodoooolllllllllllloollloooooodddddd kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkdoxkOOO00OO0KKKKKKKKK0KKXX0OkkkkkkkkkdoodddxxxxxxxxxkkkOOOOOOOOOOO00OOOOO0000000000KKKK0000K000KKKKKK00Oo;',;:okKOllxO0KKXXK00kllolloxO0KKKKXXXXXXNNNNNNNNNNNNNNNXXXXXXKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkddddddddddddddddddooooolllllllllllllloooooooooddddddddddddoooooooolo xxxxxxxxxxxxxxxxxxkkkkkkkkkkkOOkkkkkkkOOkkkxddkkkOOOOOOOO0000000000KKKOkkkkkkkkkdooodddddddddxxxkkkkOOOOOOOOO0OOOOO000000000000KKK000000000KKKKK000ko,..:x0XKdlodxk0KK0OO0klloxO0KKKKKXXXXXXXNNNNNNNNNNNNNNNXXXXXXXKKKK00OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkxoooolllllllllloooolooooooooodddddddddddooooooooooooooooooooddddxx ddddddddddddddddxdxxxxxxxxxxxxxxxxxxxxxxxxxxxdddxxkkkxxxxkkkOOOOkkOO0K0OOOkkkkOkdlooooddddddddxxxkkkkOOOOOOOOOOOOOO000000000O00000000000K000000K000Oko,'d0KKKKkolloxxxkxOXKxxO000KKKKKXXXXXXXNNNNNNNNNNNNNNNXXXXXXXKKKK00OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxxxxxxxxxxxxxxxxxxxxxxddddddddddxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkdoooloooooddddddddddooooooooooooooooooooooodddddxxxkkkkkkkkkkkkkk lllllllllllllllloooooooodddddddddddddddddddxxxdoooddddddddddxxkOOOOkO00OkxxxxxxxolloooddddddddxxxxkkkkkkkkOOOOOOOOO0000000O0000000000000000000000000OkooO00KKX0o;::ccccoOXX0000KKKKKKKXXXXXNNNNNNNXXXNNNNNNNNXXXXXXKKKKK00Okkkxxkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxddddddddddddddddddddddddddoooooolllllllldkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxdoooooooooooooooooooodddddxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkk xxdddddddddoooooooooolooolllllllllllllllllloodoollooooddddodddxxkkOOkO00OxddddddollllooodddddddddxxkkkkkkkkkOOOOOOOOOO000O000000000000000000000000000OkkkkkOO0Odc,'''';dOKK000KKKKKKXXXXXXXNNNNNNXXXXXXNNNNNNXXXXXXKKKKKK0Okxxxxdddddddddddddddddddddddddddddddddoooooooollllllllllllllllllllllllloolloooooddxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxdooodddddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk oooooooodooodddddddddxxxxxxxxxxxxddddddoooolccccccllooodddddxxxxxxkkkkO0KOxdollllllllooodddddddddxxxxkkkkkkkkkkOOOOOOOOOOOOOOO00000000000000000000000Oxxddodddddo:;,,:lxkOOOO0KKKXXXXXXXXXXXNNNNNXXXXXXXXXXNNXXXXXXXXXKKK00koooooooollllllllllllllllllllllllllllollloooolloooooooooodddddddddddddddddddddxkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk xxxxxxxxddddddddddoodddddooooooooooooooooool:::::cccllloddddxxkkkkkkOOOO0KXKK0kxollllooodddddxddxxxxxxkkkkkkkkkkOOOOOOOOOOOOOO0000000000000000000000Oxooolcccclll:;;;clooddddk0KKXXXXXXXXXXXXNNNNXXXXXXXXXXXNXXXXXXXXXXKKK0Odoooooooooooooooddoddddddddddddxxddddddddddddoooooooooooooooooddddddddddddddddxkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxxdddddddddddoc::::::::cclooooodxkkOOO00O00KKKXXK0xollloooddxxxxxxxxxxxkkkxkkkkkkkOOOOOOOOOOOOOO00000000000OOOO00000OOxccllc:ccccc:::::ccccllok0KKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKK0Oxdddddddddoodoooooooooooddoodddddoooodddoddddddddddddddxxxxxxkkkkkkkkkkkkkkkxkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkoc::;;;;;:::cllcccodxkkOO00000KKKKKXK0xoloooddxxxxxxxxxxxkkkkkkkkkkkkkkOOOOOOOOOOOO0000000000OOOOOOOOOOOxc,;c:::cccc:::::::ccclokO0KKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKK0kdxdddddddddddddxxdxxxxxxxxxkkkkkkkkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxc::;;:;;;:::clllcclodxkOOOO000KKKKKKKKOdoooddxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkOOkOOOOO000000000OOOOOOOOOOkxl,',:::::::::::::ccccloxO0KKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKKK0OOOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkl:::;;;;;::::ccloollodxxkkOO000KKKKKKKKKkdoddxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOOOO00000000OOOkkkOOkkkxo:'.....''',;:::ccc::cdxO0KKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKKK0Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc::;;;;;;;;:::ccllcclodxxkOO0000000K00KK0kdddxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOOOO000000000OOkkkkkkkkxoc;. ...',,;,;,,:oxkO0000KKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKKKKOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc::;;;;;;;;;;:::ccclllodxxxkOOOOO0000000KK0xdxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOOOO000000000OOOOkkkkkkxoc;' ...'''',,,:odxkOO0000KKKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKXKKKOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc::;;;;;;;;;;::::::cllooddxxxkkOOO000OO00KKKOxxxkkxkkkkkkkkkkkkkkkkkkkkkkkkkkOOOOOOO000000000OOOkkkkkxdol:,. ..'',,,,;;:ldxkkOO000KKKKKKKKKKKKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKKK0OkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdcc::;;;;;;;;;;:::::::looodddddxkOOO0OO0OO00O0OxxxxxkkkkkkkkkkxxkkkkkkkkkkkkkkkkkOOOOOOOO000000OOkkkkxxdol:,. ..',,,;;;:lddxxkOO0000KKKKKKKKKKKKXXXKXXXXXXXXXXXXXXXXXXXXXXXXXKKKK0OkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxlc::;;;;;;;;;;;:::::::cloooooodxkOOOO0OkkkkkOOkxxxxkkkkkkkkkkxxxkkkxxxkkkkkkkkkkkOOOOOOOOO000OOOkkkxxddolc;. ..',,;:::lodxxxkOOO00000000KKKKKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXXXXK0Okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoc::;;;;;;;;;;;:::::::::llooolloxxkkOOdloxxkO0OkxxkkkkkkkkkkxxxxxxxxxxxxxxxkkkkkkOOOOOOOOOO00OOOkxxxxddolc:' ..'',;;::clodxxxkkOOO0000000KKKKKKKKKKKKKKKKKKKKKKKKXXXXXXXXXXXXXXXXKK0OkkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdl:::;;;;;;;;;;;;::::::;:clddddooddxOOdcldxxkO0OkkkkkkkkkkkkxxxxxxxxxxxxxxxxkkkkkOOkkOOOOOOO00OOkxxxdddolc:' .'',;;:cloddxxxkkOOOO0000000K00KKK00KKKKKKKKKKKKKKKXXXXXXXXXXXXXXXXKKK0OkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkoc::;;;;;;;;;;;;;;::cccccloddoooddxkOkolodxkO00OkkkkOkkkkkkxxxxxxxxxxxxxxxxxxkkkkkkkkOOOOOO0OOOkkxddddolc:,. ..',;;:cllodddxxkkkOOOO0000000000000000KKKKKKKKKKKKKXXXXXXXXXXXXXXXXXXKK0OkkkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoc::;;;;;;;;;;;;:c::ccc::::cllodxkOO0kocodxkO00OkkkkkkkkxxxxxxxxxxxxxxxxxxxkkkkkkkkkOOOOOOOOOOOkxxddoolc:,. .',;;:cllodddxxxkkkOOOO000000000000000KKKKKKKKKKKKXXXXXXXXXXXXXXXXXXXXK0OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxl:::;;;;;;;;;;;;::;;;;;;;::ldkkkkOKKKOocldxkO00OkkkkkkxxxxxxxxxxxxxxxxxxxxkkkkkkkkOOOOOOOOOOOOkxxddoolc:,. .',;:ccllloddddxxkkkOOOOO00000000000000KKKKKKKKKKKKKKXXXXXXXXXXXXXXXXXKK0OkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc:::;;;;;;;;;;;::::::::::::ldkOOOO000OocdkkO000OkxxxxxxxxxxxxxxxxxxxxxxxxxxxkkkkOOOOOOOOOOOOOkxxddoolc:,. ..,;:cclllodddxxxxxkOOOOOOO000000000000KKKKKKKKKKKKKKKKXXXXXXXXXXXXXXXKKK0OOOkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkOko:::;;;;;;;;;;;:cccccc::::::coxxxxkkO0xcokkxxkkkkxxxxxxxxddxxxdxxxxxxxkkxxxxxkkkkOOOOOOOOOOOkkxxddoolc:,. ..';::cllloodddxxxxkkkOOOOOOOOOO00000000KKKKKKKKKKKKKKKKXXXXXXXXXXXXXXKKK0OOOOkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc::::::;;;;;;;;:lloolc:;::;;:loxxxkkOkccoxxolodxxxdddddddxxxxxxxxxxxxxkkkkkkkkkkkkkOOOOOOOOkkxxdoooll:;. ..';::clllloodddxxxxkkkkkOOOOOOOOO0000000000KKKKKKKKKKKKKXXXXXXXXXXXXXXKKK0OOkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkdc::::::;;;;;;;;:cccclc;;;;;;;:ldxxkOOkl:loxxxxxxxxdddddddxxxxddddxxxxxxkkkkkkkkkkkkkkkOOkkkkkxxdoollc:;. ..,;::ccllloooddddxxxxkkkkkkOOOOOOO0000000000KKKKKKKKKKKKKKKXXXXXXXXXXXXKK00OkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc::::::;;;;;;;;;:::::;;;,,,;:coxkOO0Oo;:lodxddxxxddddddddxxdddddxxxxxxxkkkkkkkkkkkkkkkkkkkkxxddoollc:;. ..';::cccllloodddddxxxxxkkkkkOOOOOO0000000000000000KKKKKKKKKKKKXXXXXXXXXKKK0OOOOkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxdolc::::;;;;;;;;;;;;;;,'..,;:oxOO00KOl;:codxkOOxdxdddddddddddddxxxxxxxkkkkkkkkkkkkkkkkkkkkxxddoollc:;'....',::cccllllooodddddxxxxxkkkkkkkOOOOO0000000000000KKKKKKKKKKKKKKKKXXXXXKKK00OOOkOOkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkOOkkkkkkkkkkkkkkkkOOkkkxkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxoc:::;;;;;;;;;:::;;'..';cldkOO00Kx:;ccoxkOOkxdddddddddddddddxxxxxxxxxkkkkkkkkkkkkkkxxxxxddoolcc::;'...',;:ccclllllooodddddxxxxxxxxxkkkOOOOO0000000000000KKKKKKKKKKKKXXKKXKKKKKKK0OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkxxkkkkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkOkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkxdolc:::;;;:::ccc:,...,:codxOO000l,;:codxxdolodddddddddddddddxxxxxxxxkxxxxxkkkkkkkxxxxxddollcc:coc''',,;::cclllllooooodddddxxxxxxxxkkkkOOO00OOOOO0000000KKKKKKKKKKXXXKKXKKKKKKK00OOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkOkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxl:::;;::::cc:,....;:cldkkO00x;',,;:c:;:clooooooddddddddddxxxxxxxxxxxkkkkkkkkxxxxxxdoollcc:cxkl,,,,;:::ccccllllooooddddddxxxxxxxkkkkOOOOOOOOO00000000KKKKKKKKKXXKKXXXKKKKK000OOOkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkxkkkOkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxl:::;:::::c:;,'..';:clodkOOOd:;'....';clooooooddddddddddxxxxxxxxxxxkkkkkkkxxxxxxxddoolcc:lkOxl;,;;:::ccccllllllooooddddddxxxxxkkkkOOOOOOO00000000000KKKKKKKKKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxkkkkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkOkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOxoc::::::::::;,..',;:clldxkkOOOkdl,',:clooooooooodddddddxxxxxxxxxxxxkkkkxxxxxxxxxddoolc::dOkkxc;;;:::cccccllllloooooodddddxxxxxkkkkOOOOOOOOO000000000KKKKKKKKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkdl:::::;:::;,''.,;;:codxkkxkOO00x:,:cllloooooooooooddddxxxxxxxxxxxkkkxxxxxxxxxdddolcc;cxOkkkxc;;::::cccccclllllloooodddddxxxxxkkkkOOOOOOOOO00000000000KKKKKKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkocc::::::;;;,,;:::clooodooodddl;,;:cllloooooooooddddddddddxxxxxxxkkxxxxxxxxxxddooc::okkkkkkxl::::ccccccccccllllooooodddddxxxkkkOOOOOOOOOO0000000000KKKKKKKKKKKKKKKKKKKK000OkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOkkkkkxkkkkkOkkkkkkkOkkkkkkkkkkOOOkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkkkkkkkkOOOkkkkkOkkkkkkkkkkkkkkkkkkkOkkxxdl::::;;;,;cc:;;;::::::::;;,,;::cclllooooooooodddddddddxxxxxxxxxxxxxxxxxxxddoc:cxkkkkkkkxl:::ccccccccccclllloooooddddxxxxkkOOOkkkOOOO00000000000KKKKKKKKKKKKKKKKKKKK00OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkOkkkkkxkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkOkkkkkOkkkkkkkOOkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxl:::::;;;:c::;;;;;;;,'.'',;;::ccllllloollooooooooodddddxxxxxxxxxxxxxxxxxxdoc:okkkkkkkkOxl:ccccccccccccllllllloooooddxxxkkOOOkkOOOOOOOO00000000KKKKKKKKKKKKKKKKKKK000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkOOkkkkkkkkkkkkkkkkkkkOkkkkkkkkkOOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkOkkkkkOOOOkkkkOkkkkkkkkkkOOOkkkkOOOkkkkkkkkkkkkkkkkkkkkkkkkkdl::::::::::;;,,,,,,'...',;;;:ccclllllllllooooooooodddddddxxxxxxxxxxxxkxxdoccdOkkkkkkkkkxlcccccccccccccllllllooooodddxxkkkOOOOOOOOOOOO00000000000KKKKKKKKKKKKKKK0000OOOOkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkOOkkkkkOkkkkkkkkkkkkkkOOkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkOOOOOkkkOOOOkkkkOOOOkkkOOOkkOOkkkkkOOkkkkkkkkOOkkkkOkkkkkkkkkOOOkkkkOkkkxdoc:::::::::;;;;;;,,,,;;;;::ccllllllllllllooooooodddddddddxxxddxxxkkkxxoclkOkkkkkkkkkOkoccccccccccccclllllllloodddxxkkkOOOOOOOO0OO0000000000000KKKKKKKKKKKKKK00000OOOkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkkkkOkkkkkkkkkOOkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk OkkkkkOOOkkkkOOkkkkkkkOOkkkOOOOOkkkkkkkkOkkkkkkOOOOOOOOOOOOOOkkkOkkOOkkkkkkkkkOkkxdolc:::cllllc::;;;::;;:::cccccllllllllooooooooddddddddxxxxxxxxkkkkxocdkkkkkkkOkkOkkkdcccccc::ccccccllllllooooddxxkkkOOOOOOO0000000000000000KKKKKKKKKKKKKK00000OOOkkOOOOOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkOOOkkkkkkkkkkkkkkkkkkkOOOOOOOOkkkkkkOOOOOkkkkkkOOOOkkkkkOOOOkkkkOOkkkkkkkkkkkkkkkxxxxkkkkxoc:::::::;:::ccccccllllllllooooooooooddddddddxxxxkkkkkkdlxOkkkkkkkkkOkkkkxlcccccc:ccccccclllllooooddxxkkkkOOOOO00000000000000000KKKKKKKKKKKK0000000OOkOOkkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkOOOOOOkkkOOOOOOOkkkkOOOOkkkkkkkOkkkkkOOOkkkkkOOOkkkkkkkOkkOkkkkdlc::c:::;:::::cccclllllllllllllloooooodddddddxxxkkkkkkdoxOkkkkkkkkkkkkkOkxocccccccccccccclllloooooddxxkkkkkOOO0000000000000000KKK00KKKKKKKKK000000OOOOOkkkOkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkkOOkkkkkkkkOOOOOOkkkOOOkkkkkkkkkkOkkkOkkkkkkkkkkkkkkkOkkkOkkkkkxlccc:::::::::cccccclllllllllllllllloooooodddddxxkkkkkkxdkkkkkkkkkkkkkOOOkkkdlccccccccccccccllllloooddxxkkkkkOOOO00000000000KKKKK0KKKKKKKKKKK0000000OOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOkkkkkxkkkkkkkOOkkkkkkkkkkkOOOkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkdccc:::::::::cccccccclllclllllllllllooooodddddxxxkkkkkxxkkkkkkkkkkkkkkkOkkkkxoccccccccccccccllllloooddxxkkkkOOOOOO00000000K0000K0KKKKKKKK0000000000OOkkkkkkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxocc::::::::::ccccccccccccllllllllllllooooooddxxxkkkkkkkkkkkkkkkkkkkkkkOkkkkkxolclcccccccccccllllloooddxxxkkkkkOOO0000000000KKKKKKKKKKK000000000000OOOkkkkkkkkkkkkkkkkkkkkkkOOOOOkkkkkkkkkkOOkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkOOOkkkOOkkkkkkkkkkkkkkkkOkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkdlcc::::::::::cccccccccccccccclllllllllooooddxxxkkkkkxxkkkkkkkkkkkkkkkOkkkkkxkxocccccccccccccllllllooddxxxkkkkkOO000000000KKKKKKKKKK000000000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kOOkkkOkkkkkkkkkkkkkkkkkkkkkkkkkOkkOOOkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkkkoccc:::::::::cc::ccccccccccccccccllllllloooddxxxxxkkxxkkkOkkkkkkkkOkkOkkkkkxkkxdllccccccc:ccclllllloodddxxkkkkOOO00000000KKKKKKKKK0000000000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkkkkkkkkkkkkkkkkkkkOkkkkkkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkkkOOkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkOkkkkkkkkkkkkkkkkkkkkkkkkdcccc::::::::ccc:cccccccccccccccccclllllloodddxxxxkkxxxkkkOOkkkOkOOOOOkkkkkxkkkkxolccccccccccccclllloodddxxkkkOOOO000000000KKKKK000000000000000000OOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkxkkkkOkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkOOOOOOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkkkOkkOkkkkkkkkkkkkkkkkkkkkOkocccc::::::::::::::::::::ccccccccccclllloooddxxxxkkxxxkkkkkkkkkkkkOOOkxkkkxkkxkOkxolcccccccccccclllooodddxxkkkOOOO00000000KKKKKKKK00000000000000OOOOkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkkkOOkkkkkkkOOOOOOOkkkkkkkkkkkkkkkkkOOkkkkkkkkkkkOOOkkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkxlcccc::::::::::::::::::::::::c::ccccllloooddddxxxxxxxkkkkkkkkkkkkkkOkkkkkxkkkkkOOkdlcccccccccccclllooooddxxkkOOOO00000000KKKKKKKK00000000000000OOOkkOkkkkkkkOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOkkkkOOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk kkkkkkkkkkkkkOOOkkkkkkkOOOOOOOOOOkkkkkkkkkkkkkkkkkOOOOOkkkkOOOOOkkkOkkkOkkkkkkkkkkkkkkkkkkkkkkkkkOkoccccc::::::::::::::::::::::::::ccccllllooddddxxxxxdxxkkkkkkkkkkkkOOkkkkkxkkkkkOOOOxllcccccccccccllloooddxxkkOOOO0000000000000KKK00000000OOOOOOOOkkkOkkkkkkkOOOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkOOkkkkkkxkkkkkkkkkkOkkkkkOkkkkkkkkkkkkkkkkkkOkkkkOkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk */ ================================================ FILE: errors.generated.go ================================================ package core import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: features/dns/client.go ================================================ package dns import ( "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/features" ) // Client is a V2Ray feature for querying DNS information. // // v2ray:api:stable type Client interface { features.Feature // LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses. LookupIP(domain string) ([]net.IP, error) } // IPv4Lookup is an optional feature for querying IPv4 addresses only. // // v2ray:api:beta type IPv4Lookup interface { LookupIPv4(domain string) ([]net.IP, error) } // IPv6Lookup is an optional feature for querying IPv6 addresses only. // // v2ray:api:beta type IPv6Lookup interface { LookupIPv6(domain string) ([]net.IP, error) } // ClientType returns the type of Client interface. Can be used for implementing common.HasType. // // v2ray:api:beta func ClientType() interface{} { return (*Client)(nil) } // ErrEmptyResponse indicates that DNS query succeeded but no answer was returned. var ErrEmptyResponse = errors.New("empty response") type RCodeError uint16 func (e RCodeError) Error() string { return serial.Concat("rcode: ", uint16(e)) } func RCodeFromError(err error) uint16 { if err == nil { return 0 } cause := errors.Cause(err) if r, ok := cause.(RCodeError); ok { return uint16(r) } return 0 } ================================================ FILE: features/dns/localdns/client.go ================================================ package localdns import ( "v2ray.com/core/common/net" "v2ray.com/core/features/dns" ) // Client is an implementation of dns.Client, which queries localhost for DNS. type Client struct{} // Type implements common.HasType. func (*Client) Type() interface{} { return dns.ClientType() } // Start implements common.Runnable. func (*Client) Start() error { return nil } // Close implements common.Closable. func (*Client) Close() error { return nil } // LookupIP implements Client. func (*Client) LookupIP(host string) ([]net.IP, error) { ips, err := net.LookupIP(host) if err != nil { return nil, err } parsedIPs := make([]net.IP, 0, len(ips)) for _, ip := range ips { parsed := net.IPAddress(ip) if parsed != nil { parsedIPs = append(parsedIPs, parsed.IP()) } } if len(parsedIPs) == 0 { return nil, dns.ErrEmptyResponse } return parsedIPs, nil } // LookupIPv4 implements IPv4Lookup. func (c *Client) LookupIPv4(host string) ([]net.IP, error) { ips, err := c.LookupIP(host) if err != nil { return nil, err } ipv4 := make([]net.IP, 0, len(ips)) for _, ip := range ips { if len(ip) == net.IPv4len { ipv4 = append(ipv4, ip) } } if len(ipv4) == 0 { return nil, dns.ErrEmptyResponse } return ipv4, nil } // LookupIPv6 implements IPv6Lookup. func (c *Client) LookupIPv6(host string) ([]net.IP, error) { ips, err := c.LookupIP(host) if err != nil { return nil, err } ipv6 := make([]net.IP, 0, len(ips)) for _, ip := range ips { if len(ip) == net.IPv6len { ipv6 = append(ipv6, ip) } } if len(ipv6) == 0 { return nil, dns.ErrEmptyResponse } return ipv6, nil } // New create a new dns.Client that queries localhost for DNS. func New() *Client { return &Client{} } ================================================ FILE: features/errors.generated.go ================================================ package features import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: features/feature.go ================================================ package features import "v2ray.com/core/common" //go:generate go run v2ray.com/core/common/errors/errorgen // Feature is the interface for V2Ray features. All features must implement this interface. // All existing features have an implementation in app directory. These features can be replaced by third-party ones. type Feature interface { common.HasType common.Runnable } // PrintDeprecatedFeatureWarning prints a warning for deprecated feature. func PrintDeprecatedFeatureWarning(feature string) { newError("You are using a deprecated feature: " + feature + ". Please update your config file with latest configuration format, or update your client software.").WriteToLog() } ================================================ FILE: features/inbound/inbound.go ================================================ package inbound import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/features" ) // Handler is the interface for handlers that process inbound connections. // // v2ray:api:stable type Handler interface { common.Runnable // The tag of this handler. Tag() string // Deprecated. Do not use in new code. GetRandomInboundProxy() (interface{}, net.Port, int) } // Manager is a feature that manages InboundHandlers. // // v2ray:api:stable type Manager interface { features.Feature // GetHandlers returns an InboundHandler for the given tag. GetHandler(ctx context.Context, tag string) (Handler, error) // AddHandler adds the given handler into this Manager. AddHandler(ctx context.Context, handler Handler) error // RemoveHandler removes a handler from Manager. RemoveHandler(ctx context.Context, tag string) error } // ManagerType returns the type of Manager interface. Can be used for implementing common.HasType. // // v2ray:api:stable func ManagerType() interface{} { return (*Manager)(nil) } ================================================ FILE: features/outbound/outbound.go ================================================ package outbound import ( "context" "v2ray.com/core/common" "v2ray.com/core/features" "v2ray.com/core/transport" ) // Handler is the interface for handlers that process outbound connections. // // v2ray:api:stable type Handler interface { common.Runnable Tag() string Dispatch(ctx context.Context, link *transport.Link) } type HandlerSelector interface { Select([]string) []string } // Manager is a feature that manages outbound.Handlers. // // v2ray:api:stable type Manager interface { features.Feature // GetHandler returns an outbound.Handler for the given tag. GetHandler(tag string) Handler // GetDefaultHandler returns the default outbound.Handler. It is usually the first outbound.Handler specified in the configuration. GetDefaultHandler() Handler // AddHandler adds a handler into this outbound.Manager. AddHandler(ctx context.Context, handler Handler) error // RemoveHandler removes a handler from outbound.Manager. RemoveHandler(ctx context.Context, tag string) error } // ManagerType returns the type of Manager interface. Can be used to implement common.HasType. // // v2ray:api:stable func ManagerType() interface{} { return (*Manager)(nil) } ================================================ FILE: features/policy/default.go ================================================ package policy import ( "time" ) // DefaultManager is the implementation of the Manager. type DefaultManager struct{} // Type implements common.HasType. func (DefaultManager) Type() interface{} { return ManagerType() } // ForLevel implements Manager. func (DefaultManager) ForLevel(level uint32) Session { p := SessionDefault() if level == 1 { p.Timeouts.ConnectionIdle = time.Second * 600 } return p } // ForSystem implements Manager. func (DefaultManager) ForSystem() System { return System{} } // Start implements common.Runnable. func (DefaultManager) Start() error { return nil } // Close implements common.Closable. func (DefaultManager) Close() error { return nil } ================================================ FILE: features/policy/policy.go ================================================ package policy import ( "context" "runtime" "time" "v2ray.com/core/common/platform" "v2ray.com/core/features" ) // Timeout contains limits for connection timeout. type Timeout struct { // Timeout for handshake phase in a connection. Handshake time.Duration // Timeout for connection being idle, i.e., there is no egress or ingress traffic in this connection. ConnectionIdle time.Duration // Timeout for an uplink only connection, i.e., the downlink of the connection has been closed. UplinkOnly time.Duration // Timeout for an downlink only connection, i.e., the uplink of the connection has been closed. DownlinkOnly time.Duration } // Stats contains settings for stats counters. type Stats struct { // Whether or not to enable stat counter for user uplink traffic. UserUplink bool // Whether or not to enable stat counter for user downlink traffic. UserDownlink bool } // Buffer contains settings for internal buffer. type Buffer struct { // Size of buffer per connection, in bytes. -1 for unlimited buffer. PerConnection int32 } // SystemStats contains stat policy settings on system level. type SystemStats struct { // Whether or not to enable stat counter for uplink traffic in inbound handlers. InboundUplink bool // Whether or not to enable stat counter for downlink traffic in inbound handlers. InboundDownlink bool // Whether or not to enable stat counter for uplink traffic in outbound handlers. OutboundUplink bool // Whether or not to enable stat counter for downlink traffic in outbound handlers. OutboundDownlink bool } // System contains policy settings at system level. type System struct { Stats SystemStats Buffer Buffer } // Session is session based settings for controlling V2Ray requests. It contains various settings (or limits) that may differ for different users in the context. type Session struct { Timeouts Timeout // Timeout settings Stats Stats Buffer Buffer } // Manager is a feature that provides Policy for the given user by its id or level. // // v2ray:api:stable type Manager interface { features.Feature // ForLevel returns the Session policy for the given user level. ForLevel(level uint32) Session // ForSystem returns the System policy for V2Ray system. ForSystem() System } // ManagerType returns the type of Manager interface. Can be used to implement common.HasType. // // v2ray:api:stable func ManagerType() interface{} { return (*Manager)(nil) } var defaultBufferSize int32 func init() { const key = "v2ray.ray.buffer.size" const defaultValue = -17 size := platform.EnvFlag{ Name: key, AltName: platform.NormalizeEnvName(key), }.GetValueAsInt(defaultValue) switch size { case 0: defaultBufferSize = -1 // For pipe to use unlimited size case defaultValue: // Env flag not defined. Use default values per CPU-arch. switch runtime.GOARCH { case "arm", "mips", "mipsle": defaultBufferSize = 0 case "arm64", "mips64", "mips64le": defaultBufferSize = 4 * 1024 // 4k cache for low-end devices default: defaultBufferSize = 512 * 1024 } default: defaultBufferSize = int32(size) * 1024 * 1024 } } func defaultBufferPolicy() Buffer { return Buffer{ PerConnection: defaultBufferSize, } } // SessionDefault returns the Policy when user is not specified. func SessionDefault() Session { return Session{ Timeouts: Timeout{ //Align Handshake timeout with nginx client_header_timeout //So that this value will not indicate server identity Handshake: time.Second * 60, ConnectionIdle: time.Second * 300, UplinkOnly: time.Second * 1, DownlinkOnly: time.Second * 1, }, Stats: Stats{ UserUplink: false, UserDownlink: false, }, Buffer: defaultBufferPolicy(), } } type policyKey int32 const ( bufferPolicyKey policyKey = 0 ) func ContextWithBufferPolicy(ctx context.Context, p Buffer) context.Context { return context.WithValue(ctx, bufferPolicyKey, p) } func BufferPolicyFromContext(ctx context.Context) Buffer { pPolicy := ctx.Value(bufferPolicyKey) if pPolicy == nil { return defaultBufferPolicy() } return pPolicy.(Buffer) } ================================================ FILE: features/routing/context.go ================================================ package routing import ( "v2ray.com/core/common/net" ) // Context is a feature to store connection information for routing. // // v2ray:api:stable type Context interface { // GetInboundTag returns the tag of the inbound the connection was from. GetInboundTag() string // GetSourcesIPs returns the source IPs bound to the connection. GetSourceIPs() []net.IP // GetSourcePort returns the source port of the connection. GetSourcePort() net.Port // GetTargetIPs returns the target IP of the connection or resolved IPs of target domain. GetTargetIPs() []net.IP // GetTargetPort returns the target port of the connection. GetTargetPort() net.Port // GetTargetDomain returns the target domain of the connection, if exists. GetTargetDomain() string // GetNetwork returns the network type of the connection. GetNetwork() net.Network // GetProtocol returns the protocol from the connection content, if sniffed out. GetProtocol() string // GetUser returns the user email from the connection content, if exists. GetUser() string // GetAttributes returns extra attributes from the conneciont content. GetAttributes() map[string]string } ================================================ FILE: features/routing/dispatcher.go ================================================ package routing import ( "context" "v2ray.com/core/common/net" "v2ray.com/core/features" "v2ray.com/core/transport" ) // Dispatcher is a feature that dispatches inbound requests to outbound handlers based on rules. // Dispatcher is required to be registered in a V2Ray instance to make V2Ray function properly. // // v2ray:api:stable type Dispatcher interface { features.Feature // Dispatch returns a Ray for transporting data for the given request. Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) } // DispatcherType returns the type of Dispatcher interface. Can be used to implement common.HasType. // // v2ray:api:stable func DispatcherType() interface{} { return (*Dispatcher)(nil) } ================================================ FILE: features/routing/dns/context.go ================================================ package dns //go:generate go run v2ray.com/core/common/errors/errorgen import ( "v2ray.com/core/common/net" "v2ray.com/core/features/dns" "v2ray.com/core/features/routing" ) // ResolvableContext is an implementation of routing.Context, with domain resolving capability. type ResolvableContext struct { routing.Context dnsClient dns.Client resolvedIPs []net.IP } // GetTargetIPs overrides original routing.Context's implementation. func (ctx *ResolvableContext) GetTargetIPs() []net.IP { if ips := ctx.Context.GetTargetIPs(); len(ips) != 0 { return ips } if len(ctx.resolvedIPs) > 0 { return ctx.resolvedIPs } if domain := ctx.GetTargetDomain(); len(domain) != 0 { ips, err := ctx.dnsClient.LookupIP(domain) if err == nil { ctx.resolvedIPs = ips return ips } newError("resolve ip for ", domain).Base(err).WriteToLog() } return nil } // ContextWithDNSClient creates a new routing context with domain resolving capability. // Resolved domain IPs can be retrieved by GetTargetIPs(). func ContextWithDNSClient(ctx routing.Context, client dns.Client) routing.Context { return &ResolvableContext{Context: ctx, dnsClient: client} } ================================================ FILE: features/routing/dns/errors.generated.go ================================================ package dns import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: features/routing/router.go ================================================ package routing import ( "v2ray.com/core/common" "v2ray.com/core/features" ) // Router is a feature to choose an outbound tag for the given request. // // v2ray:api:stable type Router interface { features.Feature // PickRoute returns a route decision based on the given routing context. PickRoute(ctx Context) (Route, error) } // Route is the routing result of Router feature. // // v2ray:api:stable type Route interface { // A Route is also a routing context. Context // GetOutboundGroupTags returns the detoured outbound group tags in sequence before a final outbound is chosen. GetOutboundGroupTags() []string // GetOutboundTag returns the tag of the outbound the connection was dispatched to. GetOutboundTag() string } // RouterType return the type of Router interface. Can be used to implement common.HasType. // // v2ray:api:stable func RouterType() interface{} { return (*Router)(nil) } // DefaultRouter is an implementation of Router, which always returns ErrNoClue for routing decisions. type DefaultRouter struct{} // Type implements common.HasType. func (DefaultRouter) Type() interface{} { return RouterType() } // PickRoute implements Router. func (DefaultRouter) PickRoute(ctx Context) (Route, error) { return nil, common.ErrNoClue } // Start implements common.Runnable. func (DefaultRouter) Start() error { return nil } // Close implements common.Closable. func (DefaultRouter) Close() error { return nil } ================================================ FILE: features/routing/session/context.go ================================================ package session import ( "context" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/features/routing" ) // Context is an implementation of routing.Context, which is a wrapper of context.context with session info. type Context struct { Inbound *session.Inbound Outbound *session.Outbound Content *session.Content } // GetInboundTag implements routing.Context. func (ctx *Context) GetInboundTag() string { if ctx.Inbound == nil { return "" } return ctx.Inbound.Tag } // GetSourceIPs implements routing.Context. func (ctx *Context) GetSourceIPs() []net.IP { if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() { return nil } dest := ctx.Inbound.Source if dest.Address.Family().IsDomain() { return nil } return []net.IP{dest.Address.IP()} } // GetSourcePort implements routing.Context. func (ctx *Context) GetSourcePort() net.Port { if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() { return 0 } return ctx.Inbound.Source.Port } // GetTargetIPs implements routing.Context. func (ctx *Context) GetTargetIPs() []net.IP { if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { return nil } if ctx.Outbound.Target.Address.Family().IsIP() { return []net.IP{ctx.Outbound.Target.Address.IP()} } return nil } // GetTargetPort implements routing.Context. func (ctx *Context) GetTargetPort() net.Port { if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { return 0 } return ctx.Outbound.Target.Port } // GetTargetDomain implements routing.Context. func (ctx *Context) GetTargetDomain() string { if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { return "" } dest := ctx.Outbound.Target if !dest.Address.Family().IsDomain() { return "" } return dest.Address.Domain() } // GetNetwork implements routing.Context. func (ctx *Context) GetNetwork() net.Network { if ctx.Outbound == nil { return net.Network_Unknown } return ctx.Outbound.Target.Network } // GetProtocol implements routing.Context. func (ctx *Context) GetProtocol() string { if ctx.Content == nil { return "" } return ctx.Content.Protocol } // GetUser implements routing.Context. func (ctx *Context) GetUser() string { if ctx.Inbound == nil || ctx.Inbound.User == nil { return "" } return ctx.Inbound.User.Email } // GetAttributes implements routing.Context. func (ctx *Context) GetAttributes() map[string]string { if ctx.Content == nil { return nil } return ctx.Content.Attributes } // AsRoutingContext creates a context from context.context with session info. func AsRoutingContext(ctx context.Context) routing.Context { return &Context{ Inbound: session.InboundFromContext(ctx), Outbound: session.OutboundFromContext(ctx), Content: session.ContentFromContext(ctx), } } ================================================ FILE: features/stats/errors.generated.go ================================================ package stats import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: features/stats/stats.go ================================================ package stats //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "v2ray.com/core/common" "v2ray.com/core/features" ) // Counter is the interface for stats counters. // // v2ray:api:stable type Counter interface { // Value is the current value of the counter. Value() int64 // Set sets a new value to the counter, and returns the previous one. Set(int64) int64 // Add adds a value to the current counter value, and returns the previous value. Add(int64) int64 } // Channel is the interface for stats channel. // // v2ray:api:stable type Channel interface { // Channel is a runnable unit. common.Runnable // Publish broadcasts a message through the channel with a controlling context. Publish(context.Context, interface{}) // SubscriberCount returns the number of the subscribers. Subscribers() []chan interface{} // Subscribe registers for listening to channel stream and returns a new listener channel. Subscribe() (chan interface{}, error) // Unsubscribe unregisters a listener channel from current Channel object. Unsubscribe(chan interface{}) error } // SubscribeRunnableChannel subscribes the channel and starts it if there is first subscriber coming. func SubscribeRunnableChannel(c Channel) (chan interface{}, error) { if len(c.Subscribers()) == 0 { if err := c.Start(); err != nil { return nil, err } } return c.Subscribe() } // UnsubscribeClosableChannel unsubcribes the channel and close it if there is no more subscriber. func UnsubscribeClosableChannel(c Channel, sub chan interface{}) error { if err := c.Unsubscribe(sub); err != nil { return err } if len(c.Subscribers()) == 0 { return c.Close() } return nil } // Manager is the interface for stats manager. // // v2ray:api:stable type Manager interface { features.Feature // RegisterCounter registers a new counter to the manager. The identifier string must not be empty, and unique among other counters. RegisterCounter(string) (Counter, error) // UnregisterCounter unregisters a counter from the manager by its identifier. UnregisterCounter(string) error // GetCounter returns a counter by its identifier. GetCounter(string) Counter // RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels. RegisterChannel(string) (Channel, error) // UnregisterCounter unregisters a channel from the manager by its identifier. UnregisterChannel(string) error // GetChannel returns a channel by its identifier. GetChannel(string) Channel } // GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter. func GetOrRegisterCounter(m Manager, name string) (Counter, error) { counter := m.GetCounter(name) if counter != nil { return counter, nil } return m.RegisterCounter(name) } // GetOrRegisterChannel tries to get the StatChannel first. If not exist, it then tries to create a new channel. func GetOrRegisterChannel(m Manager, name string) (Channel, error) { channel := m.GetChannel(name) if channel != nil { return channel, nil } return m.RegisterChannel(name) } // ManagerType returns the type of Manager interface. Can be used to implement common.HasType. // // v2ray:api:stable func ManagerType() interface{} { return (*Manager)(nil) } // NoopManager is an implementation of Manager, which doesn't has actual functionalities. type NoopManager struct{} // Type implements common.HasType. func (NoopManager) Type() interface{} { return ManagerType() } // RegisterCounter implements Manager. func (NoopManager) RegisterCounter(string) (Counter, error) { return nil, newError("not implemented") } // UnregisterCounter implements Manager. func (NoopManager) UnregisterCounter(string) error { return nil } // GetCounter implements Manager. func (NoopManager) GetCounter(string) Counter { return nil } // RegisterChannel implements Manager. func (NoopManager) RegisterChannel(string) (Channel, error) { return nil, newError("not implemented") } // UnregisterChannel implements Manager. func (NoopManager) UnregisterChannel(string) error { return nil } // GetChannel implements Manager. func (NoopManager) GetChannel(string) Channel { return nil } // Start implements common.Runnable. func (NoopManager) Start() error { return nil } // Close implements common.Closable. func (NoopManager) Close() error { return nil } ================================================ FILE: functions.go ================================================ // +build !confonly package core import ( "bytes" "context" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet/udp" ) // CreateObject creates a new object based on the given V2Ray instance and config. The V2Ray instance may be nil. func CreateObject(v *Instance, config interface{}) (interface{}, error) { ctx := v.ctx if v != nil { ctx = context.WithValue(ctx, v2rayKey, v) } return common.CreateObject(ctx, config) } // StartInstance starts a new V2Ray instance with given serialized config. // By default V2Ray only support config in protobuf format, i.e., configFormat = "protobuf". Caller need to load other packages to add JSON support. // // v2ray:api:stable func StartInstance(configFormat string, configBytes []byte) (*Instance, error) { config, err := LoadConfig(configFormat, "", bytes.NewReader(configBytes)) if err != nil { return nil, err } instance, err := New(config) if err != nil { return nil, err } if err := instance.Start(); err != nil { return nil, err } return instance, nil } // Dial provides an easy way for upstream caller to create net.Conn through V2Ray. // It dispatches the request to the given destination by the given V2Ray instance. // Since it is under a proxy context, the LocalAddr() and RemoteAddr() in returned net.Conn // will not show real addresses being used for communication. // // v2ray:api:stable func Dial(ctx context.Context, v *Instance, dest net.Destination) (net.Conn, error) { dispatcher := v.GetFeature(routing.DispatcherType()) if dispatcher == nil { return nil, newError("routing.Dispatcher is not registered in V2Ray core") } r, err := dispatcher.(routing.Dispatcher).Dispatch(ctx, dest) if err != nil { return nil, err } var readerOpt net.ConnectionOption if dest.Network == net.Network_TCP { readerOpt = net.ConnectionOutputMulti(r.Reader) } else { readerOpt = net.ConnectionOutputMultiUDP(r.Reader) } return net.NewConnection(net.ConnectionInputMulti(r.Writer), readerOpt), nil } // DialUDP provides a way to exchange UDP packets through V2Ray instance to remote servers. // Since it is under a proxy context, the LocalAddr() in returned PacketConn will not show the real address. // // TODO: SetDeadline() / SetReadDeadline() / SetWriteDeadline() are not implemented. // // v2ray:api:beta func DialUDP(ctx context.Context, v *Instance) (net.PacketConn, error) { dispatcher := v.GetFeature(routing.DispatcherType()) if dispatcher == nil { return nil, newError("routing.Dispatcher is not registered in V2Ray core") } return udp.DialDispatcher(ctx, dispatcher.(routing.Dispatcher)) } ================================================ FILE: functions_test.go ================================================ package core_test import ( "context" "crypto/rand" "io" "testing" "time" "github.com/golang/protobuf/proto" "github.com/google/go-cmp/cmp" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/freedom" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" ) func xor(b []byte) []byte { r := make([]byte, len(b)) for i, v := range b { r[i] = v ^ 'c' } return r } func xor2(b []byte) []byte { r := make([]byte, len(b)) for i, v := range b { r[i] = v ^ 'd' } return r } func TestV2RayDial(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } cfgBytes, err := proto.Marshal(config) common.Must(err) server, err := core.StartInstance("protobuf", cfgBytes) common.Must(err) defer server.Close() conn, err := core.Dial(context.Background(), server, dest) common.Must(err) defer conn.Close() const size = 10240 * 1024 payload := make([]byte, size) common.Must2(rand.Read(payload)) if _, err := conn.Write(payload); err != nil { t.Fatal(err) } receive := make([]byte, size) if _, err := io.ReadFull(conn, receive); err != nil { t.Fatal("failed to read all response: ", err) } if r := cmp.Diff(xor(receive), payload); r != "" { t.Error(r) } } func TestV2RayDialUDPConn(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } cfgBytes, err := proto.Marshal(config) common.Must(err) server, err := core.StartInstance("protobuf", cfgBytes) common.Must(err) defer server.Close() conn, err := core.Dial(context.Background(), server, dest) common.Must(err) defer conn.Close() const size = 1024 payload := make([]byte, size) common.Must2(rand.Read(payload)) for i := 0; i < 2; i++ { if _, err := conn.Write(payload); err != nil { t.Fatal(err) } } time.Sleep(time.Millisecond * 500) receive := make([]byte, size*2) for i := 0; i < 2; i++ { n, err := conn.Read(receive) if err != nil { t.Fatal("expect no error, but got ", err) } if n != size { t.Fatal("expect read size ", size, " but got ", n) } if r := cmp.Diff(xor(receive[:n]), payload); r != "" { t.Fatal(r) } } } func TestV2RayDialUDP(t *testing.T) { udpServer1 := udp.Server{ MsgProcessor: xor, } dest1, err := udpServer1.Start() common.Must(err) defer udpServer1.Close() udpServer2 := udp.Server{ MsgProcessor: xor2, } dest2, err := udpServer2.Start() common.Must(err) defer udpServer2.Close() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } cfgBytes, err := proto.Marshal(config) common.Must(err) server, err := core.StartInstance("protobuf", cfgBytes) common.Must(err) defer server.Close() conn, err := core.DialUDP(context.Background(), server) common.Must(err) defer conn.Close() const size = 1024 { payload := make([]byte, size) common.Must2(rand.Read(payload)) if _, err := conn.WriteTo(payload, &net.UDPAddr{ IP: dest1.Address.IP(), Port: int(dest1.Port), }); err != nil { t.Fatal(err) } receive := make([]byte, size) if _, _, err := conn.ReadFrom(receive); err != nil { t.Fatal(err) } if r := cmp.Diff(xor(receive), payload); r != "" { t.Error(r) } } { payload := make([]byte, size) common.Must2(rand.Read(payload)) if _, err := conn.WriteTo(payload, &net.UDPAddr{ IP: dest2.Address.IP(), Port: int(dest2.Port), }); err != nil { t.Fatal(err) } receive := make([]byte, size) if _, _, err := conn.ReadFrom(receive); err != nil { t.Fatal(err) } if r := cmp.Diff(xor2(receive), payload); r != "" { t.Error(r) } } } ================================================ FILE: go.mod ================================================ module v2ray.com/core go 1.15 require ( github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.2 github.com/google/go-cmp v0.5.3 github.com/gorilla/websocket v1.4.2 github.com/lucas-clemente/quic-go v0.18.1 github.com/miekg/dns v1.1.31 github.com/pires/go-proxyproto v0.2.0 github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841 github.com/stretchr/testify v1.8.1 github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf github.com/xtls/go v0.0.0-20201007031018-d42c13c57942 go.starlark.net v0.0.0-20201006213952-227f4aabceb5 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 golang.org/x/sync v0.0.0-20200930132711-30421366ff76 golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf google.golang.org/grpc v1.32.0 google.golang.org/protobuf v1.25.0 h12.io/socks v1.0.1 ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU= github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg= github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pires/go-proxyproto v0.2.0 h1:WyYKlv9pkt77b+LjMvPfwrsAxviaGCFhG4KDIy1ofLY= github.com/pires/go-proxyproto v0.2.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841 h1:pnfutQFsV7ySmHUeX6ANGfPsBo29RctUvDn8G3rmJVw= github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841/go.mod h1:ET5mVvNjwaGXRgZxO9UZr7X+8eAf87AfIYNwRSp9s4Y= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf h1:d4keT3SwLbrgnEe2zbtijPLgKE15n0ZbvJZzRH/a9GM= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf/go.mod h1:jTwBnzBuqZP3VX/Z65ErYb9zd4anQprSC7N38TmAp1E= github.com/xtls/go v0.0.0-20201007031018-d42c13c57942 h1:J+h5T5dtgVtszPN0vOFBzTapdVRZ16sXP6o6RPFNs20= github.com/xtls/go v0.0.0-20201007031018-d42c13c57942/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.starlark.net v0.0.0-20201006213952-227f4aabceb5 h1:ApvY/1gw+Yiqb/FKeks3KnVPWpkR3xzij82XPKLjJVw= go.starlark.net v0.0.0-20201006213952-227f4aabceb5/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200930132711-30421366ff76 h1:JnxiSYT3Nm0BT2a8CyvYyM6cnrWpidecD1UuSYbhKm0= golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf h1:Bg47KQy0JhTHuf4sLiQwTMKwUMfSDwgSGatrxGR7nLM= golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= h12.io/socks v1.0.1 h1:bXESSI/+hbdrp+22vcc7/JiXjmLH4UWktKdYgGr3ShA= h12.io/socks v1.0.1/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= ================================================ FILE: infra/bazel/BUILD ================================================ filegroup( name = "rules", srcs = glob(["*.bzl"]), visibility = ["//visibility:public"], ) ================================================ FILE: infra/bazel/build.bzl ================================================ def _go_command(ctx): output = ctx.attr.output if ctx.attr.os == "windows": output = output + ".exe" output_file = ctx.actions.declare_file(ctx.attr.os + "/" + ctx.attr.arch + "/" + ctx.attr.ver + "/" + output) pkg = ctx.attr.pkg ld_flags = "-s -w -buildid=" if ctx.attr.ld: ld_flags = ld_flags + " " + ctx.attr.ld options = [ "go", "build", "-trimpath", "-o", output_file.path, "-ldflags", "'%s'" % ld_flags, "-tags", "'%s'" % ctx.attr.gotags, pkg, ] command = " ".join(options) envs = [ "CGO_ENABLED=0", "GOOS="+ctx.attr.os, "GOARCH="+ctx.attr.arch, "GO111MODULE=on", "GOCACHE=${TMPDIR}/gocache" ] if ctx.attr.mips: # https://github.com/golang/go/issues/27260 envs+=["GOMIPS="+ctx.attr.mips] envs+=["GOMIPS64="+ctx.attr.mips] envs+=["GOMIPSLE="+ctx.attr.mips] envs+=["GOMIPS64LE="+ctx.attr.mips] if ctx.attr.arm: envs+=["GOARM="+ctx.attr.arm] switchToPwd="cd ${SPWD} && " command = switchToPwd + " ".join(envs) + " " + command ctx.actions.run_shell( outputs = [output_file], command = command, use_default_shell_env = True, ) runfiles = ctx.runfiles(files = [output_file]) return [DefaultInfo(executable = output_file, runfiles = runfiles)] foreign_go_binary = rule( _go_command, attrs = { 'pkg': attr.string(), 'output': attr.string(), 'os': attr.string(mandatory=True), 'arch': attr.string(mandatory=True), 'ver': attr.string(mandatory=True), 'mips': attr.string(), 'arm': attr.string(), 'ld': attr.string(), 'gotags': attr.string(), }, executable = True, ) ================================================ FILE: infra/bazel/matrix.bzl ================================================ SUPPORTED_MATRIX = [ ("windows", "amd64", "0"), ("windows", "386", "0"), ("windows", "arm", "7"), ("darwin", "amd64", "0"), ("linux", "amd64", "0"), ("linux", "386", "0"), ("linux", "arm64", "0"), ("linux", "arm", "7"), ("linux", "arm", "6"), ("linux", "arm", "5"), ("linux", "mips64", "0"), ("linux", "mips", "0"), ("linux", "mips64le", "0"), ("linux", "mipsle", "0"), ("linux", "ppc64", "0"), ("linux", "ppc64le", "0"), ("linux", "riscv64", "0"), ("linux", "s390x", "0"), ("freebsd", "amd64", "0"), ("freebsd", "386", "0"), ("openbsd", "amd64", "0"), ("openbsd", "386", "0"), ("dragonfly", "amd64", "0"), ] ================================================ FILE: infra/bazel/zip.bzl ================================================ # Copied from google/nomulus project as we don't want to import the whole repository. ZIPPER = "@bazel_tools//tools/zip:zipper" def long_path(ctx, file_): """Constructs canonical runfile path relative to TEST_SRCDIR. Args: ctx: A Skylark rule context. file_: A File object that should appear in the runfiles for the test. Returns: A string path relative to TEST_SRCDIR suitable for use in tests and testing infrastructure. """ if file_.short_path.startswith("../"): return file_.short_path[3:] if file_.owner and file_.owner.workspace_root: return file_.owner.workspace_root + "/" + file_.short_path return ctx.workspace_name + "/" + file_.short_path def collect_runfiles(targets): """Aggregates runfiles from targets. Args: targets: A list of Bazel targets. Returns: A list of Bazel files. """ data = depset() for target in targets: if hasattr(target, "runfiles"): data += target.runfiles.files continue if hasattr(target, "data_runfiles"): data += target.data_runfiles.files if hasattr(target, "default_runfiles"): data += target.default_runfiles.files return data def _get_runfiles(target, attribute): runfiles = getattr(target, attribute, None) if runfiles: return runfiles.files return [] def _zip_file(ctx): """Implementation of zip_file() rule.""" for s, d in ctx.attr.mappings.items(): if (s.startswith("/") or s.endswith("/") or d.startswith("/") or d.endswith("/")): fail("mappings should not begin or end with slash") srcs = depset(transitive = [depset(ctx.files.srcs),depset(ctx.files.data),depset(collect_runfiles(ctx.attr.data))]) mapped = _map_sources(ctx, srcs, ctx.attr.mappings) cmd = [ "#!/bin/sh", "set -e", 'repo="$(pwd)"', 'zipper="${repo}/%s"' % ctx.file._zipper.path, 'archive="${repo}/%s"' % ctx.outputs.out.path, 'tmp="$(mktemp -d "${TMPDIR:-/tmp}/zip_file.XXXXXXXXXX")"', 'cd "${tmp}"', ] cmd += [ '"${zipper}" x "${repo}/%s"' % dep.zip_file.path for dep in ctx.attr.deps ] cmd += ["rm %s" % filename for filename in ctx.attr.exclude] cmd += [ 'mkdir -p "${tmp}/%s"' % zip_path for zip_path in depset( [ zip_path[:zip_path.rindex("/")] for _, zip_path in mapped if "/" in zip_path ], ).to_list() ] cmd += [ 'ln -sf "${repo}/%s" "${tmp}/%s"' % (path, zip_path) for path, zip_path in mapped ] cmd += [ ("find . | sed 1d | cut -c 3- | LC_ALL=C sort" + ' | xargs "${zipper}" cC "${archive}"'), 'cd "${repo}"', 'rm -rf "${tmp}"', ] script = ctx.actions.declare_file("%s/%s.sh" % (ctx.bin_dir, ctx.label.name)) ctx.actions.write(output = script, content = "\n".join(cmd), is_executable = True) inputs = [ctx.file._zipper] inputs += [dep.zip_file for dep in ctx.attr.deps] inputs += list(srcs.to_list()) ctx.actions.run( inputs = inputs, outputs = [ctx.outputs.out], executable = script, mnemonic = "zip", progress_message = "Creating zip with %d inputs %s" % ( len(inputs), ctx.label, ), ) return struct(files = depset([ctx.outputs.out]), zip_file = ctx.outputs.out) def _map_sources(ctx, srcs, mappings): """Calculates paths in zip file for srcs.""" # order mappings with more path components first mappings = sorted([ (-len(source.split("/")), source, dest) for source, dest in mappings.items() ]) # get rid of the integer part of tuple used for sorting mappings = [(source, dest) for _, source, dest in mappings] mappings_indexes = range(len(mappings)) used = {i: False for i in mappings_indexes} mapped = [] for file_ in srcs.to_list(): run_path = long_path(ctx, file_) zip_path = None for i in mappings_indexes: source = mappings[i][0] dest = mappings[i][1] if not source: if dest: zip_path = dest + "/" + run_path else: zip_path = run_path elif source == run_path: if dest: zip_path = dest else: zip_path = run_path elif run_path.startswith(source + "/"): if dest: zip_path = dest + run_path[len(source):] else: zip_path = run_path[len(source) + 1:] else: continue used[i] = True break if not zip_path: fail("no mapping matched: " + run_path) mapped += [(file_.path, zip_path)] for i in mappings_indexes: if not used[i]: fail('superfluous mapping: "%s" -> "%s"' % mappings[i]) return mapped pkg_zip = rule( implementation = _zip_file, attrs = { "out": attr.output(mandatory = True), "srcs": attr.label_list(allow_files = True), "data": attr.label_list(allow_files = True), "deps": attr.label_list(providers = ["zip_file"]), "exclude": attr.string_list(), "mappings": attr.string_dict(), "_zipper": attr.label(default = Label(ZIPPER), allow_single_file = True), }, ) ================================================ FILE: infra/conf/api.go ================================================ package conf import ( "strings" "v2ray.com/core/app/commander" loggerservice "v2ray.com/core/app/log/command" handlerservice "v2ray.com/core/app/proxyman/command" statsservice "v2ray.com/core/app/stats/command" "v2ray.com/core/common/serial" ) type ApiConfig struct { Tag string `json:"tag"` Services []string `json:"services"` } func (c *ApiConfig) Build() (*commander.Config, error) { if c.Tag == "" { return nil, newError("Api tag can't be empty.") } services := make([]*serial.TypedMessage, 0, 16) for _, s := range c.Services { switch strings.ToLower(s) { case "handlerservice": services = append(services, serial.ToTypedMessage(&handlerservice.Config{})) case "loggerservice": services = append(services, serial.ToTypedMessage(&loggerservice.Config{})) case "statsservice": services = append(services, serial.ToTypedMessage(&statsservice.Config{})) } } return &commander.Config{ Tag: c.Tag, Service: services, }, nil } ================================================ FILE: infra/conf/blackhole.go ================================================ package conf import ( "encoding/json" "github.com/golang/protobuf/proto" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/blackhole" ) type NoneResponse struct{} func (*NoneResponse) Build() (proto.Message, error) { return new(blackhole.NoneResponse), nil } type HttpResponse struct{} func (*HttpResponse) Build() (proto.Message, error) { return new(blackhole.HTTPResponse), nil } type BlackholeConfig struct { Response json.RawMessage `json:"response"` } func (v *BlackholeConfig) Build() (proto.Message, error) { config := new(blackhole.Config) if v.Response != nil { response, _, err := configLoader.Load(v.Response) if err != nil { return nil, newError("Config: Failed to parse Blackhole response config.").Base(err) } responseSettings, err := response.(Buildable).Build() if err != nil { return nil, err } config.Response = serial.ToTypedMessage(responseSettings) } return config, nil } var ( configLoader = NewJSONConfigLoader( ConfigCreatorCache{ "none": func() interface{} { return new(NoneResponse) }, "http": func() interface{} { return new(HttpResponse) }, }, "type", "") ) ================================================ FILE: infra/conf/blackhole_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/blackhole" ) func TestHTTPResponseJSON(t *testing.T) { creator := func() Buildable { return new(BlackholeConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "response": { "type": "http" } }`, Parser: loadJSON(creator), Output: &blackhole.Config{ Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}), }, }, { Input: `{}`, Parser: loadJSON(creator), Output: &blackhole.Config{}, }, }) } ================================================ FILE: infra/conf/buildable.go ================================================ package conf import "github.com/golang/protobuf/proto" type Buildable interface { Build() (proto.Message, error) } ================================================ FILE: infra/conf/command/command.go ================================================ package command //go:generate go run v2ray.com/core/common/errors/errorgen import ( "os" "github.com/golang/protobuf/proto" "v2ray.com/core/common" "v2ray.com/core/infra/conf/serial" "v2ray.com/core/infra/control" ) type ConfigCommand struct{} func (c *ConfigCommand) Name() string { return "config" } func (c *ConfigCommand) Description() control.Description { return control.Description{ Short: "Convert config among different formats.", Usage: []string{ "v2ctl config", }, } } func (c *ConfigCommand) Execute(args []string) error { pbConfig, err := serial.LoadJSONConfig(os.Stdin) if err != nil { return newError("failed to parse json config").Base(err) } bytesConfig, err := proto.Marshal(pbConfig) if err != nil { return newError("failed to marshal proto config").Base(err) } if _, err := os.Stdout.Write(bytesConfig); err != nil { return newError("failed to write proto config").Base(err) } return nil } func init() { common.Must(control.RegisterCommand(&ConfigCommand{})) } ================================================ FILE: infra/conf/command/errors.generated.go ================================================ package command import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: infra/conf/common.go ================================================ package conf import ( "encoding/json" "os" "strings" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) type StringList []string func NewStringList(raw []string) *StringList { list := StringList(raw) return &list } func (v StringList) Len() int { return len(v) } func (v *StringList) UnmarshalJSON(data []byte) error { var strarray []string if err := json.Unmarshal(data, &strarray); err == nil { *v = *NewStringList(strarray) return nil } var rawstr string if err := json.Unmarshal(data, &rawstr); err == nil { strlist := strings.Split(rawstr, ",") *v = *NewStringList(strlist) return nil } return newError("unknown format of a string list: " + string(data)) } type Address struct { net.Address } func (v *Address) UnmarshalJSON(data []byte) error { var rawStr string if err := json.Unmarshal(data, &rawStr); err != nil { return newError("invalid address: ", string(data)).Base(err) } v.Address = net.ParseAddress(rawStr) return nil } func (v *Address) Build() *net.IPOrDomain { return net.NewIPOrDomain(v.Address) } type Network string func (v Network) Build() net.Network { switch strings.ToLower(string(v)) { case "tcp": return net.Network_TCP case "udp": return net.Network_UDP default: return net.Network_Unknown } } type NetworkList []Network func (v *NetworkList) UnmarshalJSON(data []byte) error { var strarray []Network if err := json.Unmarshal(data, &strarray); err == nil { nl := NetworkList(strarray) *v = nl return nil } var rawstr Network if err := json.Unmarshal(data, &rawstr); err == nil { strlist := strings.Split(string(rawstr), ",") nl := make([]Network, len(strlist)) for idx, network := range strlist { nl[idx] = Network(network) } *v = nl return nil } return newError("unknown format of a string list: " + string(data)) } func (v *NetworkList) Build() []net.Network { if v == nil { return []net.Network{net.Network_TCP} } list := make([]net.Network, 0, len(*v)) for _, network := range *v { list = append(list, network.Build()) } return list } func parseIntPort(data []byte) (net.Port, error) { var intPort uint32 err := json.Unmarshal(data, &intPort) if err != nil { return net.Port(0), err } return net.PortFromInt(intPort) } func parseStringPort(s string) (net.Port, net.Port, error) { if strings.HasPrefix(s, "env:") { s = s[4:] s = os.Getenv(s) } pair := strings.SplitN(s, "-", 2) if len(pair) == 0 { return net.Port(0), net.Port(0), newError("invalid port range: ", s) } if len(pair) == 1 { port, err := net.PortFromString(pair[0]) return port, port, err } fromPort, err := net.PortFromString(pair[0]) if err != nil { return net.Port(0), net.Port(0), err } toPort, err := net.PortFromString(pair[1]) if err != nil { return net.Port(0), net.Port(0), err } return fromPort, toPort, nil } func parseJSONStringPort(data []byte) (net.Port, net.Port, error) { var s string err := json.Unmarshal(data, &s) if err != nil { return net.Port(0), net.Port(0), err } return parseStringPort(s) } type PortRange struct { From uint32 To uint32 } func (v *PortRange) Build() *net.PortRange { return &net.PortRange{ From: v.From, To: v.To, } } // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON func (v *PortRange) UnmarshalJSON(data []byte) error { port, err := parseIntPort(data) if err == nil { v.From = uint32(port) v.To = uint32(port) return nil } from, to, err := parseJSONStringPort(data) if err == nil { v.From = uint32(from) v.To = uint32(to) if v.From > v.To { return newError("invalid port range ", v.From, " -> ", v.To) } return nil } return newError("invalid port range: ", string(data)) } type PortList struct { Range []PortRange } func (list *PortList) Build() *net.PortList { portList := new(net.PortList) for _, r := range list.Range { portList.Range = append(portList.Range, r.Build()) } return portList } // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON func (list *PortList) UnmarshalJSON(data []byte) error { var listStr string var number uint32 if err := json.Unmarshal(data, &listStr); err != nil { if err2 := json.Unmarshal(data, &number); err2 != nil { return newError("invalid port: ", string(data)).Base(err2) } } rangelist := strings.Split(listStr, ",") for _, rangeStr := range rangelist { trimmed := strings.TrimSpace(rangeStr) if len(trimmed) > 0 { if strings.Contains(trimmed, "-") { from, to, err := parseStringPort(trimmed) if err != nil { return newError("invalid port range: ", trimmed).Base(err) } list.Range = append(list.Range, PortRange{From: uint32(from), To: uint32(to)}) } else { port, err := parseIntPort([]byte(trimmed)) if err != nil { return newError("invalid port: ", trimmed).Base(err) } list.Range = append(list.Range, PortRange{From: uint32(port), To: uint32(port)}) } } } if number != 0 { list.Range = append(list.Range, PortRange{From: uint32(number), To: uint32(number)}) } return nil } type User struct { EmailString string `json:"email"` LevelByte byte `json:"level"` } func (v *User) Build() *protocol.User { return &protocol.User{ Email: v.EmailString, Level: uint32(v.LevelByte), } } ================================================ FILE: infra/conf/common_test.go ================================================ package conf_test import ( "encoding/json" "github.com/google/go-cmp/cmp/cmpopts" "os" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common/protocol" "v2ray.com/core/common" "v2ray.com/core/common/net" . "v2ray.com/core/infra/conf" ) func TestStringListUnmarshalError(t *testing.T) { rawJson := `1234` list := new(StringList) err := json.Unmarshal([]byte(rawJson), list) if err == nil { t.Error("expected error, but got nil") } } func TestStringListLen(t *testing.T) { rawJson := `"a, b, c, d"` var list StringList err := json.Unmarshal([]byte(rawJson), &list) common.Must(err) if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" { t.Error(r) } } func TestIPParsing(t *testing.T) { rawJson := "\"8.8.8.8\"" var address Address err := json.Unmarshal([]byte(rawJson), &address) common.Must(err) if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" { t.Error(r) } } func TestDomainParsing(t *testing.T) { rawJson := "\"v2ray.com\"" var address Address common.Must(json.Unmarshal([]byte(rawJson), &address)) if address.Domain() != "v2ray.com" { t.Error("domain: ", address.Domain()) } } func TestURLParsing(t *testing.T) { { rawJson := "\"https://dns.google/dns-query\"" var address Address common.Must(json.Unmarshal([]byte(rawJson), &address)) if address.Domain() != "https://dns.google/dns-query" { t.Error("URL: ", address.Domain()) } } { rawJson := "\"https+local://dns.google/dns-query\"" var address Address common.Must(json.Unmarshal([]byte(rawJson), &address)) if address.Domain() != "https+local://dns.google/dns-query" { t.Error("URL: ", address.Domain()) } } } func TestInvalidAddressJson(t *testing.T) { rawJson := "1234" var address Address err := json.Unmarshal([]byte(rawJson), &address) if err == nil { t.Error("nil error") } } func TestStringNetwork(t *testing.T) { var network Network common.Must(json.Unmarshal([]byte(`"tcp"`), &network)) if v := network.Build(); v != net.Network_TCP { t.Error("network: ", v) } } func TestArrayNetworkList(t *testing.T) { var list NetworkList common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list)) nlist := list.Build() if !net.HasNetwork(nlist, net.Network_TCP) { t.Error("no tcp network") } if net.HasNetwork(nlist, net.Network_UDP) { t.Error("has udp network") } } func TestStringNetworkList(t *testing.T) { var list NetworkList common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list)) nlist := list.Build() if !net.HasNetwork(nlist, net.Network_TCP) { t.Error("no tcp network") } if net.HasNetwork(nlist, net.Network_UDP) { t.Error("has udp network") } } func TestInvalidNetworkJson(t *testing.T) { var list NetworkList err := json.Unmarshal([]byte("0"), &list) if err == nil { t.Error("nil error") } } func TestIntPort(t *testing.T) { var portRange PortRange common.Must(json.Unmarshal([]byte("1234"), &portRange)) if r := cmp.Diff(portRange, PortRange{ From: 1234, To: 1234, }); r != "" { t.Error(r) } } func TestOverRangeIntPort(t *testing.T) { var portRange PortRange err := json.Unmarshal([]byte("70000"), &portRange) if err == nil { t.Error("nil error") } err = json.Unmarshal([]byte("-1"), &portRange) if err == nil { t.Error("nil error") } } func TestEnvPort(t *testing.T) { common.Must(os.Setenv("PORT", "1234")) var portRange PortRange common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange)) if r := cmp.Diff(portRange, PortRange{ From: 1234, To: 1234, }); r != "" { t.Error(r) } } func TestSingleStringPort(t *testing.T) { var portRange PortRange common.Must(json.Unmarshal([]byte("\"1234\""), &portRange)) if r := cmp.Diff(portRange, PortRange{ From: 1234, To: 1234, }); r != "" { t.Error(r) } } func TestStringPairPort(t *testing.T) { var portRange PortRange common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange)) if r := cmp.Diff(portRange, PortRange{ From: 1234, To: 5678, }); r != "" { t.Error(r) } } func TestOverRangeStringPort(t *testing.T) { var portRange PortRange err := json.Unmarshal([]byte("\"65536\""), &portRange) if err == nil { t.Error("nil error") } err = json.Unmarshal([]byte("\"70000-80000\""), &portRange) if err == nil { t.Error("nil error") } err = json.Unmarshal([]byte("\"1-90000\""), &portRange) if err == nil { t.Error("nil error") } err = json.Unmarshal([]byte("\"700-600\""), &portRange) if err == nil { t.Error("nil error") } } func TestUserParsing(t *testing.T) { user := new(User) common.Must(json.Unmarshal([]byte(`{ "id": "96edb838-6d68-42ef-a933-25f7ac3a9d09", "email": "love@v2ray.com", "level": 1, "alterId": 100 }`), user)) nUser := user.Build() if r := cmp.Diff(nUser, &protocol.User{ Level: 1, Email: "love@v2ray.com", }, cmpopts.IgnoreUnexported(protocol.User{})); r != "" { t.Error(r) } } func TestInvalidUserJson(t *testing.T) { user := new(User) err := json.Unmarshal([]byte(`{"email": 1234}`), user) if err == nil { t.Error("nil error") } } ================================================ FILE: infra/conf/conf.go ================================================ package conf //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: infra/conf/dns.go ================================================ package conf import ( "encoding/json" "sort" "strings" "v2ray.com/core/app/dns" "v2ray.com/core/app/router" "v2ray.com/core/common/net" ) type NameServerConfig struct { Address *Address Port uint16 Domains []string ExpectIPs StringList } func (c *NameServerConfig) UnmarshalJSON(data []byte) error { var address Address if err := json.Unmarshal(data, &address); err == nil { c.Address = &address return nil } var advanced struct { Address *Address `json:"address"` Port uint16 `json:"port"` Domains []string `json:"domains"` ExpectIPs StringList `json:"expectIps"` } if err := json.Unmarshal(data, &advanced); err == nil { c.Address = advanced.Address c.Port = advanced.Port c.Domains = advanced.Domains c.ExpectIPs = advanced.ExpectIPs return nil } return newError("failed to parse name server: ", string(data)) } func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType { switch t { case router.Domain_Domain: return dns.DomainMatchingType_Subdomain case router.Domain_Full: return dns.DomainMatchingType_Full case router.Domain_Plain: return dns.DomainMatchingType_Keyword case router.Domain_Regex: return dns.DomainMatchingType_Regex default: panic("unknown domain type") } } func (c *NameServerConfig) Build() (*dns.NameServer, error) { if c.Address == nil { return nil, newError("NameServer address is not specified.") } var domains []*dns.NameServer_PriorityDomain var originalRules []*dns.NameServer_OriginalRule for _, rule := range c.Domains { parsedDomain, err := parseDomainRule(rule) if err != nil { return nil, newError("invalid domain rule: ", rule).Base(err) } for _, pd := range parsedDomain { domains = append(domains, &dns.NameServer_PriorityDomain{ Type: toDomainMatchingType(pd.Type), Domain: pd.Value, }) } originalRules = append(originalRules, &dns.NameServer_OriginalRule{ Rule: rule, Size: uint32(len(parsedDomain)), }) } geoipList, err := toCidrList(c.ExpectIPs) if err != nil { return nil, newError("invalid ip rule: ", c.ExpectIPs).Base(err) } return &dns.NameServer{ Address: &net.Endpoint{ Network: net.Network_UDP, Address: c.Address.Build(), Port: uint32(c.Port), }, PrioritizedDomain: domains, Geoip: geoipList, OriginalRules: originalRules, }, nil } var typeMap = map[router.Domain_Type]dns.DomainMatchingType{ router.Domain_Full: dns.DomainMatchingType_Full, router.Domain_Domain: dns.DomainMatchingType_Subdomain, router.Domain_Plain: dns.DomainMatchingType_Keyword, router.Domain_Regex: dns.DomainMatchingType_Regex, } // DnsConfig is a JSON serializable object for dns.Config. type DnsConfig struct { Servers []*NameServerConfig `json:"servers"` Hosts map[string]*Address `json:"hosts"` ClientIP *Address `json:"clientIp"` Tag string `json:"tag"` } func getHostMapping(addr *Address) *dns.Config_HostMapping { if addr.Family().IsIP() { return &dns.Config_HostMapping{ Ip: [][]byte{[]byte(addr.IP())}, } } else { return &dns.Config_HostMapping{ ProxiedDomain: addr.Domain(), } } } // Build implements Buildable func (c *DnsConfig) Build() (*dns.Config, error) { config := &dns.Config{ Tag: c.Tag, } if c.ClientIP != nil { if !c.ClientIP.Family().IsIP() { return nil, newError("not an IP address:", c.ClientIP.String()) } config.ClientIp = []byte(c.ClientIP.IP()) } for _, server := range c.Servers { ns, err := server.Build() if err != nil { return nil, newError("failed to build name server").Base(err) } config.NameServer = append(config.NameServer, ns) } if c.Hosts != nil && len(c.Hosts) > 0 { domains := make([]string, 0, len(c.Hosts)) for domain := range c.Hosts { domains = append(domains, domain) } sort.Strings(domains) for _, domain := range domains { addr := c.Hosts[domain] var mappings []*dns.Config_HostMapping if strings.HasPrefix(domain, "domain:") { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Subdomain mapping.Domain = domain[7:] mappings = append(mappings, mapping) } else if strings.HasPrefix(domain, "geosite:") { domains, err := loadGeositeWithAttr("geosite.dat", strings.ToUpper(domain[8:])) if err != nil { return nil, newError("invalid geosite settings: ", domain).Base(err) } for _, d := range domains { mapping := getHostMapping(addr) mapping.Type = typeMap[d.Type] mapping.Domain = d.Value mappings = append(mappings, mapping) } } else if strings.HasPrefix(domain, "regexp:") { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Regex mapping.Domain = domain[7:] mappings = append(mappings, mapping) } else if strings.HasPrefix(domain, "keyword:") { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Keyword mapping.Domain = domain[8:] mappings = append(mappings, mapping) } else if strings.HasPrefix(domain, "full:") { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Full mapping.Domain = domain[5:] mappings = append(mappings, mapping) } else if strings.HasPrefix(domain, "dotless:") { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Regex switch substr := domain[8:]; { case substr == "": mapping.Domain = "^[^.]*$" case !strings.Contains(substr, "."): mapping.Domain = "^[^.]*" + substr + "[^.]*$" default: return nil, newError("substr in dotless rule should not contain a dot: ", substr) } mappings = append(mappings, mapping) } else if strings.HasPrefix(domain, "ext:") { kv := strings.Split(domain[4:], ":") if len(kv) != 2 { return nil, newError("invalid external resource: ", domain) } filename := kv[0] country := kv[1] domains, err := loadGeositeWithAttr(filename, country) if err != nil { return nil, newError("failed to load domains: ", country, " from ", filename).Base(err) } for _, d := range domains { mapping := getHostMapping(addr) mapping.Type = typeMap[d.Type] mapping.Domain = d.Value mappings = append(mappings, mapping) } } else { mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Full mapping.Domain = domain mappings = append(mappings, mapping) } config.StaticHosts = append(config.StaticHosts, mappings...) } } return config, nil } ================================================ FILE: infra/conf/dns_proxy.go ================================================ package conf import ( "github.com/golang/protobuf/proto" "v2ray.com/core/common/net" "v2ray.com/core/proxy/dns" ) type DnsOutboundConfig struct { Network Network `json:"network"` Address *Address `json:"address"` Port uint16 `json:"port"` } func (c *DnsOutboundConfig) Build() (proto.Message, error) { config := &dns.Config{ Server: &net.Endpoint{ Network: c.Network.Build(), Port: uint32(c.Port), }, } if c.Address != nil { config.Server.Address = c.Address.Build() } return config, nil } ================================================ FILE: infra/conf/dns_proxy_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/dns" ) func TestDnsProxyConfig(t *testing.T) { creator := func() Buildable { return new(DnsOutboundConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "address": "8.8.8.8", "port": 53, "network": "tcp" }`, Parser: loadJSON(creator), Output: &dns.Config{ Server: &net.Endpoint{ Network: net.Network_TCP, Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})), Port: 53, }, }, }, }) } ================================================ FILE: infra/conf/dns_test.go ================================================ package conf_test import ( "encoding/json" "os" "path/filepath" "testing" "github.com/golang/protobuf/proto" "v2ray.com/core/app/dns" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/platform/filesystem" . "v2ray.com/core/infra/conf" ) func init() { wd, err := os.Getwd() common.Must(err) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) } geositeFilePath := filepath.Join(wd, "geosite.dat") os.Setenv("v2ray.location.asset", wd) geositeFile, err := os.OpenFile(geositeFilePath, os.O_CREATE|os.O_WRONLY, 0600) common.Must(err) defer geositeFile.Close() list := &router.GeoSiteList{ Entry: []*router.GeoSite{ { CountryCode: "TEST", Domain: []*router.Domain{ {Type: router.Domain_Full, Value: "example.com"}, }, }, }, } listBytes, err := proto.Marshal(list) common.Must(err) common.Must2(geositeFile.Write(listBytes)) } func TestDnsConfigParsing(t *testing.T) { geositePath := platform.GetAssetLocation("geosite.dat") defer func() { os.Remove(geositePath) os.Unsetenv("v2ray.location.asset") }() parserCreator := func() func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { config := new(DnsConfig) if err := json.Unmarshal([]byte(s), config); err != nil { return nil, err } return config.Build() } } runMultiTestCase(t, []TestCase{ { Input: `{ "servers": [{ "address": "8.8.8.8", "port": 5353, "domains": ["domain:v2ray.com"] }], "hosts": { "v2ray.com": "127.0.0.1", "domain:example.com": "google.com", "geosite:test": "10.0.0.1", "keyword:google": "8.8.8.8", "regexp:.*\\.com": "8.8.4.4" }, "clientIp": "10.0.0.1" }`, Parser: parserCreator(), Output: &dns.Config{ NameServer: []*dns.NameServer{ { Address: &net.Endpoint{ Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{8, 8, 8, 8}, }, }, Network: net.Network_UDP, Port: 5353, }, PrioritizedDomain: []*dns.NameServer_PriorityDomain{ { Type: dns.DomainMatchingType_Subdomain, Domain: "v2ray.com", }, }, OriginalRules: []*dns.NameServer_OriginalRule{ { Rule: "domain:v2ray.com", Size: 1, }, }, }, }, StaticHosts: []*dns.Config_HostMapping{ { Type: dns.DomainMatchingType_Subdomain, Domain: "example.com", ProxiedDomain: "google.com", }, { Type: dns.DomainMatchingType_Full, Domain: "example.com", Ip: [][]byte{{10, 0, 0, 1}}, }, { Type: dns.DomainMatchingType_Keyword, Domain: "google", Ip: [][]byte{{8, 8, 8, 8}}, }, { Type: dns.DomainMatchingType_Regex, Domain: ".*\\.com", Ip: [][]byte{{8, 8, 4, 4}}, }, { Type: dns.DomainMatchingType_Full, Domain: "v2ray.com", Ip: [][]byte{{127, 0, 0, 1}}, }, }, ClientIp: []byte{10, 0, 0, 1}, }, }, }) } ================================================ FILE: infra/conf/dokodemo.go ================================================ package conf import ( "github.com/golang/protobuf/proto" "v2ray.com/core/proxy/dokodemo" ) type DokodemoConfig struct { Host *Address `json:"address"` PortValue uint16 `json:"port"` NetworkList *NetworkList `json:"network"` TimeoutValue uint32 `json:"timeout"` Redirect bool `json:"followRedirect"` UserLevel uint32 `json:"userLevel"` } func (v *DokodemoConfig) Build() (proto.Message, error) { config := new(dokodemo.Config) if v.Host != nil { config.Address = v.Host.Build() } config.Port = uint32(v.PortValue) config.Networks = v.NetworkList.Build() config.Timeout = v.TimeoutValue config.FollowRedirect = v.Redirect config.UserLevel = v.UserLevel return config, nil } ================================================ FILE: infra/conf/dokodemo_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/dokodemo" ) func TestDokodemoConfig(t *testing.T) { creator := func() Buildable { return new(DokodemoConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "address": "8.8.8.8", "port": 53, "network": "tcp", "timeout": 10, "followRedirect": true, "userLevel": 1 }`, Parser: loadJSON(creator), Output: &dokodemo.Config{ Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{8, 8, 8, 8}, }, }, Port: 53, Networks: []net.Network{net.Network_TCP}, Timeout: 10, FollowRedirect: true, UserLevel: 1, }, }, }) } ================================================ FILE: infra/conf/errors.generated.go ================================================ package conf import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: infra/conf/freedom.go ================================================ package conf import ( "net" "strings" "github.com/golang/protobuf/proto" v2net "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/proxy/freedom" ) type FreedomConfig struct { DomainStrategy string `json:"domainStrategy"` Timeout *uint32 `json:"timeout"` Redirect string `json:"redirect"` UserLevel uint32 `json:"userLevel"` } // Build implements Buildable func (c *FreedomConfig) Build() (proto.Message, error) { config := new(freedom.Config) config.DomainStrategy = freedom.Config_AS_IS switch strings.ToLower(c.DomainStrategy) { case "useip", "use_ip": config.DomainStrategy = freedom.Config_USE_IP case "useip4", "useipv4", "use_ipv4", "use_ip_v4", "use_ip4": config.DomainStrategy = freedom.Config_USE_IP4 case "useip6", "useipv6", "use_ipv6", "use_ip_v6", "use_ip6": config.DomainStrategy = freedom.Config_USE_IP6 } if c.Timeout != nil { config.Timeout = *c.Timeout } config.UserLevel = c.UserLevel if len(c.Redirect) > 0 { host, portStr, err := net.SplitHostPort(c.Redirect) if err != nil { return nil, newError("invalid redirect address: ", c.Redirect, ": ", err).Base(err) } port, err := v2net.PortFromString(portStr) if err != nil { return nil, newError("invalid redirect port: ", c.Redirect, ": ", err).Base(err) } config.DestinationOverride = &freedom.DestinationOverride{ Server: &protocol.ServerEndpoint{ Port: uint32(port), }, } if len(host) > 0 { config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host)) } } return config, nil } ================================================ FILE: infra/conf/freedom_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/freedom" ) func TestFreedomConfig(t *testing.T) { creator := func() Buildable { return new(FreedomConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "domainStrategy": "AsIs", "timeout": 10, "redirect": "127.0.0.1:3366", "userLevel": 1 }`, Parser: loadJSON(creator), Output: &freedom.Config{ DomainStrategy: freedom.Config_AS_IS, Timeout: 10, DestinationOverride: &freedom.DestinationOverride{ Server: &protocol.ServerEndpoint{ Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 3366, }, }, UserLevel: 1, }, }, }) } ================================================ FILE: infra/conf/general_test.go ================================================ package conf_test import ( "encoding/json" "testing" "github.com/golang/protobuf/proto" "v2ray.com/core/common" . "v2ray.com/core/infra/conf" ) func loadJSON(creator func() Buildable) func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { instance := creator() if err := json.Unmarshal([]byte(s), instance); err != nil { return nil, err } return instance.Build() } } type TestCase struct { Input string Parser func(string) (proto.Message, error) Output proto.Message } func runMultiTestCase(t *testing.T, testCases []TestCase) { for _, testCase := range testCases { actual, err := testCase.Parser(testCase.Input) common.Must(err) if !proto.Equal(actual, testCase.Output) { t.Fatalf("Failed in test case:\n%s\nActual:\n%v\nExpected:\n%v", testCase.Input, actual, testCase.Output) } } } ================================================ FILE: infra/conf/http.go ================================================ package conf import ( "encoding/json" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/http" ) type HttpAccount struct { Username string `json:"user"` Password string `json:"pass"` } func (v *HttpAccount) Build() *http.Account { return &http.Account{ Username: v.Username, Password: v.Password, } } type HttpServerConfig struct { Timeout uint32 `json:"timeout"` Accounts []*HttpAccount `json:"accounts"` Transparent bool `json:"allowTransparent"` UserLevel uint32 `json:"userLevel"` } func (c *HttpServerConfig) Build() (proto.Message, error) { config := &http.ServerConfig{ Timeout: c.Timeout, AllowTransparent: c.Transparent, UserLevel: c.UserLevel, } if len(c.Accounts) > 0 { config.Accounts = make(map[string]string) for _, account := range c.Accounts { config.Accounts[account.Username] = account.Password } } return config, nil } type HttpRemoteConfig struct { Address *Address `json:"address"` Port uint16 `json:"port"` Users []json.RawMessage `json:"users"` } type HttpClientConfig struct { Servers []*HttpRemoteConfig `json:"servers"` } func (v *HttpClientConfig) Build() (proto.Message, error) { config := new(http.ClientConfig) config.Server = make([]*protocol.ServerEndpoint, len(v.Servers)) for idx, serverConfig := range v.Servers { server := &protocol.ServerEndpoint{ Address: serverConfig.Address.Build(), Port: uint32(serverConfig.Port), } for _, rawUser := range serverConfig.Users { user := new(protocol.User) if err := json.Unmarshal(rawUser, user); err != nil { return nil, newError("failed to parse HTTP user").Base(err).AtError() } account := new(HttpAccount) if err := json.Unmarshal(rawUser, account); err != nil { return nil, newError("failed to parse HTTP account").Base(err).AtError() } user.Account = serial.ToTypedMessage(account.Build()) server.User = append(server.User, user) } config.Server[idx] = server } return config, nil } ================================================ FILE: infra/conf/http_test.go ================================================ package conf_test import ( "testing" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/http" ) func TestHttpServerConfig(t *testing.T) { creator := func() Buildable { return new(HttpServerConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "timeout": 10, "accounts": [ { "user": "my-username", "pass": "my-password" } ], "allowTransparent": true, "userLevel": 1 }`, Parser: loadJSON(creator), Output: &http.ServerConfig{ Accounts: map[string]string{ "my-username": "my-password", }, AllowTransparent: true, UserLevel: 1, Timeout: 10, }, }, }) } ================================================ FILE: infra/conf/json/reader.go ================================================ package json import ( "io" "v2ray.com/core/common/buf" ) // State is the internal state of parser. type State byte const ( StateContent State = iota StateEscape StateDoubleQuote StateDoubleQuoteEscape StateSingleQuote StateSingleQuoteEscape StateComment StateSlash StateMultilineComment StateMultilineCommentStar ) // Reader is a reader for filtering comments. // It supports Java style single and multi line comment syntax, and Python style single line comment syntax. type Reader struct { io.Reader state State br *buf.BufferedReader } // Read implements io.Reader.Read(). Buffer must be at least 3 bytes. func (v *Reader) Read(b []byte) (int, error) { if v.br == nil { v.br = &buf.BufferedReader{Reader: buf.NewReader(v.Reader)} } p := b[:0] for len(p) < len(b)-2 { x, err := v.br.ReadByte() if err != nil { if len(p) == 0 { return 0, err } return len(p), nil } switch v.state { case StateContent: switch x { case '"': v.state = StateDoubleQuote p = append(p, x) case '\'': v.state = StateSingleQuote p = append(p, x) case '\\': v.state = StateEscape case '#': v.state = StateComment case '/': v.state = StateSlash default: p = append(p, x) } case StateEscape: p = append(p, '\\', x) v.state = StateContent case StateDoubleQuote: switch x { case '"': v.state = StateContent p = append(p, x) case '\\': v.state = StateDoubleQuoteEscape default: p = append(p, x) } case StateDoubleQuoteEscape: p = append(p, '\\', x) v.state = StateDoubleQuote case StateSingleQuote: switch x { case '\'': v.state = StateContent p = append(p, x) case '\\': v.state = StateSingleQuoteEscape default: p = append(p, x) } case StateSingleQuoteEscape: p = append(p, '\\', x) v.state = StateSingleQuote case StateComment: if x == '\n' { v.state = StateContent p = append(p, '\n') } case StateSlash: switch x { case '/': v.state = StateComment case '*': v.state = StateMultilineComment default: p = append(p, '/', x) } case StateMultilineComment: switch x { case '*': v.state = StateMultilineCommentStar case '\n': p = append(p, '\n') } case StateMultilineCommentStar: switch x { case '/': v.state = StateContent case '*': // Stay case '\n': p = append(p, '\n') default: v.state = StateMultilineComment } default: panic("Unknown state.") } } return len(p), nil } ================================================ FILE: infra/conf/json/reader_test.go ================================================ package json_test import ( "bytes" "io" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/infra/conf/json" ) func TestReader(t *testing.T) { data := []struct { input string output string }{ { ` content #comment 1 #comment 2 content 2`, ` content content 2`}, {`content`, `content`}, {" ", " "}, {`con/*abcd*/tent`, "content"}, {` text // adlkhdf /* //comment adfkj text 2*/`, ` text text 2*`}, {`"//"content`, `"//"content`}, {`abcd'//'abcd`, `abcd'//'abcd`}, {`"\""`, `"\""`}, {`\"/*abcd*/\"`, `\"\"`}, } for _, testCase := range data { reader := &Reader{ Reader: bytes.NewReader([]byte(testCase.input)), } actual := make([]byte, 1024) n, err := reader.Read(actual) common.Must(err) if r := cmp.Diff(string(actual[:n]), testCase.output); r != "" { t.Error(r) } } } func TestReader1(t *testing.T) { type dataStruct struct { input string output string } bufLen := 8 data := []dataStruct{ {"loooooooooooooooooooooooooooooooooooooooog", "loooooooooooooooooooooooooooooooooooooooog"}, {`{"t": "\/testlooooooooooooooooooooooooooooong"}`, `{"t": "\/testlooooooooooooooooooooooooooooong"}`}, {`{"t": "\/test"}`, `{"t": "\/test"}`}, {`"\// fake comment"`, `"\// fake comment"`}, {`"\/\/\/\/\/"`, `"\/\/\/\/\/"`}, } for _, testCase := range data { reader := &Reader{ Reader: bytes.NewReader([]byte(testCase.input)), } target := make([]byte, 0) buf := make([]byte, bufLen) var n int var err error for n, err = reader.Read(buf); err == nil; n, err = reader.Read(buf) { if n > len(buf) { t.Error("n: ", n) } target = append(target, buf[:n]...) buf = make([]byte, bufLen) } if err != nil && err != io.EOF { t.Error("error: ", err) } if string(target) != testCase.output { t.Error("got ", string(target), " want ", testCase.output) } } } ================================================ FILE: infra/conf/loader.go ================================================ package conf import ( "encoding/json" "strings" ) type ConfigCreator func() interface{} type ConfigCreatorCache map[string]ConfigCreator func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error { if _, found := v[id]; found { return newError(id, " already registered.").AtError() } v[id] = creator return nil } func (v ConfigCreatorCache) CreateConfig(id string) (interface{}, error) { creator, found := v[id] if !found { return nil, newError("unknown config id: ", id) } return creator(), nil } type JSONConfigLoader struct { cache ConfigCreatorCache idKey string configKey string } func NewJSONConfigLoader(cache ConfigCreatorCache, idKey string, configKey string) *JSONConfigLoader { return &JSONConfigLoader{ idKey: idKey, configKey: configKey, cache: cache, } } func (v *JSONConfigLoader) LoadWithID(raw []byte, id string) (interface{}, error) { id = strings.ToLower(id) config, err := v.cache.CreateConfig(id) if err != nil { return nil, err } if err := json.Unmarshal(raw, config); err != nil { return nil, err } return config, nil } func (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) { var obj map[string]json.RawMessage if err := json.Unmarshal(raw, &obj); err != nil { return nil, "", err } rawID, found := obj[v.idKey] if !found { return nil, "", newError(v.idKey, " not found in JSON context").AtError() } var id string if err := json.Unmarshal(rawID, &id); err != nil { return nil, "", err } rawConfig := json.RawMessage(raw) if len(v.configKey) > 0 { configValue, found := obj[v.configKey] if found { rawConfig = configValue } else { // Default to empty json object. rawConfig = json.RawMessage([]byte("{}")) } } config, err := v.LoadWithID([]byte(rawConfig), id) if err != nil { return nil, id, err } return config, id, nil } ================================================ FILE: infra/conf/log.go ================================================ package conf import ( "strings" "v2ray.com/core/app/log" clog "v2ray.com/core/common/log" ) func DefaultLogConfig() *log.Config { return &log.Config{ AccessLogType: log.LogType_None, ErrorLogType: log.LogType_Console, ErrorLogLevel: clog.Severity_Warning, } } type LogConfig struct { AccessLog string `json:"access"` ErrorLog string `json:"error"` LogLevel string `json:"loglevel"` } func (v *LogConfig) Build() *log.Config { if v == nil { return nil } config := &log.Config{ ErrorLogType: log.LogType_Console, AccessLogType: log.LogType_Console, } if v.AccessLog == "none" { config.AccessLogType = log.LogType_None } else if len(v.AccessLog) > 0 { config.AccessLogPath = v.AccessLog config.AccessLogType = log.LogType_File } if v.ErrorLog == "none" { config.ErrorLogType = log.LogType_None } else if len(v.ErrorLog) > 0 { config.ErrorLogPath = v.ErrorLog config.ErrorLogType = log.LogType_File } level := strings.ToLower(v.LogLevel) switch level { case "debug": config.ErrorLogLevel = clog.Severity_Debug case "info": config.ErrorLogLevel = clog.Severity_Info case "error": config.ErrorLogLevel = clog.Severity_Error case "none": config.ErrorLogType = log.LogType_None config.AccessLogType = log.LogType_None default: config.ErrorLogLevel = clog.Severity_Warning } return config } ================================================ FILE: infra/conf/mtproto.go ================================================ package conf import ( "encoding/hex" "encoding/json" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/mtproto" ) type MTProtoAccount struct { Secret string `json:"secret"` } // Build implements Buildable func (a *MTProtoAccount) Build() (*mtproto.Account, error) { if len(a.Secret) != 32 { return nil, newError("MTProto secret must have 32 chars") } secret, err := hex.DecodeString(a.Secret) if err != nil { return nil, newError("failed to decode secret: ", a.Secret).Base(err) } return &mtproto.Account{ Secret: secret, }, nil } type MTProtoServerConfig struct { Users []json.RawMessage `json:"users"` } func (c *MTProtoServerConfig) Build() (proto.Message, error) { config := &mtproto.ServerConfig{} if len(c.Users) == 0 { return nil, newError("zero MTProto users configured.") } config.User = make([]*protocol.User, len(c.Users)) for idx, rawData := range c.Users { user := new(protocol.User) if err := json.Unmarshal(rawData, user); err != nil { return nil, newError("invalid MTProto user").Base(err) } account := new(MTProtoAccount) if err := json.Unmarshal(rawData, account); err != nil { return nil, newError("invalid MTProto user").Base(err) } accountProto, err := account.Build() if err != nil { return nil, newError("failed to parse MTProto user").Base(err) } user.Account = serial.ToTypedMessage(accountProto) config.User[idx] = user } return config, nil } type MTProtoClientConfig struct { } func (c *MTProtoClientConfig) Build() (proto.Message, error) { config := new(mtproto.ClientConfig) return config, nil } ================================================ FILE: infra/conf/mtproto_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/mtproto" ) func TestMTProtoServerConfig(t *testing.T) { creator := func() Buildable { return new(MTProtoServerConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "users": [{ "email": "love@v2ray.com", "level": 1, "secret": "b0cbcef5a486d9636472ac27f8e11a9d" }] }`, Parser: loadJSON(creator), Output: &mtproto.ServerConfig{ User: []*protocol.User{ { Email: "love@v2ray.com", Level: 1, Account: serial.ToTypedMessage(&mtproto.Account{ Secret: []byte{176, 203, 206, 245, 164, 134, 217, 99, 100, 114, 172, 39, 248, 225, 26, 157}, }), }, }, }, }, }) } ================================================ FILE: infra/conf/policy.go ================================================ package conf import ( "v2ray.com/core/app/policy" ) type Policy struct { Handshake *uint32 `json:"handshake"` ConnectionIdle *uint32 `json:"connIdle"` UplinkOnly *uint32 `json:"uplinkOnly"` DownlinkOnly *uint32 `json:"downlinkOnly"` StatsUserUplink bool `json:"statsUserUplink"` StatsUserDownlink bool `json:"statsUserDownlink"` BufferSize *int32 `json:"bufferSize"` } func (t *Policy) Build() (*policy.Policy, error) { config := new(policy.Policy_Timeout) if t.Handshake != nil { config.Handshake = &policy.Second{Value: *t.Handshake} } if t.ConnectionIdle != nil { config.ConnectionIdle = &policy.Second{Value: *t.ConnectionIdle} } if t.UplinkOnly != nil { config.UplinkOnly = &policy.Second{Value: *t.UplinkOnly} } if t.DownlinkOnly != nil { config.DownlinkOnly = &policy.Second{Value: *t.DownlinkOnly} } p := &policy.Policy{ Timeout: config, Stats: &policy.Policy_Stats{ UserUplink: t.StatsUserUplink, UserDownlink: t.StatsUserDownlink, }, } if t.BufferSize != nil { bs := int32(-1) if *t.BufferSize >= 0 { bs = (*t.BufferSize) * 1024 } p.Buffer = &policy.Policy_Buffer{ Connection: bs, } } return p, nil } type SystemPolicy struct { StatsInboundUplink bool `json:"statsInboundUplink"` StatsInboundDownlink bool `json:"statsInboundDownlink"` StatsOutboundUplink bool `json:"statsOutboundUplink"` StatsOutboundDownlink bool `json:"statsOutboundDownlink"` } func (p *SystemPolicy) Build() (*policy.SystemPolicy, error) { return &policy.SystemPolicy{ Stats: &policy.SystemPolicy_Stats{ InboundUplink: p.StatsInboundUplink, InboundDownlink: p.StatsInboundDownlink, OutboundUplink: p.StatsOutboundUplink, OutboundDownlink: p.StatsOutboundDownlink, }, }, nil } type PolicyConfig struct { Levels map[uint32]*Policy `json:"levels"` System *SystemPolicy `json:"system"` } func (c *PolicyConfig) Build() (*policy.Config, error) { levels := make(map[uint32]*policy.Policy) for l, p := range c.Levels { if p != nil { pp, err := p.Build() if err != nil { return nil, err } levels[l] = pp } } config := &policy.Config{ Level: levels, } if c.System != nil { sc, err := c.System.Build() if err != nil { return nil, err } config.System = sc } return config, nil } ================================================ FILE: infra/conf/policy_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common" . "v2ray.com/core/infra/conf" ) func TestBufferSize(t *testing.T) { cases := []struct { Input int32 Output int32 }{ { Input: 0, Output: 0, }, { Input: -1, Output: -1, }, { Input: 1, Output: 1024, }, } for _, c := range cases { bs := int32(c.Input) pConf := Policy{ BufferSize: &bs, } p, err := pConf.Build() common.Must(err) if p.Buffer.Connection != c.Output { t.Error("expected buffer size ", c.Output, " but got ", p.Buffer.Connection) } } } ================================================ FILE: infra/conf/reverse.go ================================================ package conf import ( "github.com/golang/protobuf/proto" "v2ray.com/core/app/reverse" ) type BridgeConfig struct { Tag string `json:"tag"` Domain string `json:"domain"` } func (c *BridgeConfig) Build() (*reverse.BridgeConfig, error) { return &reverse.BridgeConfig{ Tag: c.Tag, Domain: c.Domain, }, nil } type PortalConfig struct { Tag string `json:"tag"` Domain string `json:"domain"` } func (c *PortalConfig) Build() (*reverse.PortalConfig, error) { return &reverse.PortalConfig{ Tag: c.Tag, Domain: c.Domain, }, nil } type ReverseConfig struct { Bridges []BridgeConfig `json:"bridges"` Portals []PortalConfig `json:"portals"` } func (c *ReverseConfig) Build() (proto.Message, error) { config := &reverse.Config{} for _, bconfig := range c.Bridges { b, err := bconfig.Build() if err != nil { return nil, err } config.BridgeConfig = append(config.BridgeConfig, b) } for _, pconfig := range c.Portals { p, err := pconfig.Build() if err != nil { return nil, err } config.PortalConfig = append(config.PortalConfig, p) } return config, nil } ================================================ FILE: infra/conf/reverse_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/app/reverse" "v2ray.com/core/infra/conf" ) func TestReverseConfig(t *testing.T) { creator := func() conf.Buildable { return new(conf.ReverseConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "bridges": [{ "tag": "test", "domain": "test.v2ray.com" }] }`, Parser: loadJSON(creator), Output: &reverse.Config{ BridgeConfig: []*reverse.BridgeConfig{ {Tag: "test", Domain: "test.v2ray.com"}, }, }, }, { Input: `{ "portals": [{ "tag": "test", "domain": "test.v2ray.com" }] }`, Parser: loadJSON(creator), Output: &reverse.Config{ PortalConfig: []*reverse.PortalConfig{ {Tag: "test", Domain: "test.v2ray.com"}, }, }, }, }) } ================================================ FILE: infra/conf/router.go ================================================ package conf import ( "encoding/json" "strconv" "strings" "v2ray.com/core/app/router" "v2ray.com/core/common/net" "v2ray.com/core/common/platform/filesystem" "github.com/golang/protobuf/proto" ) type RouterRulesConfig struct { RuleList []json.RawMessage `json:"rules"` DomainStrategy string `json:"domainStrategy"` } type BalancingRule struct { Tag string `json:"tag"` Selectors StringList `json:"selector"` } func (r *BalancingRule) Build() (*router.BalancingRule, error) { if r.Tag == "" { return nil, newError("empty balancer tag") } if len(r.Selectors) == 0 { return nil, newError("empty selector list") } return &router.BalancingRule{ Tag: r.Tag, OutboundSelector: []string(r.Selectors), }, nil } type RouterConfig struct { Settings *RouterRulesConfig `json:"settings"` // Deprecated RuleList []json.RawMessage `json:"rules"` DomainStrategy *string `json:"domainStrategy"` Balancers []*BalancingRule `json:"balancers"` } func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy { ds := "" if c.DomainStrategy != nil { ds = *c.DomainStrategy } else if c.Settings != nil { ds = c.Settings.DomainStrategy } switch strings.ToLower(ds) { case "alwaysip": return router.Config_UseIp case "ipifnonmatch": return router.Config_IpIfNonMatch case "ipondemand": return router.Config_IpOnDemand default: return router.Config_AsIs } } func (c *RouterConfig) Build() (*router.Config, error) { config := new(router.Config) config.DomainStrategy = c.getDomainStrategy() rawRuleList := c.RuleList if c.Settings != nil { rawRuleList = append(c.RuleList, c.Settings.RuleList...) } for _, rawRule := range rawRuleList { rule, err := ParseRule(rawRule) if err != nil { return nil, err } config.Rule = append(config.Rule, rule) } for _, rawBalancer := range c.Balancers { balancer, err := rawBalancer.Build() if err != nil { return nil, err } config.BalancingRule = append(config.BalancingRule, balancer) } return config, nil } type RouterRule struct { Type string `json:"type"` OutboundTag string `json:"outboundTag"` BalancerTag string `json:"balancerTag"` } func ParseIP(s string) (*router.CIDR, error) { var addr, mask string i := strings.Index(s, "/") if i < 0 { addr = s } else { addr = s[:i] mask = s[i+1:] } ip := net.ParseAddress(addr) switch ip.Family() { case net.AddressFamilyIPv4: bits := uint32(32) if len(mask) > 0 { bits64, err := strconv.ParseUint(mask, 10, 32) if err != nil { return nil, newError("invalid network mask for router: ", mask).Base(err) } bits = uint32(bits64) } if bits > 32 { return nil, newError("invalid network mask for router: ", bits) } return &router.CIDR{ Ip: []byte(ip.IP()), Prefix: bits, }, nil case net.AddressFamilyIPv6: bits := uint32(128) if len(mask) > 0 { bits64, err := strconv.ParseUint(mask, 10, 32) if err != nil { return nil, newError("invalid network mask for router: ", mask).Base(err) } bits = uint32(bits64) } if bits > 128 { return nil, newError("invalid network mask for router: ", bits) } return &router.CIDR{ Ip: []byte(ip.IP()), Prefix: bits, }, nil default: return nil, newError("unsupported address for router: ", s) } } func loadGeoIP(country string) ([]*router.CIDR, error) { return loadIP("geoip.dat", country) } func loadIP(filename, country string) ([]*router.CIDR, error) { geoipBytes, err := filesystem.ReadAsset(filename) if err != nil { return nil, newError("failed to open file: ", filename).Base(err) } var geoipList router.GeoIPList if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { return nil, err } for _, geoip := range geoipList.Entry { if geoip.CountryCode == country { return geoip.Cidr, nil } } return nil, newError("country not found in ", filename, ": ", country) } func loadSite(filename, country string) ([]*router.Domain, error) { geositeBytes, err := filesystem.ReadAsset(filename) if err != nil { return nil, newError("failed to open file: ", filename).Base(err) } var geositeList router.GeoSiteList if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { return nil, err } for _, site := range geositeList.Entry { if site.CountryCode == country { return site.Domain, nil } } return nil, newError("list not found in ", filename, ": ", country) } type AttributeMatcher interface { Match(*router.Domain) bool } type BooleanMatcher string func (m BooleanMatcher) Match(domain *router.Domain) bool { for _, attr := range domain.Attribute { if attr.Key == string(m) { return true } } return false } type AttributeList struct { matcher []AttributeMatcher } func (al *AttributeList) Match(domain *router.Domain) bool { for _, matcher := range al.matcher { if !matcher.Match(domain) { return false } } return true } func (al *AttributeList) IsEmpty() bool { return len(al.matcher) == 0 } func parseAttrs(attrs []string) *AttributeList { al := new(AttributeList) for _, attr := range attrs { lc := strings.ToLower(attr) al.matcher = append(al.matcher, BooleanMatcher(lc)) } return al } func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { parts := strings.Split(siteWithAttr, "@") if len(parts) == 0 { return nil, newError("empty site") } country := strings.ToUpper(parts[0]) attrs := parseAttrs(parts[1:]) domains, err := loadSite(file, country) if err != nil { return nil, err } if attrs.IsEmpty() { return domains, nil } filteredDomains := make([]*router.Domain, 0, len(domains)) for _, domain := range domains { if attrs.Match(domain) { filteredDomains = append(filteredDomains, domain) } } return filteredDomains, nil } func parseDomainRule(domain string) ([]*router.Domain, error) { if strings.HasPrefix(domain, "geosite:") { country := strings.ToUpper(domain[8:]) domains, err := loadGeositeWithAttr("geosite.dat", country) if err != nil { return nil, newError("failed to load geosite: ", country).Base(err) } return domains, nil } var isExtDatFile = 0 { const prefix = "ext:" if strings.HasPrefix(domain, prefix) { isExtDatFile = len(prefix) } const prefixQualified = "ext-domain:" if strings.HasPrefix(domain, prefixQualified) { isExtDatFile = len(prefixQualified) } } if isExtDatFile != 0 { kv := strings.Split(domain[isExtDatFile:], ":") if len(kv) != 2 { return nil, newError("invalid external resource: ", domain) } filename := kv[0] country := kv[1] domains, err := loadGeositeWithAttr(filename, country) if err != nil { return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err) } return domains, nil } domainRule := new(router.Domain) switch { case strings.HasPrefix(domain, "regexp:"): domainRule.Type = router.Domain_Regex domainRule.Value = domain[7:] case strings.HasPrefix(domain, "domain:"): domainRule.Type = router.Domain_Domain domainRule.Value = domain[7:] case strings.HasPrefix(domain, "full:"): domainRule.Type = router.Domain_Full domainRule.Value = domain[5:] case strings.HasPrefix(domain, "keyword:"): domainRule.Type = router.Domain_Plain domainRule.Value = domain[8:] case strings.HasPrefix(domain, "dotless:"): domainRule.Type = router.Domain_Regex switch substr := domain[8:]; { case substr == "": domainRule.Value = "^[^.]*$" case !strings.Contains(substr, "."): domainRule.Value = "^[^.]*" + substr + "[^.]*$" default: return nil, newError("substr in dotless rule should not contain a dot: ", substr) } default: domainRule.Type = router.Domain_Plain domainRule.Value = domain } return []*router.Domain{domainRule}, nil } func toCidrList(ips StringList) ([]*router.GeoIP, error) { var geoipList []*router.GeoIP var customCidrs []*router.CIDR for _, ip := range ips { if strings.HasPrefix(ip, "geoip:") { country := ip[6:] geoip, err := loadGeoIP(strings.ToUpper(country)) if err != nil { return nil, newError("failed to load GeoIP: ", country).Base(err) } geoipList = append(geoipList, &router.GeoIP{ CountryCode: strings.ToUpper(country), Cidr: geoip, }) continue } var isExtDatFile = 0 { const prefix = "ext:" if strings.HasPrefix(ip, prefix) { isExtDatFile = len(prefix) } const prefixQualified = "ext-ip:" if strings.HasPrefix(ip, prefixQualified) { isExtDatFile = len(prefixQualified) } } if isExtDatFile != 0 { kv := strings.Split(ip[isExtDatFile:], ":") if len(kv) != 2 { return nil, newError("invalid external resource: ", ip) } filename := kv[0] country := kv[1] geoip, err := loadIP(filename, strings.ToUpper(country)) if err != nil { return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err) } geoipList = append(geoipList, &router.GeoIP{ CountryCode: strings.ToUpper(filename + "_" + country), Cidr: geoip, }) continue } ipRule, err := ParseIP(ip) if err != nil { return nil, newError("invalid IP: ", ip).Base(err) } customCidrs = append(customCidrs, ipRule) } if len(customCidrs) > 0 { geoipList = append(geoipList, &router.GeoIP{ Cidr: customCidrs, }) } return geoipList, nil } func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { type RawFieldRule struct { RouterRule Domain *StringList `json:"domain"` IP *StringList `json:"ip"` Port *PortList `json:"port"` Network *NetworkList `json:"network"` SourceIP *StringList `json:"source"` SourcePort *PortList `json:"sourcePort"` User *StringList `json:"user"` InboundTag *StringList `json:"inboundTag"` Protocols *StringList `json:"protocol"` Attributes string `json:"attrs"` } rawFieldRule := new(RawFieldRule) err := json.Unmarshal(msg, rawFieldRule) if err != nil { return nil, err } rule := new(router.RoutingRule) if len(rawFieldRule.OutboundTag) > 0 { rule.TargetTag = &router.RoutingRule_Tag{ Tag: rawFieldRule.OutboundTag, } } else if len(rawFieldRule.BalancerTag) > 0 { rule.TargetTag = &router.RoutingRule_BalancingTag{ BalancingTag: rawFieldRule.BalancerTag, } } else { return nil, newError("neither outboundTag nor balancerTag is specified in routing rule") } if rawFieldRule.Domain != nil { for _, domain := range *rawFieldRule.Domain { rules, err := parseDomainRule(domain) if err != nil { return nil, newError("failed to parse domain rule: ", domain).Base(err) } rule.Domain = append(rule.Domain, rules...) } } if rawFieldRule.IP != nil { geoipList, err := toCidrList(*rawFieldRule.IP) if err != nil { return nil, err } rule.Geoip = geoipList } if rawFieldRule.Port != nil { rule.PortList = rawFieldRule.Port.Build() } if rawFieldRule.Network != nil { rule.Networks = rawFieldRule.Network.Build() } if rawFieldRule.SourceIP != nil { geoipList, err := toCidrList(*rawFieldRule.SourceIP) if err != nil { return nil, err } rule.SourceGeoip = geoipList } if rawFieldRule.SourcePort != nil { rule.SourcePortList = rawFieldRule.SourcePort.Build() } if rawFieldRule.User != nil { for _, s := range *rawFieldRule.User { rule.UserEmail = append(rule.UserEmail, s) } } if rawFieldRule.InboundTag != nil { for _, s := range *rawFieldRule.InboundTag { rule.InboundTag = append(rule.InboundTag, s) } } if rawFieldRule.Protocols != nil { for _, s := range *rawFieldRule.Protocols { rule.Protocol = append(rule.Protocol, s) } } if len(rawFieldRule.Attributes) > 0 { rule.Attributes = rawFieldRule.Attributes } return rule, nil } func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) { rawRule := new(RouterRule) err := json.Unmarshal(msg, rawRule) if err != nil { return nil, newError("invalid router rule").Base(err) } if rawRule.Type == "field" { fieldrule, err := parseFieldRule(msg) if err != nil { return nil, newError("invalid field rule").Base(err) } return fieldrule, nil } if rawRule.Type == "chinaip" { chinaiprule, err := parseChinaIPRule(msg) if err != nil { return nil, newError("invalid chinaip rule").Base(err) } return chinaiprule, nil } if rawRule.Type == "chinasites" { chinasitesrule, err := parseChinaSitesRule(msg) if err != nil { return nil, newError("invalid chinasites rule").Base(err) } return chinasitesrule, nil } return nil, newError("unknown router rule type: ", rawRule.Type) } func parseChinaIPRule(data []byte) (*router.RoutingRule, error) { rawRule := new(RouterRule) err := json.Unmarshal(data, rawRule) if err != nil { return nil, newError("invalid router rule").Base(err) } chinaIPs, err := loadGeoIP("CN") if err != nil { return nil, newError("failed to load geoip:cn").Base(err) } return &router.RoutingRule{ TargetTag: &router.RoutingRule_Tag{ Tag: rawRule.OutboundTag, }, Cidr: chinaIPs, }, nil } func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) { rawRule := new(RouterRule) err := json.Unmarshal(data, rawRule) if err != nil { return nil, newError("invalid router rule").Base(err).AtError() } domains, err := loadGeositeWithAttr("geosite.dat", "CN") if err != nil { return nil, newError("failed to load geosite:cn.").Base(err) } return &router.RoutingRule{ TargetTag: &router.RoutingRule_Tag{ Tag: rawRule.OutboundTag, }, Domain: domains, }, nil } ================================================ FILE: infra/conf/router_test.go ================================================ package conf_test import ( "encoding/json" "testing" "github.com/golang/protobuf/proto" "v2ray.com/core/app/router" "v2ray.com/core/common/net" . "v2ray.com/core/infra/conf" ) func TestRouterConfig(t *testing.T) { createParser := func() func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { config := new(RouterConfig) if err := json.Unmarshal([]byte(s), config); err != nil { return nil, err } return config.Build() } } runMultiTestCase(t, []TestCase{ { Input: `{ "strategy": "rules", "settings": { "domainStrategy": "AsIs", "rules": [ { "type": "field", "domain": [ "baidu.com", "qq.com" ], "outboundTag": "direct" }, { "type": "field", "ip": [ "10.0.0.0/8", "::1/128" ], "outboundTag": "test" },{ "type": "field", "port": "53, 443, 1000-2000", "outboundTag": "test" },{ "type": "field", "port": 123, "outboundTag": "test" } ] }, "balancers": [ { "tag": "b1", "selector": ["test"] } ] }`, Parser: createParser(), Output: &router.Config{ DomainStrategy: router.Config_AsIs, BalancingRule: []*router.BalancingRule{ { Tag: "b1", OutboundSelector: []string{"test"}, }, }, Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ { Type: router.Domain_Plain, Value: "baidu.com", }, { Type: router.Domain_Plain, Value: "qq.com", }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "direct", }, }, { Geoip: []*router.GeoIP{ { Cidr: []*router.CIDR{ { Ip: []byte{10, 0, 0, 0}, Prefix: 8, }, { Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128, }, }, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "test", }, }, { PortList: &net.PortList{ Range: []*net.PortRange{ {From: 53, To: 53}, {From: 443, To: 443}, {From: 1000, To: 2000}, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "test", }, }, { PortList: &net.PortList{ Range: []*net.PortRange{ {From: 123, To: 123}, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "test", }, }, }, }, }, { Input: `{ "strategy": "rules", "settings": { "domainStrategy": "IPIfNonMatch", "rules": [ { "type": "field", "domain": [ "baidu.com", "qq.com" ], "outboundTag": "direct" }, { "type": "field", "ip": [ "10.0.0.0/8", "::1/128" ], "outboundTag": "test" } ] } }`, Parser: createParser(), Output: &router.Config{ DomainStrategy: router.Config_IpIfNonMatch, Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ { Type: router.Domain_Plain, Value: "baidu.com", }, { Type: router.Domain_Plain, Value: "qq.com", }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "direct", }, }, { Geoip: []*router.GeoIP{ { Cidr: []*router.CIDR{ { Ip: []byte{10, 0, 0, 0}, Prefix: 8, }, { Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128, }, }, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "test", }, }, }, }, }, { Input: `{ "domainStrategy": "AsIs", "rules": [ { "type": "field", "domain": [ "baidu.com", "qq.com" ], "outboundTag": "direct" }, { "type": "field", "ip": [ "10.0.0.0/8", "::1/128" ], "outboundTag": "test" } ] }`, Parser: createParser(), Output: &router.Config{ DomainStrategy: router.Config_AsIs, Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ { Type: router.Domain_Plain, Value: "baidu.com", }, { Type: router.Domain_Plain, Value: "qq.com", }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "direct", }, }, { Geoip: []*router.GeoIP{ { Cidr: []*router.CIDR{ { Ip: []byte{10, 0, 0, 0}, Prefix: 8, }, { Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Prefix: 128, }, }, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "test", }, }, }, }, }, }) } ================================================ FILE: infra/conf/serial/errors.generated.go ================================================ package serial import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: infra/conf/serial/loader.go ================================================ package serial import ( "bytes" "encoding/json" "io" "v2ray.com/core" "v2ray.com/core/common/errors" "v2ray.com/core/infra/conf" json_reader "v2ray.com/core/infra/conf/json" ) type offset struct { line int char int } func findOffset(b []byte, o int) *offset { if o >= len(b) || o < 0 { return nil } line := 1 char := 0 for i, x := range b { if i == o { break } if x == '\n' { line++ char = 0 } else { char++ } } return &offset{line: line, char: char} } // DecodeJSONConfig reads from reader and decode the config into *conf.Config // syntax error could be detected. func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) { jsonConfig := &conf.Config{} jsonContent := bytes.NewBuffer(make([]byte, 0, 10240)) jsonReader := io.TeeReader(&json_reader.Reader{ Reader: reader, }, jsonContent) decoder := json.NewDecoder(jsonReader) if err := decoder.Decode(jsonConfig); err != nil { var pos *offset cause := errors.Cause(err) switch tErr := cause.(type) { case *json.SyntaxError: pos = findOffset(jsonContent.Bytes(), int(tErr.Offset)) case *json.UnmarshalTypeError: pos = findOffset(jsonContent.Bytes(), int(tErr.Offset)) } if pos != nil { return nil, newError("failed to read config file at line ", pos.line, " char ", pos.char).Base(err) } return nil, newError("failed to read config file").Base(err) } return jsonConfig, nil } func LoadJSONConfig(reader io.Reader) (*core.Config, error) { jsonConfig, err := DecodeJSONConfig(reader) if err != nil { return nil, err } pbConfig, err := jsonConfig.Build() if err != nil { return nil, newError("failed to parse json config").Base(err) } return pbConfig, nil } ================================================ FILE: infra/conf/serial/loader_test.go ================================================ package serial_test import ( "bytes" "strings" "testing" "v2ray.com/core/infra/conf/serial" ) func TestLoaderError(t *testing.T) { testCases := []struct { Input string Output string }{ { Input: `{ "log": { // abcd 0, "loglevel": "info" } }`, Output: "line 4 char 6", }, { Input: `{ "log": { // abcd "loglevel": "info", } }`, Output: "line 5 char 5", }, { Input: `{ "port": 1, "inbounds": [{ "protocol": "test" }] }`, Output: "parse json config", }, { Input: `{ "inbounds": [{ "port": 1, "listen": 0, "protocol": "test" }] }`, Output: "line 1 char 1", }, } for _, testCase := range testCases { reader := bytes.NewReader([]byte(testCase.Input)) _, err := serial.LoadJSONConfig(reader) errString := err.Error() if !strings.Contains(errString, testCase.Output) { t.Error("unexpected output from json: ", testCase.Input, ". expected ", testCase.Output, ", but actually ", errString) } } } ================================================ FILE: infra/conf/serial/serial.go ================================================ package serial //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: infra/conf/shadowsocks.go ================================================ package conf import ( "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/shadowsocks" ) func cipherFromString(c string) shadowsocks.CipherType { switch strings.ToLower(c) { case "aes-256-cfb": return shadowsocks.CipherType_AES_256_CFB case "aes-128-cfb": return shadowsocks.CipherType_AES_128_CFB case "chacha20": return shadowsocks.CipherType_CHACHA20 case "chacha20-ietf": return shadowsocks.CipherType_CHACHA20_IETF case "aes-128-gcm", "aead_aes_128_gcm": return shadowsocks.CipherType_AES_128_GCM case "aes-256-gcm", "aead_aes_256_gcm": return shadowsocks.CipherType_AES_256_GCM case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305": return shadowsocks.CipherType_CHACHA20_POLY1305 case "none", "plain": return shadowsocks.CipherType_NONE default: return shadowsocks.CipherType_UNKNOWN } } type ShadowsocksServerConfig struct { Cipher string `json:"method"` Password string `json:"password"` UDP bool `json:"udp"` Level byte `json:"level"` Email string `json:"email"` NetworkList *NetworkList `json:"network"` } func (v *ShadowsocksServerConfig) Build() (proto.Message, error) { config := new(shadowsocks.ServerConfig) config.UdpEnabled = v.UDP config.Network = v.NetworkList.Build() if v.Password == "" { return nil, newError("Shadowsocks password is not specified.") } account := &shadowsocks.Account{ Password: v.Password, } account.CipherType = cipherFromString(v.Cipher) if account.CipherType == shadowsocks.CipherType_UNKNOWN { return nil, newError("unknown cipher method: ", v.Cipher) } config.User = &protocol.User{ Email: v.Email, Level: uint32(v.Level), Account: serial.ToTypedMessage(account), } return config, nil } type ShadowsocksServerTarget struct { Address *Address `json:"address"` Port uint16 `json:"port"` Cipher string `json:"method"` Password string `json:"password"` Email string `json:"email"` Ota bool `json:"ota"` Level byte `json:"level"` } type ShadowsocksClientConfig struct { Servers []*ShadowsocksServerTarget `json:"servers"` } func (v *ShadowsocksClientConfig) Build() (proto.Message, error) { config := new(shadowsocks.ClientConfig) if len(v.Servers) == 0 { return nil, newError("0 Shadowsocks server configured.") } serverSpecs := make([]*protocol.ServerEndpoint, len(v.Servers)) for idx, server := range v.Servers { if server.Address == nil { return nil, newError("Shadowsocks server address is not set.") } if server.Port == 0 { return nil, newError("Invalid Shadowsocks port.") } if server.Password == "" { return nil, newError("Shadowsocks password is not specified.") } account := &shadowsocks.Account{ Password: server.Password, } account.CipherType = cipherFromString(server.Cipher) if account.CipherType == shadowsocks.CipherType_UNKNOWN { return nil, newError("unknown cipher method: ", server.Cipher) } ss := &protocol.ServerEndpoint{ Address: server.Address.Build(), Port: uint32(server.Port), User: []*protocol.User{ { Level: uint32(server.Level), Email: server.Email, Account: serial.ToTypedMessage(account), }, }, } serverSpecs[idx] = ss } config.Server = serverSpecs return config, nil } ================================================ FILE: infra/conf/shadowsocks_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/shadowsocks" ) func TestShadowsocksServerConfigParsing(t *testing.T) { creator := func() Buildable { return new(ShadowsocksServerConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "method": "aes-128-cfb", "password": "v2ray-password" }`, Parser: loadJSON(creator), Output: &shadowsocks.ServerConfig{ User: &protocol.User{ Account: serial.ToTypedMessage(&shadowsocks.Account{ CipherType: shadowsocks.CipherType_AES_128_CFB, Password: "v2ray-password", }), }, Network: []net.Network{net.Network_TCP}, }, }, }) } ================================================ FILE: infra/conf/socks.go ================================================ package conf import ( "encoding/json" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/socks" ) type SocksAccount struct { Username string `json:"user"` Password string `json:"pass"` } func (v *SocksAccount) Build() *socks.Account { return &socks.Account{ Username: v.Username, Password: v.Password, } } const ( AuthMethodNoAuth = "noauth" AuthMethodUserPass = "password" ) type SocksServerConfig struct { AuthMethod string `json:"auth"` Accounts []*SocksAccount `json:"accounts"` UDP bool `json:"udp"` Host *Address `json:"ip"` Timeout uint32 `json:"timeout"` UserLevel uint32 `json:"userLevel"` } func (v *SocksServerConfig) Build() (proto.Message, error) { config := new(socks.ServerConfig) switch v.AuthMethod { case AuthMethodNoAuth: config.AuthType = socks.AuthType_NO_AUTH case AuthMethodUserPass: config.AuthType = socks.AuthType_PASSWORD default: //newError("unknown socks auth method: ", v.AuthMethod, ". Default to noauth.").AtWarning().WriteToLog() config.AuthType = socks.AuthType_NO_AUTH } if len(v.Accounts) > 0 { config.Accounts = make(map[string]string, len(v.Accounts)) for _, account := range v.Accounts { config.Accounts[account.Username] = account.Password } } config.UdpEnabled = v.UDP if v.Host != nil { config.Address = v.Host.Build() } config.Timeout = v.Timeout config.UserLevel = v.UserLevel return config, nil } type SocksRemoteConfig struct { Address *Address `json:"address"` Port uint16 `json:"port"` Users []json.RawMessage `json:"users"` } type SocksClientConfig struct { Servers []*SocksRemoteConfig `json:"servers"` } func (v *SocksClientConfig) Build() (proto.Message, error) { config := new(socks.ClientConfig) config.Server = make([]*protocol.ServerEndpoint, len(v.Servers)) for idx, serverConfig := range v.Servers { server := &protocol.ServerEndpoint{ Address: serverConfig.Address.Build(), Port: uint32(serverConfig.Port), } for _, rawUser := range serverConfig.Users { user := new(protocol.User) if err := json.Unmarshal(rawUser, user); err != nil { return nil, newError("failed to parse Socks user").Base(err).AtError() } account := new(SocksAccount) if err := json.Unmarshal(rawUser, account); err != nil { return nil, newError("failed to parse socks account").Base(err).AtError() } user.Account = serial.ToTypedMessage(account.Build()) server.User = append(server.User, user) } config.Server[idx] = server } return config, nil } ================================================ FILE: infra/conf/socks_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/socks" ) func TestSocksInboundConfig(t *testing.T) { creator := func() Buildable { return new(SocksServerConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "auth": "password", "accounts": [ { "user": "my-username", "pass": "my-password" } ], "udp": false, "ip": "127.0.0.1", "timeout": 5, "userLevel": 1 }`, Parser: loadJSON(creator), Output: &socks.ServerConfig{ AuthType: socks.AuthType_PASSWORD, Accounts: map[string]string{ "my-username": "my-password", }, UdpEnabled: false, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Timeout: 5, UserLevel: 1, }, }, }) } func TestSocksOutboundConfig(t *testing.T) { creator := func() Buildable { return new(SocksClientConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "servers": [{ "address": "127.0.0.1", "port": 1234, "users": [ {"user": "test user", "pass": "test pass", "email": "test@email.com"} ] }] }`, Parser: loadJSON(creator), Output: &socks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 1234, User: []*protocol.User{ { Email: "test@email.com", Account: serial.ToTypedMessage(&socks.Account{ Username: "test user", Password: "test pass", }), }, }, }, }, }, }, }) } ================================================ FILE: infra/conf/transport.go ================================================ package conf import ( "v2ray.com/core/common/serial" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) type TransportConfig struct { TCPConfig *TCPConfig `json:"tcpSettings"` KCPConfig *KCPConfig `json:"kcpSettings"` WSConfig *WebSocketConfig `json:"wsSettings"` HTTPConfig *HTTPConfig `json:"httpSettings"` DSConfig *DomainSocketConfig `json:"dsSettings"` QUICConfig *QUICConfig `json:"quicSettings"` } // Build implements Buildable. func (c *TransportConfig) Build() (*transport.Config, error) { config := new(transport.Config) if c.TCPConfig != nil { ts, err := c.TCPConfig.Build() if err != nil { return nil, newError("failed to build TCP config").Base(err).AtError() } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "tcp", Settings: serial.ToTypedMessage(ts), }) } if c.KCPConfig != nil { ts, err := c.KCPConfig.Build() if err != nil { return nil, newError("failed to build mKCP config").Base(err).AtError() } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "mkcp", Settings: serial.ToTypedMessage(ts), }) } if c.WSConfig != nil { ts, err := c.WSConfig.Build() if err != nil { return nil, newError("failed to build WebSocket config").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "websocket", Settings: serial.ToTypedMessage(ts), }) } if c.HTTPConfig != nil { ts, err := c.HTTPConfig.Build() if err != nil { return nil, newError("Failed to build HTTP config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "http", Settings: serial.ToTypedMessage(ts), }) } if c.DSConfig != nil { ds, err := c.DSConfig.Build() if err != nil { return nil, newError("Failed to build DomainSocket config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "domainsocket", Settings: serial.ToTypedMessage(ds), }) } if c.QUICConfig != nil { qs, err := c.QUICConfig.Build() if err != nil { return nil, newError("Failed to build QUIC config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "quic", Settings: serial.ToTypedMessage(qs), }) } return config, nil } ================================================ FILE: infra/conf/transport_authenticators.go ================================================ package conf import ( "sort" "github.com/golang/protobuf/proto" "v2ray.com/core/transport/internet/headers/http" "v2ray.com/core/transport/internet/headers/noop" "v2ray.com/core/transport/internet/headers/srtp" "v2ray.com/core/transport/internet/headers/tls" "v2ray.com/core/transport/internet/headers/utp" "v2ray.com/core/transport/internet/headers/wechat" "v2ray.com/core/transport/internet/headers/wireguard" ) type NoOpAuthenticator struct{} func (NoOpAuthenticator) Build() (proto.Message, error) { return new(noop.Config), nil } type NoOpConnectionAuthenticator struct{} func (NoOpConnectionAuthenticator) Build() (proto.Message, error) { return new(noop.ConnectionConfig), nil } type SRTPAuthenticator struct{} func (SRTPAuthenticator) Build() (proto.Message, error) { return new(srtp.Config), nil } type UTPAuthenticator struct{} func (UTPAuthenticator) Build() (proto.Message, error) { return new(utp.Config), nil } type WechatVideoAuthenticator struct{} func (WechatVideoAuthenticator) Build() (proto.Message, error) { return new(wechat.VideoConfig), nil } type WireguardAuthenticator struct{} func (WireguardAuthenticator) Build() (proto.Message, error) { return new(wireguard.WireguardConfig), nil } type DTLSAuthenticator struct{} func (DTLSAuthenticator) Build() (proto.Message, error) { return new(tls.PacketConfig), nil } type HTTPAuthenticatorRequest struct { Version string `json:"version"` Method string `json:"method"` Path StringList `json:"path"` Headers map[string]*StringList `json:"headers"` } func sortMapKeys(m map[string]*StringList) []string { var keys []string for key := range m { keys = append(keys, key) } sort.Strings(keys) return keys } func (v *HTTPAuthenticatorRequest) Build() (*http.RequestConfig, error) { config := &http.RequestConfig{ Uri: []string{"/"}, Header: []*http.Header{ { Name: "Host", Value: []string{"www.baidu.com", "www.bing.com"}, }, { Name: "User-Agent", Value: []string{ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46", }, }, { Name: "Accept-Encoding", Value: []string{"gzip, deflate"}, }, { Name: "Connection", Value: []string{"keep-alive"}, }, { Name: "Pragma", Value: []string{"no-cache"}, }, }, } if len(v.Version) > 0 { config.Version = &http.Version{Value: v.Version} } if len(v.Method) > 0 { config.Method = &http.Method{Value: v.Method} } if len(v.Path) > 0 { config.Uri = append([]string(nil), (v.Path)...) } if len(v.Headers) > 0 { config.Header = make([]*http.Header, 0, len(v.Headers)) headerNames := sortMapKeys(v.Headers) for _, key := range headerNames { value := v.Headers[key] if value == nil { return nil, newError("empty HTTP header value: " + key).AtError() } config.Header = append(config.Header, &http.Header{ Name: key, Value: append([]string(nil), (*value)...), }) } } return config, nil } type HTTPAuthenticatorResponse struct { Version string `json:"version"` Status string `json:"status"` Reason string `json:"reason"` Headers map[string]*StringList `json:"headers"` } func (v *HTTPAuthenticatorResponse) Build() (*http.ResponseConfig, error) { config := &http.ResponseConfig{ Header: []*http.Header{ { Name: "Content-Type", Value: []string{"application/octet-stream", "video/mpeg"}, }, { Name: "Transfer-Encoding", Value: []string{"chunked"}, }, { Name: "Connection", Value: []string{"keep-alive"}, }, { Name: "Pragma", Value: []string{"no-cache"}, }, { Name: "Cache-Control", Value: []string{"private", "no-cache"}, }, }, } if len(v.Version) > 0 { config.Version = &http.Version{Value: v.Version} } if len(v.Status) > 0 || len(v.Reason) > 0 { config.Status = &http.Status{ Code: "200", Reason: "OK", } if len(v.Status) > 0 { config.Status.Code = v.Status } if len(v.Reason) > 0 { config.Status.Reason = v.Reason } } if len(v.Headers) > 0 { config.Header = make([]*http.Header, 0, len(v.Headers)) headerNames := sortMapKeys(v.Headers) for _, key := range headerNames { value := v.Headers[key] if value == nil { return nil, newError("empty HTTP header value: " + key).AtError() } config.Header = append(config.Header, &http.Header{ Name: key, Value: append([]string(nil), (*value)...), }) } } return config, nil } type HTTPAuthenticator struct { Request HTTPAuthenticatorRequest `json:"request"` Response HTTPAuthenticatorResponse `json:"response"` } func (v *HTTPAuthenticator) Build() (proto.Message, error) { config := new(http.Config) requestConfig, err := v.Request.Build() if err != nil { return nil, err } config.Request = requestConfig responseConfig, err := v.Response.Build() if err != nil { return nil, err } config.Response = responseConfig return config, nil } ================================================ FILE: infra/conf/transport_internet.go ================================================ package conf import ( "encoding/json" "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common/platform/filesystem" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/domainsocket" "v2ray.com/core/transport/internet/http" "v2ray.com/core/transport/internet/kcp" "v2ray.com/core/transport/internet/quic" "v2ray.com/core/transport/internet/tcp" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/websocket" "v2ray.com/core/transport/internet/xtls" ) var ( kcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ "none": func() interface{} { return new(NoOpAuthenticator) }, "srtp": func() interface{} { return new(SRTPAuthenticator) }, "utp": func() interface{} { return new(UTPAuthenticator) }, "wechat-video": func() interface{} { return new(WechatVideoAuthenticator) }, "dtls": func() interface{} { return new(DTLSAuthenticator) }, "wireguard": func() interface{} { return new(WireguardAuthenticator) }, }, "type", "") tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ "none": func() interface{} { return new(NoOpConnectionAuthenticator) }, "http": func() interface{} { return new(HTTPAuthenticator) }, }, "type", "") ) type KCPConfig struct { Mtu *uint32 `json:"mtu"` Tti *uint32 `json:"tti"` UpCap *uint32 `json:"uplinkCapacity"` DownCap *uint32 `json:"downlinkCapacity"` Congestion *bool `json:"congestion"` ReadBufferSize *uint32 `json:"readBufferSize"` WriteBufferSize *uint32 `json:"writeBufferSize"` HeaderConfig json.RawMessage `json:"header"` Seed *string `json:"seed"` } // Build implements Buildable. func (c *KCPConfig) Build() (proto.Message, error) { config := new(kcp.Config) if c.Mtu != nil { mtu := *c.Mtu if mtu < 576 || mtu > 1460 { return nil, newError("invalid mKCP MTU size: ", mtu).AtError() } config.Mtu = &kcp.MTU{Value: mtu} } if c.Tti != nil { tti := *c.Tti if tti < 10 || tti > 100 { return nil, newError("invalid mKCP TTI: ", tti).AtError() } config.Tti = &kcp.TTI{Value: tti} } if c.UpCap != nil { config.UplinkCapacity = &kcp.UplinkCapacity{Value: *c.UpCap} } if c.DownCap != nil { config.DownlinkCapacity = &kcp.DownlinkCapacity{Value: *c.DownCap} } if c.Congestion != nil { config.Congestion = *c.Congestion } if c.ReadBufferSize != nil { size := *c.ReadBufferSize if size > 0 { config.ReadBuffer = &kcp.ReadBuffer{Size: size * 1024 * 1024} } else { config.ReadBuffer = &kcp.ReadBuffer{Size: 512 * 1024} } } if c.WriteBufferSize != nil { size := *c.WriteBufferSize if size > 0 { config.WriteBuffer = &kcp.WriteBuffer{Size: size * 1024 * 1024} } else { config.WriteBuffer = &kcp.WriteBuffer{Size: 512 * 1024} } } if len(c.HeaderConfig) > 0 { headerConfig, _, err := kcpHeaderLoader.Load(c.HeaderConfig) if err != nil { return nil, newError("invalid mKCP header config.").Base(err).AtError() } ts, err := headerConfig.(Buildable).Build() if err != nil { return nil, newError("invalid mKCP header config").Base(err).AtError() } config.HeaderConfig = serial.ToTypedMessage(ts) } if c.Seed != nil { config.Seed = &kcp.EncryptionSeed{Seed: *c.Seed} } return config, nil } type TCPConfig struct { HeaderConfig json.RawMessage `json:"header"` AcceptProxyProtocol bool `json:"acceptProxyProtocol"` } // Build implements Buildable. func (c *TCPConfig) Build() (proto.Message, error) { config := new(tcp.Config) if len(c.HeaderConfig) > 0 { headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig) if err != nil { return nil, newError("invalid TCP header config").Base(err).AtError() } ts, err := headerConfig.(Buildable).Build() if err != nil { return nil, newError("invalid TCP header config").Base(err).AtError() } config.HeaderSettings = serial.ToTypedMessage(ts) } if c.AcceptProxyProtocol { config.AcceptProxyProtocol = c.AcceptProxyProtocol } return config, nil } type WebSocketConfig struct { Path string `json:"path"` Path2 string `json:"Path"` // The key was misspelled. For backward compatibility, we have to keep track the old key. Headers map[string]string `json:"headers"` AcceptProxyProtocol bool `json:"acceptProxyProtocol"` } // Build implements Buildable. func (c *WebSocketConfig) Build() (proto.Message, error) { path := c.Path if path == "" && c.Path2 != "" { path = c.Path2 } header := make([]*websocket.Header, 0, 32) for key, value := range c.Headers { header = append(header, &websocket.Header{ Key: key, Value: value, }) } config := &websocket.Config{ Path: path, Header: header, } if c.AcceptProxyProtocol { config.AcceptProxyProtocol = c.AcceptProxyProtocol } return config, nil } type HTTPConfig struct { Host *StringList `json:"host"` Path string `json:"path"` } // Build implements Buildable. func (c *HTTPConfig) Build() (proto.Message, error) { config := &http.Config{ Path: c.Path, } if c.Host != nil { config.Host = []string(*c.Host) } return config, nil } type QUICConfig struct { Header json.RawMessage `json:"header"` Security string `json:"security"` Key string `json:"key"` } // Build implements Buildable. func (c *QUICConfig) Build() (proto.Message, error) { config := &quic.Config{ Key: c.Key, } if len(c.Header) > 0 { headerConfig, _, err := kcpHeaderLoader.Load(c.Header) if err != nil { return nil, newError("invalid QUIC header config.").Base(err).AtError() } ts, err := headerConfig.(Buildable).Build() if err != nil { return nil, newError("invalid QUIC header config").Base(err).AtError() } config.Header = serial.ToTypedMessage(ts) } var st protocol.SecurityType switch strings.ToLower(c.Security) { case "aes-128-gcm": st = protocol.SecurityType_AES128_GCM case "chacha20-poly1305": st = protocol.SecurityType_CHACHA20_POLY1305 default: st = protocol.SecurityType_NONE } config.Security = &protocol.SecurityConfig{ Type: st, } return config, nil } type DomainSocketConfig struct { Path string `json:"path"` Abstract bool `json:"abstract"` Padding bool `json:"padding"` AcceptProxyProtocol bool `json:"acceptProxyProtocol"` } // Build implements Buildable. func (c *DomainSocketConfig) Build() (proto.Message, error) { return &domainsocket.Config{ Path: c.Path, Abstract: c.Abstract, Padding: c.Padding, AcceptProxyProtocol: c.AcceptProxyProtocol, }, nil } func readFileOrString(f string, s []string) ([]byte, error) { if len(f) > 0 { return filesystem.ReadFile(f) } if len(s) > 0 { return []byte(strings.Join(s, "\n")), nil } return nil, newError("both file and bytes are empty.") } type TLSCertConfig struct { CertFile string `json:"certificateFile"` CertStr []string `json:"certificate"` KeyFile string `json:"keyFile"` KeyStr []string `json:"key"` Usage string `json:"usage"` } // Build implements Buildable. func (c *TLSCertConfig) Build() (*tls.Certificate, error) { certificate := new(tls.Certificate) cert, err := readFileOrString(c.CertFile, c.CertStr) if err != nil { return nil, newError("failed to parse certificate").Base(err) } certificate.Certificate = cert if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 { key, err := readFileOrString(c.KeyFile, c.KeyStr) if err != nil { return nil, newError("failed to parse key").Base(err) } certificate.Key = key } switch strings.ToLower(c.Usage) { case "encipherment": certificate.Usage = tls.Certificate_ENCIPHERMENT case "verify": certificate.Usage = tls.Certificate_AUTHORITY_VERIFY case "issue": certificate.Usage = tls.Certificate_AUTHORITY_ISSUE default: certificate.Usage = tls.Certificate_ENCIPHERMENT } return certificate, nil } type TLSConfig struct { Insecure bool `json:"allowInsecure"` InsecureCiphers bool `json:"allowInsecureCiphers"` Certs []*TLSCertConfig `json:"certificates"` ServerName string `json:"serverName"` ALPN *StringList `json:"alpn"` DisableSessionResumption bool `json:"disableSessionResumption"` DisableSystemRoot bool `json:"disableSystemRoot"` } // Build implements Buildable. func (c *TLSConfig) Build() (proto.Message, error) { config := new(tls.Config) config.Certificate = make([]*tls.Certificate, len(c.Certs)) for idx, certConf := range c.Certs { cert, err := certConf.Build() if err != nil { return nil, err } config.Certificate[idx] = cert } serverName := c.ServerName config.AllowInsecure = c.Insecure config.AllowInsecureCiphers = c.InsecureCiphers if len(c.ServerName) > 0 { config.ServerName = serverName } if c.ALPN != nil && len(*c.ALPN) > 0 { config.NextProtocol = []string(*c.ALPN) } config.DisableSessionResumption = c.DisableSessionResumption config.DisableSystemRoot = c.DisableSystemRoot return config, nil } type XTLSCertConfig struct { CertFile string `json:"certificateFile"` CertStr []string `json:"certificate"` KeyFile string `json:"keyFile"` KeyStr []string `json:"key"` Usage string `json:"usage"` } // Build implements Buildable. func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) { certificate := new(xtls.Certificate) cert, err := readFileOrString(c.CertFile, c.CertStr) if err != nil { return nil, newError("failed to parse certificate").Base(err) } certificate.Certificate = cert if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 { key, err := readFileOrString(c.KeyFile, c.KeyStr) if err != nil { return nil, newError("failed to parse key").Base(err) } certificate.Key = key } switch strings.ToLower(c.Usage) { case "encipherment": certificate.Usage = xtls.Certificate_ENCIPHERMENT case "verify": certificate.Usage = xtls.Certificate_AUTHORITY_VERIFY case "issue": certificate.Usage = xtls.Certificate_AUTHORITY_ISSUE default: certificate.Usage = xtls.Certificate_ENCIPHERMENT } return certificate, nil } type XTLSConfig struct { Insecure bool `json:"allowInsecure"` InsecureCiphers bool `json:"allowInsecureCiphers"` Certs []*XTLSCertConfig `json:"certificates"` ServerName string `json:"serverName"` ALPN *StringList `json:"alpn"` DisableSessionResumption bool `json:"disableSessionResumption"` DisableSystemRoot bool `json:"disableSystemRoot"` } // Build implements Buildable. func (c *XTLSConfig) Build() (proto.Message, error) { config := new(xtls.Config) config.Certificate = make([]*xtls.Certificate, len(c.Certs)) for idx, certConf := range c.Certs { cert, err := certConf.Build() if err != nil { return nil, err } config.Certificate[idx] = cert } serverName := c.ServerName config.AllowInsecure = c.Insecure config.AllowInsecureCiphers = c.InsecureCiphers if len(c.ServerName) > 0 { config.ServerName = serverName } if c.ALPN != nil && len(*c.ALPN) > 0 { config.NextProtocol = []string(*c.ALPN) } config.DisableSessionResumption = c.DisableSessionResumption config.DisableSystemRoot = c.DisableSystemRoot return config, nil } type TransportProtocol string // Build implements Buildable. func (p TransportProtocol) Build() (string, error) { switch strings.ToLower(string(p)) { case "tcp": return "tcp", nil case "kcp", "mkcp": return "mkcp", nil case "ws", "websocket": return "websocket", nil case "h2", "http": return "http", nil case "ds", "domainsocket": return "domainsocket", nil case "quic": return "quic", nil default: return "", newError("Config: unknown transport protocol: ", p) } } type SocketConfig struct { Mark int32 `json:"mark"` TFO *bool `json:"tcpFastOpen"` TProxy string `json:"tproxy"` } // Build implements Buildable. func (c *SocketConfig) Build() (*internet.SocketConfig, error) { var tfoSettings internet.SocketConfig_TCPFastOpenState if c.TFO != nil { if *c.TFO { tfoSettings = internet.SocketConfig_Enable } else { tfoSettings = internet.SocketConfig_Disable } } var tproxy internet.SocketConfig_TProxyMode switch strings.ToLower(c.TProxy) { case "tproxy": tproxy = internet.SocketConfig_TProxy case "redirect": tproxy = internet.SocketConfig_Redirect default: tproxy = internet.SocketConfig_Off } return &internet.SocketConfig{ Mark: c.Mark, Tfo: tfoSettings, Tproxy: tproxy, }, nil } type StreamConfig struct { Network *TransportProtocol `json:"network"` Security string `json:"security"` TLSSettings *TLSConfig `json:"tlsSettings"` XTLSSettings *XTLSConfig `json:"xtlsSettings"` TCPSettings *TCPConfig `json:"tcpSettings"` KCPSettings *KCPConfig `json:"kcpSettings"` WSSettings *WebSocketConfig `json:"wsSettings"` HTTPSettings *HTTPConfig `json:"httpSettings"` DSSettings *DomainSocketConfig `json:"dsSettings"` QUICSettings *QUICConfig `json:"quicSettings"` SocketSettings *SocketConfig `json:"sockopt"` } // Build implements Buildable. func (c *StreamConfig) Build() (*internet.StreamConfig, error) { config := &internet.StreamConfig{ ProtocolName: "tcp", } if c.Network != nil { protocol, err := (*c.Network).Build() if err != nil { return nil, err } config.ProtocolName = protocol } if strings.EqualFold(c.Security, "tls") { tlsSettings := c.TLSSettings if tlsSettings == nil { if c.XTLSSettings != nil { return nil, newError(`TLS: Please use "tlsSettings" instead of "xtlsSettings".`) } tlsSettings = &TLSConfig{} } ts, err := tlsSettings.Build() if err != nil { return nil, newError("Failed to build TLS config.").Base(err) } tm := serial.ToTypedMessage(ts) config.SecuritySettings = append(config.SecuritySettings, tm) config.SecurityType = tm.Type } if strings.EqualFold(c.Security, "xtls") { if config.ProtocolName != "tcp" && config.ProtocolName != "mkcp" && config.ProtocolName != "domainsocket" { return nil, newError("XTLS only supports TCP, mKCP and DomainSocket for now.") } xtlsSettings := c.XTLSSettings if xtlsSettings == nil { if c.TLSSettings != nil { return nil, newError(`XTLS: Please use "xtlsSettings" instead of "tlsSettings".`) } xtlsSettings = &XTLSConfig{} } ts, err := xtlsSettings.Build() if err != nil { return nil, newError("Failed to build XTLS config.").Base(err) } tm := serial.ToTypedMessage(ts) config.SecuritySettings = append(config.SecuritySettings, tm) config.SecurityType = tm.Type } if c.TCPSettings != nil { ts, err := c.TCPSettings.Build() if err != nil { return nil, newError("Failed to build TCP config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "tcp", Settings: serial.ToTypedMessage(ts), }) } if c.KCPSettings != nil { ts, err := c.KCPSettings.Build() if err != nil { return nil, newError("Failed to build mKCP config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "mkcp", Settings: serial.ToTypedMessage(ts), }) } if c.WSSettings != nil { ts, err := c.WSSettings.Build() if err != nil { return nil, newError("Failed to build WebSocket config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "websocket", Settings: serial.ToTypedMessage(ts), }) } if c.HTTPSettings != nil { ts, err := c.HTTPSettings.Build() if err != nil { return nil, newError("Failed to build HTTP config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "http", Settings: serial.ToTypedMessage(ts), }) } if c.DSSettings != nil { ds, err := c.DSSettings.Build() if err != nil { return nil, newError("Failed to build DomainSocket config.").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "domainsocket", Settings: serial.ToTypedMessage(ds), }) } if c.QUICSettings != nil { qs, err := c.QUICSettings.Build() if err != nil { return nil, newError("Failed to build QUIC config").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "quic", Settings: serial.ToTypedMessage(qs), }) } if c.SocketSettings != nil { ss, err := c.SocketSettings.Build() if err != nil { return nil, newError("Failed to build sockopt").Base(err) } config.SocketSettings = ss } return config, nil } type ProxyConfig struct { Tag string `json:"tag"` } // Build implements Buildable. func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) { if v.Tag == "" { return nil, newError("Proxy tag is not set.") } return &internet.ProxyConfig{ Tag: v.Tag, }, nil } ================================================ FILE: infra/conf/transport_test.go ================================================ package conf_test import ( "encoding/json" "testing" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/headers/http" "v2ray.com/core/transport/internet/headers/noop" "v2ray.com/core/transport/internet/headers/tls" "v2ray.com/core/transport/internet/kcp" "v2ray.com/core/transport/internet/quic" "v2ray.com/core/transport/internet/tcp" "v2ray.com/core/transport/internet/websocket" ) func TestSocketConfig(t *testing.T) { createParser := func() func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { config := new(SocketConfig) if err := json.Unmarshal([]byte(s), config); err != nil { return nil, err } return config.Build() } } runMultiTestCase(t, []TestCase{ { Input: `{ "mark": 1, "tcpFastOpen": true }`, Parser: createParser(), Output: &internet.SocketConfig{ Mark: 1, Tfo: internet.SocketConfig_Enable, }, }, }) } func TestTransportConfig(t *testing.T) { createParser := func() func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { config := new(TransportConfig) if err := json.Unmarshal([]byte(s), config); err != nil { return nil, err } return config.Build() } } runMultiTestCase(t, []TestCase{ { Input: `{ "tcpSettings": { "header": { "type": "http", "request": { "version": "1.1", "method": "GET", "path": "/b", "headers": { "a": "b", "c": "d" } }, "response": { "version": "1.0", "status": "404", "reason": "Not Found" } } }, "kcpSettings": { "mtu": 1200, "header": { "type": "none" } }, "wsSettings": { "path": "/t" }, "quicSettings": { "key": "abcd", "header": { "type": "dtls" } } }`, Parser: createParser(), Output: &transport.Config{ TransportSettings: []*internet.TransportConfig{ { ProtocolName: "tcp", Settings: serial.ToTypedMessage(&tcp.Config{ HeaderSettings: serial.ToTypedMessage(&http.Config{ Request: &http.RequestConfig{ Version: &http.Version{Value: "1.1"}, Method: &http.Method{Value: "GET"}, Uri: []string{"/b"}, Header: []*http.Header{ {Name: "a", Value: []string{"b"}}, {Name: "c", Value: []string{"d"}}, }, }, Response: &http.ResponseConfig{ Version: &http.Version{Value: "1.0"}, Status: &http.Status{Code: "404", Reason: "Not Found"}, Header: []*http.Header{ { Name: "Content-Type", Value: []string{"application/octet-stream", "video/mpeg"}, }, { Name: "Transfer-Encoding", Value: []string{"chunked"}, }, { Name: "Connection", Value: []string{"keep-alive"}, }, { Name: "Pragma", Value: []string{"no-cache"}, }, { Name: "Cache-Control", Value: []string{"private", "no-cache"}, }, }, }, }), }), }, { ProtocolName: "mkcp", Settings: serial.ToTypedMessage(&kcp.Config{ Mtu: &kcp.MTU{Value: 1200}, HeaderConfig: serial.ToTypedMessage(&noop.Config{}), }), }, { ProtocolName: "websocket", Settings: serial.ToTypedMessage(&websocket.Config{ Path: "/t", }), }, { ProtocolName: "quic", Settings: serial.ToTypedMessage(&quic.Config{ Key: "abcd", Security: &protocol.SecurityConfig{ Type: protocol.SecurityType_NONE, }, Header: serial.ToTypedMessage(&tls.PacketConfig{}), }), }, }, }, }, }) } ================================================ FILE: infra/conf/trojan.go ================================================ package conf import ( "encoding/json" "runtime" "strconv" "syscall" "github.com/golang/protobuf/proto" // nolint: staticcheck "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/trojan" ) // TrojanServerTarget is configuration of a single trojan server type TrojanServerTarget struct { Address *Address `json:"address"` Port uint16 `json:"port"` Password string `json:"password"` Email string `json:"email"` Level byte `json:"level"` } // TrojanClientConfig is configuration of trojan servers type TrojanClientConfig struct { Servers []*TrojanServerTarget `json:"servers"` } // Build implements Buildable func (c *TrojanClientConfig) Build() (proto.Message, error) { config := new(trojan.ClientConfig) if len(c.Servers) == 0 { return nil, newError("0 Trojan server configured.") } serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers)) for idx, rec := range c.Servers { if rec.Address == nil { return nil, newError("Trojan server address is not set.") } if rec.Port == 0 { return nil, newError("Invalid Trojan port.") } if rec.Password == "" { return nil, newError("Trojan password is not specified.") } account := &trojan.Account{ Password: rec.Password, } trojan := &protocol.ServerEndpoint{ Address: rec.Address.Build(), Port: uint32(rec.Port), User: []*protocol.User{ { Level: uint32(rec.Level), Email: rec.Email, Account: serial.ToTypedMessage(account), }, }, } serverSpecs[idx] = trojan } config.Server = serverSpecs return config, nil } // TrojanInboundFallback is fallback configuration type TrojanInboundFallback struct { Alpn string `json:"alpn"` Path string `json:"path"` Type string `json:"type"` Dest json.RawMessage `json:"dest"` Xver uint64 `json:"xver"` } // TrojanUserConfig is user configuration type TrojanUserConfig struct { Password string `json:"password"` Level byte `json:"level"` Email string `json:"email"` } // TrojanServerConfig is Inbound configuration type TrojanServerConfig struct { Clients []*TrojanUserConfig `json:"clients"` Fallback json.RawMessage `json:"fallback"` Fallbacks []*TrojanInboundFallback `json:"fallbacks"` } // Build implements Buildable func (c *TrojanServerConfig) Build() (proto.Message, error) { config := new(trojan.ServerConfig) if len(c.Clients) == 0 { return nil, newError("No trojan user settings.") } config.Users = make([]*protocol.User, len(c.Clients)) for idx, rawUser := range c.Clients { user := new(protocol.User) account := &trojan.Account{ Password: rawUser.Password, } user.Email = rawUser.Email user.Level = uint32(rawUser.Level) user.Account = serial.ToTypedMessage(account) config.Users[idx] = user } if c.Fallback != nil { return nil, newError(`Trojan settings: please use "fallbacks":[{}] instead of "fallback":{}`) } for _, fb := range c.Fallbacks { var i uint16 var s string if err := json.Unmarshal(fb.Dest, &i); err == nil { s = strconv.Itoa(int(i)) } else { _ = json.Unmarshal(fb.Dest, &s) } config.Fallbacks = append(config.Fallbacks, &trojan.Fallback{ Alpn: fb.Alpn, Path: fb.Path, Type: fb.Type, Dest: s, Xver: fb.Xver, }) } for _, fb := range config.Fallbacks { /* if fb.Alpn == "h2" && fb.Path != "" { return nil, newError(`Trojan fallbacks: "alpn":"h2" doesn't support "path"`) } */ if fb.Path != "" && fb.Path[0] != '/' { return nil, newError(`Trojan fallbacks: "path" must be empty or start with "/"`) } if fb.Type == "" && fb.Dest != "" { if fb.Dest == "serve-ws-none" { fb.Type = "serve" } else { switch fb.Dest[0] { case '@', '/': fb.Type = "unix" if fb.Dest[0] == '@' && len(fb.Dest) > 1 && fb.Dest[1] == '@' && runtime.GOOS == "linux" { fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work in front of haproxy copy(fullAddr, fb.Dest[1:]) fb.Dest = string(fullAddr) } default: if _, err := strconv.Atoi(fb.Dest); err == nil { fb.Dest = "127.0.0.1:" + fb.Dest } if _, _, err := net.SplitHostPort(fb.Dest); err == nil { fb.Type = "tcp" } } } } if fb.Type == "" { return nil, newError(`Trojan fallbacks: please fill in a valid value for every "dest"`) } if fb.Xver > 2 { return nil, newError(`Trojan fallbacks: invalid PROXY protocol version, "xver" only accepts 0, 1, 2`) } } return config, nil } ================================================ FILE: infra/conf/v2ray.go ================================================ package conf import ( "encoding/json" "log" "os" "strings" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/stats" "v2ray.com/core/common/serial" "v2ray.com/core/transport/internet/xtls" ) var ( inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, "http": func() interface{} { return new(HttpServerConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, "socks": func() interface{} { return new(SocksServerConfig) }, "vless": func() interface{} { return new(VLessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) }, "mtproto": func() interface{} { return new(MTProtoServerConfig) }, }, "protocol", "settings") outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ "blackhole": func() interface{} { return new(BlackholeConfig) }, "freedom": func() interface{} { return new(FreedomConfig) }, "http": func() interface{} { return new(HttpClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, "socks": func() interface{} { return new(SocksClientConfig) }, "vless": func() interface{} { return new(VLessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) }, "trojan": func() interface{} { return new(TrojanClientConfig) }, "mtproto": func() interface{} { return new(MTProtoClientConfig) }, "dns": func() interface{} { return new(DnsOutboundConfig) }, }, "protocol", "settings") ctllog = log.New(os.Stderr, "v2ctl> ", 0) ) func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) { kp := make([]proxyman.KnownProtocols, 0, 8) for _, p := range s { switch strings.ToLower(p) { case "http": kp = append(kp, proxyman.KnownProtocols_HTTP) case "https", "tls", "ssl": kp = append(kp, proxyman.KnownProtocols_TLS) default: return nil, newError("Unknown protocol: ", p) } } return kp, nil } type SniffingConfig struct { Enabled bool `json:"enabled"` DestOverride *StringList `json:"destOverride"` } // Build implements Buildable. func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { var p []string if c.DestOverride != nil { for _, domainOverride := range *c.DestOverride { switch strings.ToLower(domainOverride) { case "http": p = append(p, "http") case "tls", "https", "ssl": p = append(p, "tls") default: return nil, newError("unknown protocol: ", domainOverride) } } } return &proxyman.SniffingConfig{ Enabled: c.Enabled, DestinationOverride: p, }, nil } type MuxConfig struct { Enabled bool `json:"enabled"` Concurrency int16 `json:"concurrency"` } // Build creates MultiplexingConfig, Concurrency < 0 completely disables mux. func (m *MuxConfig) Build() *proxyman.MultiplexingConfig { if m.Concurrency < 0 { return nil } var con uint32 = 8 if m.Concurrency > 0 { con = uint32(m.Concurrency) } return &proxyman.MultiplexingConfig{ Enabled: m.Enabled, Concurrency: con, } } type InboundDetourAllocationConfig struct { Strategy string `json:"strategy"` Concurrency *uint32 `json:"concurrency"` RefreshMin *uint32 `json:"refresh"` } // Build implements Buildable. func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) { config := new(proxyman.AllocationStrategy) switch strings.ToLower(c.Strategy) { case "always": config.Type = proxyman.AllocationStrategy_Always case "random": config.Type = proxyman.AllocationStrategy_Random case "external": config.Type = proxyman.AllocationStrategy_External default: return nil, newError("unknown allocation strategy: ", c.Strategy) } if c.Concurrency != nil { config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ Value: *c.Concurrency, } } if c.RefreshMin != nil { config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{ Value: *c.RefreshMin, } } return config, nil } type InboundDetourConfig struct { Protocol string `json:"protocol"` PortRange *PortRange `json:"port"` ListenOn *Address `json:"listen"` Settings *json.RawMessage `json:"settings"` Tag string `json:"tag"` Allocation *InboundDetourAllocationConfig `json:"allocate"` StreamSetting *StreamConfig `json:"streamSettings"` DomainOverride *StringList `json:"domainOverride"` SniffingConfig *SniffingConfig `json:"sniffing"` } // Build implements Buildable. func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { receiverSettings := &proxyman.ReceiverConfig{} if c.PortRange == nil { return nil, newError("port range not specified in InboundDetour.") } receiverSettings.PortRange = c.PortRange.Build() if c.ListenOn != nil { if c.ListenOn.Family().IsDomain() { return nil, newError("unable to listen on domain address: ", c.ListenOn.Domain()) } receiverSettings.Listen = c.ListenOn.Build() } if c.Allocation != nil { concurrency := -1 if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" { concurrency = int(*c.Allocation.Concurrency) } portRange := int(c.PortRange.To - c.PortRange.From + 1) if concurrency >= 0 && concurrency >= portRange { return nil, newError("not enough ports. concurrency = ", concurrency, " ports: ", c.PortRange.From, " - ", c.PortRange.To) } as, err := c.Allocation.Build() if err != nil { return nil, err } receiverSettings.AllocationStrategy = as } if c.StreamSetting != nil { ss, err := c.StreamSetting.Build() if err != nil { return nil, err } if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") { return nil, newError("XTLS only supports VLESS for now.") } receiverSettings.StreamSettings = ss } if c.SniffingConfig != nil { s, err := c.SniffingConfig.Build() if err != nil { return nil, newError("failed to build sniffing config").Base(err) } receiverSettings.SniffingSettings = s } if c.DomainOverride != nil { kp, err := toProtocolList(*c.DomainOverride) if err != nil { return nil, newError("failed to parse inbound detour config").Base(err) } receiverSettings.DomainOverride = kp } settings := []byte("{}") if c.Settings != nil { settings = ([]byte)(*c.Settings) } rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol) if err != nil { return nil, newError("failed to load inbound detour config.").Base(err) } if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok { receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect } ts, err := rawConfig.(Buildable).Build() if err != nil { return nil, err } return &core.InboundHandlerConfig{ Tag: c.Tag, ReceiverSettings: serial.ToTypedMessage(receiverSettings), ProxySettings: serial.ToTypedMessage(ts), }, nil } type OutboundDetourConfig struct { Protocol string `json:"protocol"` SendThrough *Address `json:"sendThrough"` Tag string `json:"tag"` Settings *json.RawMessage `json:"settings"` StreamSetting *StreamConfig `json:"streamSettings"` ProxySettings *ProxyConfig `json:"proxySettings"` MuxSettings *MuxConfig `json:"mux"` } // Build implements Buildable. func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { senderSettings := &proxyman.SenderConfig{} if c.SendThrough != nil { address := c.SendThrough if address.Family().IsDomain() { return nil, newError("unable to send through: " + address.String()) } senderSettings.Via = address.Build() } if c.StreamSetting != nil { ss, err := c.StreamSetting.Build() if err != nil { return nil, err } if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") { return nil, newError("XTLS only supports VLESS for now.") } senderSettings.StreamSettings = ss } if c.ProxySettings != nil { ps, err := c.ProxySettings.Build() if err != nil { return nil, newError("invalid outbound detour proxy settings.").Base(err) } senderSettings.ProxySettings = ps } if c.MuxSettings != nil { ms := c.MuxSettings.Build() if ms != nil && ms.Enabled { if ss := senderSettings.StreamSettings; ss != nil { if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) { return nil, newError("XTLS doesn't support Mux for now.") } } } senderSettings.MultiplexSettings = ms } settings := []byte("{}") if c.Settings != nil { settings = ([]byte)(*c.Settings) } rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol) if err != nil { return nil, newError("failed to parse to outbound detour config.").Base(err) } ts, err := rawConfig.(Buildable).Build() if err != nil { return nil, err } return &core.OutboundHandlerConfig{ SenderSettings: serial.ToTypedMessage(senderSettings), Tag: c.Tag, ProxySettings: serial.ToTypedMessage(ts), }, nil } type StatsConfig struct{} // Build implements Buildable. func (c *StatsConfig) Build() (*stats.Config, error) { return &stats.Config{}, nil } type Config struct { Port uint16 `json:"port"` // Port of this Point server. Deprecated. LogConfig *LogConfig `json:"log"` RouterConfig *RouterConfig `json:"routing"` DNSConfig *DnsConfig `json:"dns"` InboundConfigs []InboundDetourConfig `json:"inbounds"` OutboundConfigs []OutboundDetourConfig `json:"outbounds"` InboundConfig *InboundDetourConfig `json:"inbound"` // Deprecated. OutboundConfig *OutboundDetourConfig `json:"outbound"` // Deprecated. InboundDetours []InboundDetourConfig `json:"inboundDetour"` // Deprecated. OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` // Deprecated. Transport *TransportConfig `json:"transport"` Policy *PolicyConfig `json:"policy"` Api *ApiConfig `json:"api"` Stats *StatsConfig `json:"stats"` Reverse *ReverseConfig `json:"reverse"` } func (c *Config) findInboundTag(tag string) int { found := -1 for idx, ib := range c.InboundConfigs { if ib.Tag == tag { found = idx break } } return found } func (c *Config) findOutboundTag(tag string) int { found := -1 for idx, ob := range c.OutboundConfigs { if ob.Tag == tag { found = idx break } } return found } // Override method accepts another Config overrides the current attribute func (c *Config) Override(o *Config, fn string) { // only process the non-deprecated members if o.LogConfig != nil { c.LogConfig = o.LogConfig } if o.RouterConfig != nil { c.RouterConfig = o.RouterConfig } if o.DNSConfig != nil { c.DNSConfig = o.DNSConfig } if o.Transport != nil { c.Transport = o.Transport } if o.Policy != nil { c.Policy = o.Policy } if o.Api != nil { c.Api = o.Api } if o.Stats != nil { c.Stats = o.Stats } if o.Reverse != nil { c.Reverse = o.Reverse } // deprecated attrs... keep them for now if o.InboundConfig != nil { c.InboundConfig = o.InboundConfig } if o.OutboundConfig != nil { c.OutboundConfig = o.OutboundConfig } if o.InboundDetours != nil { c.InboundDetours = o.InboundDetours } if o.OutboundDetours != nil { c.OutboundDetours = o.OutboundDetours } // deprecated attrs // update the Inbound in slice if the only one in overide config has same tag if len(o.InboundConfigs) > 0 { if len(c.InboundConfigs) > 0 && len(o.InboundConfigs) == 1 { if idx := c.findInboundTag(o.InboundConfigs[0].Tag); idx > -1 { c.InboundConfigs[idx] = o.InboundConfigs[0] ctllog.Println("[", fn, "] updated inbound with tag: ", o.InboundConfigs[0].Tag) } else { c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[0]) ctllog.Println("[", fn, "] appended inbound with tag: ", o.InboundConfigs[0].Tag) } } else { c.InboundConfigs = o.InboundConfigs } } // update the Outbound in slice if the only one in overide config has same tag if len(o.OutboundConfigs) > 0 { if len(c.OutboundConfigs) > 0 && len(o.OutboundConfigs) == 1 { if idx := c.findOutboundTag(o.OutboundConfigs[0].Tag); idx > -1 { c.OutboundConfigs[idx] = o.OutboundConfigs[0] ctllog.Println("[", fn, "] updated outbound with tag: ", o.OutboundConfigs[0].Tag) } else { if strings.Contains(strings.ToLower(fn), "tail") { c.OutboundConfigs = append(c.OutboundConfigs, o.OutboundConfigs[0]) ctllog.Println("[", fn, "] appended outbound with tag: ", o.OutboundConfigs[0].Tag) } else { c.OutboundConfigs = append(o.OutboundConfigs, c.OutboundConfigs...) ctllog.Println("[", fn, "] prepended outbound with tag: ", o.OutboundConfigs[0].Tag) } } } else { c.OutboundConfigs = o.OutboundConfigs } } } func applyTransportConfig(s *StreamConfig, t *TransportConfig) { if s.TCPSettings == nil { s.TCPSettings = t.TCPConfig } if s.KCPSettings == nil { s.KCPSettings = t.KCPConfig } if s.WSSettings == nil { s.WSSettings = t.WSConfig } if s.HTTPSettings == nil { s.HTTPSettings = t.HTTPConfig } if s.DSSettings == nil { s.DSSettings = t.DSConfig } } // Build implements Buildable. func (c *Config) Build() (*core.Config, error) { config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, } if c.Api != nil { apiConf, err := c.Api.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(apiConf)) } if c.Stats != nil { statsConf, err := c.Stats.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(statsConf)) } var logConfMsg *serial.TypedMessage if c.LogConfig != nil { logConfMsg = serial.ToTypedMessage(c.LogConfig.Build()) } else { logConfMsg = serial.ToTypedMessage(DefaultLogConfig()) } // let logger module be the first App to start, // so that other modules could print log during initiating config.App = append([]*serial.TypedMessage{logConfMsg}, config.App...) if c.RouterConfig != nil { routerConfig, err := c.RouterConfig.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(routerConfig)) } if c.DNSConfig != nil { dnsApp, err := c.DNSConfig.Build() if err != nil { return nil, newError("failed to parse DNS config").Base(err) } config.App = append(config.App, serial.ToTypedMessage(dnsApp)) } if c.Policy != nil { pc, err := c.Policy.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(pc)) } if c.Reverse != nil { r, err := c.Reverse.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } var inbounds []InboundDetourConfig if c.InboundConfig != nil { inbounds = append(inbounds, *c.InboundConfig) } if len(c.InboundDetours) > 0 { inbounds = append(inbounds, c.InboundDetours...) } if len(c.InboundConfigs) > 0 { inbounds = append(inbounds, c.InboundConfigs...) } // Backward compatibility. if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 { inbounds[0].PortRange = &PortRange{ From: uint32(c.Port), To: uint32(c.Port), } } for _, rawInboundConfig := range inbounds { if c.Transport != nil { if rawInboundConfig.StreamSetting == nil { rawInboundConfig.StreamSetting = &StreamConfig{} } applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport) } ic, err := rawInboundConfig.Build() if err != nil { return nil, err } config.Inbound = append(config.Inbound, ic) } var outbounds []OutboundDetourConfig if c.OutboundConfig != nil { outbounds = append(outbounds, *c.OutboundConfig) } if len(c.OutboundDetours) > 0 { outbounds = append(outbounds, c.OutboundDetours...) } if len(c.OutboundConfigs) > 0 { outbounds = append(outbounds, c.OutboundConfigs...) } for _, rawOutboundConfig := range outbounds { if c.Transport != nil { if rawOutboundConfig.StreamSetting == nil { rawOutboundConfig.StreamSetting = &StreamConfig{} } applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport) } oc, err := rawOutboundConfig.Build() if err != nil { return nil, err } config.Outbound = append(config.Outbound, oc) } return config, nil } ================================================ FILE: infra/conf/v2ray_test.go ================================================ package conf_test import ( "encoding/json" "reflect" "testing" "github.com/golang/protobuf/proto" "github.com/google/go-cmp/cmp" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/router" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/blackhole" dns_proxy "v2ray.com/core/proxy/dns" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/http" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/websocket" ) func TestV2RayConfig(t *testing.T) { createParser := func() func(string) (proto.Message, error) { return func(s string) (proto.Message, error) { config := new(Config) if err := json.Unmarshal([]byte(s), config); err != nil { return nil, err } return config.Build() } } runMultiTestCase(t, []TestCase{ { Input: `{ "outbound": { "protocol": "freedom", "settings": {} }, "log": { "access": "/var/log/v2ray/access.log", "loglevel": "error", "error": "/var/log/v2ray/error.log" }, "inbound": { "streamSettings": { "network": "ws", "wsSettings": { "headers": { "host": "example.domain" }, "path": "" }, "tlsSettings": { "alpn": "h2" }, "security": "tls" }, "protocol": "vmess", "port": 443, "settings": { "clients": [ { "alterId": 100, "security": "aes-128-gcm", "id": "0cdf8a45-303d-4fed-9780-29aa7f54175e" } ] } }, "inbounds": [{ "streamSettings": { "network": "ws", "wsSettings": { "headers": { "host": "example.domain" }, "path": "" }, "tlsSettings": { "alpn": "h2" }, "security": "tls" }, "protocol": "vmess", "port": "443-500", "allocate": { "strategy": "random", "concurrency": 3 }, "settings": { "clients": [ { "alterId": 100, "security": "aes-128-gcm", "id": "0cdf8a45-303d-4fed-9780-29aa7f54175e" } ] } }], "outboundDetour": [ { "tag": "blocked", "protocol": "blackhole" }, { "protocol": "dns" } ], "routing": { "strategy": "rules", "settings": { "rules": [ { "ip": [ "10.0.0.0/8" ], "type": "field", "outboundTag": "blocked" } ] } }, "transport": { "httpSettings": { "path": "/test" } } }`, Parser: createParser(), Output: &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogType: log.LogType_File, ErrorLogPath: "/var/log/v2ray/error.log", ErrorLogLevel: clog.Severity_Error, AccessLogType: log.LogType_File, AccessLogPath: "/var/log/v2ray/access.log", }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&router.Config{ DomainStrategy: router.Config_AsIs, Rule: []*router.RoutingRule{ { Geoip: []*router.GeoIP{ { Cidr: []*router.CIDR{ { Ip: []byte{10, 0, 0, 0}, Prefix: 8, }, }, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "blocked", }, }, }, }), }, Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ ProtocolName: "tcp", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "http", Settings: serial.ToTypedMessage(&http.Config{ Path: "/test", }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&freedom.Config{ DomainStrategy: freedom.Config_AS_IS, UserLevel: 0, }), }, { Tag: "blocked", SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ ProtocolName: "tcp", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "http", Settings: serial.ToTypedMessage(&http.Config{ Path: "/test", }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ ProtocolName: "tcp", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "http", Settings: serial.ToTypedMessage(&http.Config{ Path: "/test", }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{ Server: &net.Endpoint{}, }), }, }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: &net.PortRange{ From: 443, To: 443, }, StreamSettings: &internet.StreamConfig{ ProtocolName: "websocket", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "websocket", Settings: serial.ToTypedMessage(&websocket.Config{ Header: []*websocket.Header{ { Key: "host", Value: "example.domain", }, }, }), }, { ProtocolName: "http", Settings: serial.ToTypedMessage(&http.Config{ Path: "/test", }), }, }, SecurityType: "v2ray.core.transport.internet.tls.Config", SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ NextProtocol: []string{"h2"}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Level: 0, Account: serial.ToTypedMessage(&vmess.Account{ Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e", AlterId: 100, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: &net.PortRange{ From: 443, To: 500, }, AllocationStrategy: &proxyman.AllocationStrategy{ Type: proxyman.AllocationStrategy_Random, Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ Value: 3, }, }, StreamSettings: &internet.StreamConfig{ ProtocolName: "websocket", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "websocket", Settings: serial.ToTypedMessage(&websocket.Config{ Header: []*websocket.Header{ { Key: "host", Value: "example.domain", }, }, }), }, { ProtocolName: "http", Settings: serial.ToTypedMessage(&http.Config{ Path: "/test", }), }, }, SecurityType: "v2ray.core.transport.internet.tls.Config", SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ NextProtocol: []string{"h2"}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Level: 0, Account: serial.ToTypedMessage(&vmess.Account{ Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e", AlterId: 100, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }), }, }, }, }, }) } func TestMuxConfig_Build(t *testing.T) { tests := []struct { name string fields string want *proxyman.MultiplexingConfig }{ {"default", `{"enabled": true, "concurrency": 16}`, &proxyman.MultiplexingConfig{ Enabled: true, Concurrency: 16, }}, {"empty def", `{}`, &proxyman.MultiplexingConfig{ Enabled: false, Concurrency: 8, }}, {"not enable", `{"enabled": false, "concurrency": 4}`, &proxyman.MultiplexingConfig{ Enabled: false, Concurrency: 4, }}, {"forbidden", `{"enabled": false, "concurrency": -1}`, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := &MuxConfig{} common.Must(json.Unmarshal([]byte(tt.fields), m)) if got := m.Build(); !reflect.DeepEqual(got, tt.want) { t.Errorf("MuxConfig.Build() = %v, want %v", got, tt.want) } }) } } func TestConfig_Override(t *testing.T) { tests := []struct { name string orig *Config over *Config fn string want *Config }{ {"combine/empty", &Config{}, &Config{ LogConfig: &LogConfig{}, RouterConfig: &RouterConfig{}, DNSConfig: &DnsConfig{}, Transport: &TransportConfig{}, Policy: &PolicyConfig{}, Api: &ApiConfig{}, Stats: &StatsConfig{}, Reverse: &ReverseConfig{}, }, "", &Config{ LogConfig: &LogConfig{}, RouterConfig: &RouterConfig{}, DNSConfig: &DnsConfig{}, Transport: &TransportConfig{}, Policy: &PolicyConfig{}, Api: &ApiConfig{}, Stats: &StatsConfig{}, Reverse: &ReverseConfig{}, }, }, {"combine/newattr", &Config{InboundConfigs: []InboundDetourConfig{{Tag: "old"}}}, &Config{LogConfig: &LogConfig{}}, "", &Config{LogConfig: &LogConfig{}, InboundConfigs: []InboundDetourConfig{{Tag: "old"}}}}, {"replace/inbounds", &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/inbounds-replaceall", &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}, "", &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}}, {"replace/notag-append", &Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: "vmess"}}}, &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", &Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: "vmess"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/outbounds", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/outbounds-prepend", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}, "config.json", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}}, {"replace/outbounds-append", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos2", Protocol: "kcp"}}}, "config_tail.json", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}, {Tag: "pos2", Protocol: "kcp"}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.orig.Override(tt.over, tt.fn) if r := cmp.Diff(tt.orig, tt.want); r != "" { t.Error(r) } }) } } ================================================ FILE: infra/conf/vless.go ================================================ package conf import ( "encoding/json" "runtime" "strconv" "syscall" "github.com/golang/protobuf/proto" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/vless" "v2ray.com/core/proxy/vless/inbound" "v2ray.com/core/proxy/vless/outbound" ) type VLessInboundFallback struct { Alpn string `json:"alpn"` Path string `json:"path"` Type string `json:"type"` Dest json.RawMessage `json:"dest"` Xver uint64 `json:"xver"` } type VLessInboundConfig struct { Clients []json.RawMessage `json:"clients"` Decryption string `json:"decryption"` Fallback json.RawMessage `json:"fallback"` Fallbacks []*VLessInboundFallback `json:"fallbacks"` } // Build implements Buildable func (c *VLessInboundConfig) Build() (proto.Message, error) { config := new(inbound.Config) if len(c.Clients) == 0 { //return nil, newError(`VLESS settings: "clients" is empty`) } config.Clients = make([]*protocol.User, len(c.Clients)) for idx, rawUser := range c.Clients { user := new(protocol.User) if err := json.Unmarshal(rawUser, user); err != nil { return nil, newError(`VLESS clients: invalid user`).Base(err) } account := new(vless.Account) if err := json.Unmarshal(rawUser, account); err != nil { return nil, newError(`VLESS clients: invalid user`).Base(err) } switch account.Flow { case "", "xtls-rprx-origin", "xtls-rprx-direct": default: return nil, newError(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) } if account.Encryption != "" { return nil, newError(`VLESS clients: "encryption" should not in inbound settings`) } user.Account = serial.ToTypedMessage(account) config.Clients[idx] = user } if c.Decryption != "none" { return nil, newError(`VLESS settings: please add/set "decryption":"none" to every settings`) } config.Decryption = c.Decryption if c.Fallback != nil { return nil, newError(`VLESS settings: please use "fallbacks":[{}] instead of "fallback":{}`) } for _, fb := range c.Fallbacks { var i uint16 var s string if err := json.Unmarshal(fb.Dest, &i); err == nil { s = strconv.Itoa(int(i)) } else { _ = json.Unmarshal(fb.Dest, &s) } config.Fallbacks = append(config.Fallbacks, &inbound.Fallback{ Alpn: fb.Alpn, Path: fb.Path, Type: fb.Type, Dest: s, Xver: fb.Xver, }) } for _, fb := range config.Fallbacks { /* if fb.Alpn == "h2" && fb.Path != "" { return nil, newError(`VLESS fallbacks: "alpn":"h2" doesn't support "path"`) } */ if fb.Path != "" && fb.Path[0] != '/' { return nil, newError(`VLESS fallbacks: "path" must be empty or start with "/"`) } if fb.Type == "" && fb.Dest != "" { if fb.Dest == "serve-ws-none" { fb.Type = "serve" } else { switch fb.Dest[0] { case '@', '/': fb.Type = "unix" if fb.Dest[0] == '@' && len(fb.Dest) > 1 && fb.Dest[1] == '@' && runtime.GOOS == "linux" { fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work in front of haproxy copy(fullAddr, fb.Dest[1:]) fb.Dest = string(fullAddr) } default: if _, err := strconv.Atoi(fb.Dest); err == nil { fb.Dest = "127.0.0.1:" + fb.Dest } if _, _, err := net.SplitHostPort(fb.Dest); err == nil { fb.Type = "tcp" } } } } if fb.Type == "" { return nil, newError(`VLESS fallbacks: please fill in a valid value for every "dest"`) } if fb.Xver > 2 { return nil, newError(`VLESS fallbacks: invalid PROXY protocol version, "xver" only accepts 0, 1, 2`) } } return config, nil } type VLessOutboundVnext struct { Address *Address `json:"address"` Port uint16 `json:"port"` Users []json.RawMessage `json:"users"` } type VLessOutboundConfig struct { Vnext []*VLessOutboundVnext `json:"vnext"` } // Build implements Buildable func (c *VLessOutboundConfig) Build() (proto.Message, error) { config := new(outbound.Config) if len(c.Vnext) == 0 { return nil, newError(`VLESS settings: "vnext" is empty`) } config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext)) for idx, rec := range c.Vnext { if rec.Address == nil { return nil, newError(`VLESS vnext: "address" is not set`) } if len(rec.Users) == 0 { return nil, newError(`VLESS vnext: "users" is empty`) } spec := &protocol.ServerEndpoint{ Address: rec.Address.Build(), Port: uint32(rec.Port), User: make([]*protocol.User, len(rec.Users)), } for idx, rawUser := range rec.Users { user := new(protocol.User) if err := json.Unmarshal(rawUser, user); err != nil { return nil, newError(`VLESS users: invalid user`).Base(err) } account := new(vless.Account) if err := json.Unmarshal(rawUser, account); err != nil { return nil, newError(`VLESS users: invalid user`).Base(err) } switch account.Flow { case "", "xtls-rprx-origin", "xtls-rprx-origin-udp443", "xtls-rprx-direct", "xtls-rprx-direct-udp443": default: return nil, newError(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) } if account.Encryption != "none" { return nil, newError(`VLESS users: please add/set "encryption":"none" for every user`) } user.Account = serial.ToTypedMessage(account) spec.User[idx] = user } config.Vnext[idx] = spec } return config, nil } ================================================ FILE: infra/conf/vless_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/vless" "v2ray.com/core/proxy/vless/inbound" "v2ray.com/core/proxy/vless/outbound" ) func TestVLessOutbound(t *testing.T) { creator := func() Buildable { return new(VLessOutboundConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "vnext": [{ "address": "example.com", "port": 443, "users": [ { "id": "27848739-7e62-4138-9fd3-098a63964b6b", "flow": "xtls-rprx-origin-udp443", "encryption": "none", "level": 0 } ] }] }`, Parser: loadJSON(creator), Output: &outbound.Config{ Vnext: []*protocol.ServerEndpoint{ { Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Domain{ Domain: "example.com", }, }, Port: 443, User: []*protocol.User{ { Account: serial.ToTypedMessage(&vless.Account{ Id: "27848739-7e62-4138-9fd3-098a63964b6b", Flow: "xtls-rprx-origin-udp443", Encryption: "none", }), Level: 0, }, }, }, }, }, }, }) } func TestVLessInbound(t *testing.T) { creator := func() Buildable { return new(VLessInboundConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "clients": [ { "id": "27848739-7e62-4138-9fd3-098a63964b6b", "flow": "xtls-rprx-origin", "level": 0, "email": "love@v2fly.org" } ], "decryption": "none", "fallbacks": [ { "dest": 80 }, { "alpn": "h2", "dest": "@/dev/shm/domain.socket", "xver": 2 }, { "path": "/innerws", "dest": "serve-ws-none" } ] }`, Parser: loadJSON(creator), Output: &inbound.Config{ Clients: []*protocol.User{ { Account: serial.ToTypedMessage(&vless.Account{ Id: "27848739-7e62-4138-9fd3-098a63964b6b", Flow: "xtls-rprx-origin", }), Level: 0, Email: "love@v2fly.org", }, }, Decryption: "none", Fallbacks: []*inbound.Fallback{ { Alpn: "", Path: "", Type: "tcp", Dest: "127.0.0.1:80", Xver: 0, }, { Alpn: "h2", Path: "", Type: "unix", Dest: "@/dev/shm/domain.socket", Xver: 2, }, { Alpn: "", Path: "/innerws", Type: "serve", Dest: "serve-ws-none", Xver: 0, }, }, }, }, }) } ================================================ FILE: infra/conf/vmess.go ================================================ package conf import ( "encoding/json" "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" ) type VMessAccount struct { ID string `json:"id"` AlterIds uint16 `json:"alterId"` Security string `json:"security"` } // Build implements Buildable func (a *VMessAccount) Build() *vmess.Account { var st protocol.SecurityType switch strings.ToLower(a.Security) { case "aes-128-gcm": st = protocol.SecurityType_AES128_GCM case "chacha20-poly1305": st = protocol.SecurityType_CHACHA20_POLY1305 case "auto": st = protocol.SecurityType_AUTO case "none": st = protocol.SecurityType_NONE default: st = protocol.SecurityType_AUTO } return &vmess.Account{ Id: a.ID, AlterId: uint32(a.AlterIds), SecuritySettings: &protocol.SecurityConfig{ Type: st, }, } } type VMessDetourConfig struct { ToTag string `json:"to"` } // Build implements Buildable func (c *VMessDetourConfig) Build() *inbound.DetourConfig { return &inbound.DetourConfig{ To: c.ToTag, } } type FeaturesConfig struct { Detour *VMessDetourConfig `json:"detour"` } type VMessDefaultConfig struct { AlterIDs uint16 `json:"alterId"` Level byte `json:"level"` } // Build implements Buildable func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig { config := new(inbound.DefaultConfig) config.AlterId = uint32(c.AlterIDs) if config.AlterId == 0 { config.AlterId = 32 } config.Level = uint32(c.Level) return config } type VMessInboundConfig struct { Users []json.RawMessage `json:"clients"` Features *FeaturesConfig `json:"features"` Defaults *VMessDefaultConfig `json:"default"` DetourConfig *VMessDetourConfig `json:"detour"` SecureOnly bool `json:"disableInsecureEncryption"` } // Build implements Buildable func (c *VMessInboundConfig) Build() (proto.Message, error) { config := &inbound.Config{ SecureEncryptionOnly: c.SecureOnly, } if c.Defaults != nil { config.Default = c.Defaults.Build() } if c.DetourConfig != nil { config.Detour = c.DetourConfig.Build() } else if c.Features != nil && c.Features.Detour != nil { config.Detour = c.Features.Detour.Build() } config.User = make([]*protocol.User, len(c.Users)) for idx, rawData := range c.Users { user := new(protocol.User) if err := json.Unmarshal(rawData, user); err != nil { return nil, newError("invalid VMess user").Base(err) } account := new(VMessAccount) if err := json.Unmarshal(rawData, account); err != nil { return nil, newError("invalid VMess user").Base(err) } user.Account = serial.ToTypedMessage(account.Build()) config.User[idx] = user } return config, nil } type VMessOutboundTarget struct { Address *Address `json:"address"` Port uint16 `json:"port"` Users []json.RawMessage `json:"users"` } type VMessOutboundConfig struct { Receivers []*VMessOutboundTarget `json:"vnext"` } // Build implements Buildable func (c *VMessOutboundConfig) Build() (proto.Message, error) { config := new(outbound.Config) if len(c.Receivers) == 0 { return nil, newError("0 VMess receiver configured") } serverSpecs := make([]*protocol.ServerEndpoint, len(c.Receivers)) for idx, rec := range c.Receivers { if len(rec.Users) == 0 { return nil, newError("0 user configured for VMess outbound") } if rec.Address == nil { return nil, newError("address is not set in VMess outbound config") } spec := &protocol.ServerEndpoint{ Address: rec.Address.Build(), Port: uint32(rec.Port), } for _, rawUser := range rec.Users { user := new(protocol.User) if err := json.Unmarshal(rawUser, user); err != nil { return nil, newError("invalid VMess user").Base(err) } account := new(VMessAccount) if err := json.Unmarshal(rawUser, account); err != nil { return nil, newError("invalid VMess user").Base(err) } user.Account = serial.ToTypedMessage(account.Build()) spec.User = append(spec.User, user) } serverSpecs[idx] = spec } config.Receiver = serverSpecs return config, nil } ================================================ FILE: infra/conf/vmess_test.go ================================================ package conf_test import ( "testing" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" . "v2ray.com/core/infra/conf" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" ) func TestVMessOutbound(t *testing.T) { creator := func() Buildable { return new(VMessOutboundConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "vnext": [{ "address": "127.0.0.1", "port": 80, "users": [ { "id": "e641f5ad-9397-41e3-bf1a-e8740dfed019", "email": "love@v2ray.com", "level": 255 } ] }] }`, Parser: loadJSON(creator), Output: &outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: 80, User: []*protocol.User{ { Email: "love@v2ray.com", Level: 255, Account: serial.ToTypedMessage(&vmess.Account{ Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019", AlterId: 0, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AUTO, }, }), }, }, }, }, }, }, }) } func TestVMessInbound(t *testing.T) { creator := func() Buildable { return new(VMessInboundConfig) } runMultiTestCase(t, []TestCase{ { Input: `{ "clients": [ { "id": "27848739-7e62-4138-9fd3-098a63964b6b", "level": 0, "alterId": 16, "email": "love@v2ray.com", "security": "aes-128-gcm" } ], "default": { "level": 0, "alterId": 32 }, "detour": { "to": "tag_to_detour" }, "disableInsecureEncryption": true }`, Parser: loadJSON(creator), Output: &inbound.Config{ User: []*protocol.User{ { Level: 0, Email: "love@v2ray.com", Account: serial.ToTypedMessage(&vmess.Account{ Id: "27848739-7e62-4138-9fd3-098a63964b6b", AlterId: 16, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, Default: &inbound.DefaultConfig{ Level: 0, AlterId: 32, }, Detour: &inbound.DetourConfig{ To: "tag_to_detour", }, SecureEncryptionOnly: true, }, }, }) } ================================================ FILE: infra/control/api.go ================================================ package control import ( "context" "errors" "flag" "fmt" "strings" "time" "github.com/golang/protobuf/proto" "google.golang.org/grpc" logService "v2ray.com/core/app/log/command" statsService "v2ray.com/core/app/stats/command" "v2ray.com/core/common" ) type ApiCommand struct{} func (c *ApiCommand) Name() string { return "api" } func (c *ApiCommand) Description() Description { return Description{ Short: "Call V2Ray API", Usage: []string{ "v2ctl api [--server=127.0.0.1:8080] Service.Method Request", "Call an API in an V2Ray process.", "The following methods are currently supported:", "\tLoggerService.RestartLogger", "\tStatsService.GetStats", "\tStatsService.QueryStats", "API calls in this command have a timeout to the server of 3 seconds.", "Examples:", "v2ctl api --server=127.0.0.1:8080 LoggerService.RestartLogger '' ", "v2ctl api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: \"\" reset: false'", "v2ctl api --server=127.0.0.1:8080 StatsService.GetStats 'name: \"inbound>>>statin>>>traffic>>>downlink\" reset: false'", "v2ctl api --server=127.0.0.1:8080 StatsService.GetSysStats ''", }, } } func (c *ApiCommand) Execute(args []string) error { fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) serverAddrPtr := fs.String("server", "127.0.0.1:8080", "Server address") if err := fs.Parse(args); err != nil { return err } unnamedArgs := fs.Args() if len(unnamedArgs) < 2 { return newError("service name or request not specified.") } service, method := getServiceMethod(unnamedArgs[0]) handler, found := serivceHandlerMap[strings.ToLower(service)] if !found { return newError("unknown service: ", service) } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() conn, err := grpc.DialContext(ctx, *serverAddrPtr, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { return newError("failed to dial ", *serverAddrPtr).Base(err) } defer conn.Close() response, err := handler(ctx, conn, method, unnamedArgs[1]) if err != nil { return newError("failed to call service ", unnamedArgs[0]).Base(err) } fmt.Println(response) return nil } func getServiceMethod(s string) (string, string) { ss := strings.Split(s, ".") service := ss[0] var method string if len(ss) > 1 { method = ss[1] } return service, method } type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) var serivceHandlerMap = map[string]serviceHandler{ "statsservice": callStatsService, "loggerservice": callLogService, } func callLogService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) { client := logService.NewLoggerServiceClient(conn) switch strings.ToLower(method) { case "restartlogger": r := &logService.RestartLoggerRequest{} if err := proto.UnmarshalText(request, r); err != nil { return "", err } resp, err := client.RestartLogger(ctx, r) if err != nil { return "", err } return proto.MarshalTextString(resp), nil default: return "", errors.New("Unknown method: " + method) } } func callStatsService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) { client := statsService.NewStatsServiceClient(conn) switch strings.ToLower(method) { case "getstats": r := &statsService.GetStatsRequest{} if err := proto.UnmarshalText(request, r); err != nil { return "", err } resp, err := client.GetStats(ctx, r) if err != nil { return "", err } return proto.MarshalTextString(resp), nil case "querystats": r := &statsService.QueryStatsRequest{} if err := proto.UnmarshalText(request, r); err != nil { return "", err } resp, err := client.QueryStats(ctx, r) if err != nil { return "", err } return proto.MarshalTextString(resp), nil case "getsysstats": // SysStatsRequest is an empty message r := &statsService.SysStatsRequest{} resp, err := client.GetSysStats(ctx, r) if err != nil { return "", err } return proto.MarshalTextString(resp), nil default: return "", errors.New("Unknown method: " + method) } } func init() { common.Must(RegisterCommand(&ApiCommand{})) } ================================================ FILE: infra/control/cert.go ================================================ package control import ( "context" "crypto/x509" "encoding/json" "flag" "os" "strings" "time" "v2ray.com/core/common" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/common/task" ) type stringList []string func (l *stringList) String() string { return "String list" } func (l *stringList) Set(v string) error { if v == "" { return newError("empty value") } *l = append(*l, v) return nil } type jsonCert struct { Certificate []string `json:"certificate"` Key []string `json:"key"` } type CertificateCommand struct { } func (c *CertificateCommand) Name() string { return "cert" } func (c *CertificateCommand) Description() Description { return Description{ Short: "Generate TLS certificates.", Usage: []string{ "v2ctl cert [--ca] [--domain=v2ray.com] [--expire=240h]", "Generate new TLS certificate", "--ca The new certificate is a CA certificate", "--domain Common name for the certificate", "--expire Time until certificate expires. 240h = 10 days.", }, } } func (c *CertificateCommand) printJson(certificate *cert.Certificate) { certPEM, keyPEM := certificate.ToPEM() jCert := &jsonCert{ Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"), Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"), } content, err := json.MarshalIndent(jCert, "", " ") common.Must(err) os.Stdout.Write(content) os.Stdout.WriteString("\n") } func (c *CertificateCommand) writeFile(content []byte, name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() return common.Error2(f.Write(content)) } func (c *CertificateCommand) printFile(certificate *cert.Certificate, name string) error { certPEM, keyPEM := certificate.ToPEM() return task.Run(context.Background(), func() error { return c.writeFile(certPEM, name+"_cert.pem") }, func() error { return c.writeFile(keyPEM, name+"_key.pem") }) } func (c *CertificateCommand) Execute(args []string) error { fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) var domainNames stringList fs.Var(&domainNames, "domain", "Domain name for the certificate") commonName := fs.String("name", "V2Ray Inc", "The common name of this certificate") organization := fs.String("org", "V2Ray Inc", "Organization of the certificate") isCA := fs.Bool("ca", false, "Whether this certificate is a CA") jsonOutput := fs.Bool("json", true, "Print certificate in JSON format") fileOutput := fs.String("file", "", "Save certificate in file.") expire := fs.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.") if err := fs.Parse(args); err != nil { return err } var opts []cert.Option if *isCA { opts = append(opts, cert.Authority(*isCA)) opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature)) } opts = append(opts, cert.NotAfter(time.Now().Add(*expire))) opts = append(opts, cert.CommonName(*commonName)) if len(domainNames) > 0 { opts = append(opts, cert.DNSNames(domainNames...)) } opts = append(opts, cert.Organization(*organization)) cert, err := cert.Generate(nil, opts...) if err != nil { return newError("failed to generate TLS certificate").Base(err) } if *jsonOutput { c.printJson(cert) } if len(*fileOutput) > 0 { if err := c.printFile(cert, *fileOutput); err != nil { return err } } return nil } func init() { common.Must(RegisterCommand(&CertificateCommand{})) } ================================================ FILE: infra/control/command.go ================================================ package control import ( "fmt" "log" "os" "strings" ) type Description struct { Short string Usage []string } type Command interface { Name() string Description() Description Execute(args []string) error } var ( commandRegistry = make(map[string]Command) ctllog = log.New(os.Stderr, "v2ctl> ", 0) ) func RegisterCommand(cmd Command) error { entry := strings.ToLower(cmd.Name()) if entry == "" { return newError("empty command name") } commandRegistry[entry] = cmd return nil } func GetCommand(name string) Command { cmd, found := commandRegistry[name] if !found { return nil } return cmd } type hiddenCommand interface { Hidden() bool } func PrintUsage() { for name, cmd := range commandRegistry { if _, ok := cmd.(hiddenCommand); ok { continue } fmt.Println(" ", name, "\t\t\t", cmd.Description()) } } ================================================ FILE: infra/control/config.go ================================================ package control import ( "bytes" "io" "io/ioutil" "os" "strings" "github.com/golang/protobuf/proto" "v2ray.com/core/common" "v2ray.com/core/infra/conf" "v2ray.com/core/infra/conf/serial" ) // ConfigCommand is the json to pb convert struct type ConfigCommand struct{} // Name for cmd usage func (c *ConfigCommand) Name() string { return "config" } // Description for help usage func (c *ConfigCommand) Description() Description { return Description{ Short: "merge multiple json config", Usage: []string{"v2ctl config config.json c1.json c2.json .json"}, } } // Execute real work here. func (c *ConfigCommand) Execute(args []string) error { if len(args) < 1 { return newError("empty config list") } conf := &conf.Config{} for _, arg := range args { ctllog.Println("Read config: ", arg) r, err := c.LoadArg(arg) common.Must(err) c, err := serial.DecodeJSONConfig(r) if err != nil { ctllog.Fatalln(err) } conf.Override(c, arg) } pbConfig, err := conf.Build() if err != nil { return err } bytesConfig, err := proto.Marshal(pbConfig) if err != nil { return newError("failed to marshal proto config").Base(err) } if _, err := os.Stdout.Write(bytesConfig); err != nil { return newError("failed to write proto config").Base(err) } return nil } // LoadArg loads one arg, maybe an remote url, or local file path func (c *ConfigCommand) LoadArg(arg string) (out io.Reader, err error) { var data []byte if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { data, err = FetchHTTPContent(arg) } else if arg == "stdin:" { data, err = ioutil.ReadAll(os.Stdin) } else { data, err = ioutil.ReadFile(arg) } if err != nil { return } out = bytes.NewBuffer(data) return } func init() { common.Must(RegisterCommand(&ConfigCommand{})) } ================================================ FILE: infra/control/control.go ================================================ package control //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: infra/control/errors.generated.go ================================================ package control import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: infra/control/fetch.go ================================================ package control import ( "net/http" "net/url" "os" "strings" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" ) type FetchCommand struct{} func (c *FetchCommand) Name() string { return "fetch" } func (c *FetchCommand) Description() Description { return Description{ Short: "Fetch resources", Usage: []string{"v2ctl fetch "}, } } func (c *FetchCommand) Execute(args []string) error { if len(args) < 1 { return newError("empty url") } content, err := FetchHTTPContent(args[0]) if err != nil { return newError("failed to read HTTP response").Base(err) } os.Stdout.Write(content) return nil } // FetchHTTPContent dials https for remote content func FetchHTTPContent(target string) ([]byte, error) { parsedTarget, err := url.Parse(target) if err != nil { return nil, newError("invalid URL: ", target).Base(err) } if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { return nil, newError("invalid scheme: ", parsedTarget.Scheme) } client := &http.Client{ Timeout: 30 * time.Second, } resp, err := client.Do(&http.Request{ Method: "GET", URL: parsedTarget, Close: true, }) if err != nil { return nil, newError("failed to dial to ", target).Base(err) } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, newError("unexpected HTTP status code: ", resp.StatusCode) } content, err := buf.ReadAllToBytes(resp.Body) if err != nil { return nil, newError("failed to read HTTP response").Base(err) } return content, nil } func init() { common.Must(RegisterCommand(&FetchCommand{})) } ================================================ FILE: infra/control/love.go ================================================ package control import ( "bufio" "bytes" "compress/gzip" "encoding/base64" "fmt" "v2ray.com/core/common" "v2ray.com/core/common/platform" ) const content = "H4sIAAAAAAAC/4SVMaskNwzH+/kUW6izcSthMGrcqLhVk0rdQS5cSMg7Xu4S0vizB8meZd57M3ta2GHX/ukvyZZmY2ZKDMzCzJyY5yOlxKII1omsf+qkBiiC6WhbYsbkjDAfySQsJqD3jtrD0EBM3sBHzG3kUsrglIQREXonpd47kYIi4AHmgI9Wcq2jlJITC6JZJ+v3ECYzBMAHyYm392yuY4zWsjACmHZSh6l3A0JETzGlWZqDsnArpTg62mhJONhOdO90p97V1BAnteoaOcuummtrrtuERQwUiJwP8a4KGKcyxdOCw1spOY+WHueFqmakAIgUSSuhwKNgobxKXSLbtg6r5cFmBiAeF6yCkYycmv+BiCIiW8ScHa3DgxAuZQbRhFNrLTFo96RBmx9jKWWG5nBsjyJzuIkftUblonppZU5t5LzwIks5L1a4lijagQxLokbIYwxfytNDC+XQqrWW9fzAunhqh5/Tg8PuaMw0d/Tcw3iDO81bHfWM/AnutMh2xqSUntMzd3wHDy9iHMQz8bmUZYvqedTJ5GgOnrNt7FIbSlwXE3wDI19n/KA38MsLaP4l89b5F8AV3ESOMIEhIBgezHBc0H6xV9KbaXwMvPcNvIHcC0C7UPZQx4JVTb35/AneSQq+bAYXsBmY7TCRupF2NTdVm/+ch22xa0pvRERKqt1oxj9DUbXzU84Gvj5hc5a81SlAUwMwgEs4T9+7sg9lb9h+908MWiKV8xtWciVTmnB3tivRjNerfXdxpfEBbq2NUvLMM5R9NLuyQg8nXT0PIh1xPd/wrcV49oJ6zbZaPlj2V87IY9T3F2XCOcW2MbZyZd49H+9m81E1N9SxlU+ff/1y+/f3719vf7788+Ugv/ffbMIH7ZNj0dsT4WMHHwLPu/Rp2O75uh99AK+N2xn7ZHq1OK6gczkN+9ngdOl1Qvki5xwSR8vFX6D+9vXA97B/+fr5rz9u/738uP328urP19vfP759e3n9Xs6jamvqlfJ/AAAA//+YAMZjDgkAAA==" type LoveCommand struct{} func (*LoveCommand) Name() string { return "lovevictoria" } func (*LoveCommand) Hidden() bool { return false } func (c *LoveCommand) Description() Description { return Description{ Short: "", Usage: []string{""}, } } func (*LoveCommand) Execute([]string) error { c, err := base64.StdEncoding.DecodeString(content) common.Must(err) reader, err := gzip.NewReader(bytes.NewBuffer(c)) common.Must(err) b := make([]byte, 4096) nBytes, _ := reader.Read(b) bb := bytes.NewBuffer(b[:nBytes]) scanner := bufio.NewScanner(bb) for scanner.Scan() { s := scanner.Text() fmt.Print(s + platform.LineSeparator()) } return nil } func init() { common.Must(RegisterCommand(&LoveCommand{})) } ================================================ FILE: infra/control/main/BUILD ================================================ load("//infra/bazel:matrix.bzl", "SUPPORTED_MATRIX") load("//infra/control/main:targets.bzl", "gen_targets") package(default_visibility=["//visibility:public"]) gen_targets(SUPPORTED_MATRIX) ================================================ FILE: infra/control/main/main.go ================================================ package main import ( "flag" "fmt" "os" commlog "v2ray.com/core/common/log" // _ "v2ray.com/core/infra/conf/command" "v2ray.com/core/infra/control" ) func getCommandName() string { if len(os.Args) > 1 { return os.Args[1] } return "" } func main() { // let the v2ctl prints log at stderr commlog.RegisterHandler(commlog.NewLogger(commlog.CreateStderrLogWriter())) name := getCommandName() cmd := control.GetCommand(name) if cmd == nil { fmt.Fprintln(os.Stderr, "Unknown command:", name) fmt.Fprintln(os.Stderr) fmt.Println("v2ctl ") fmt.Println("Available commands:") control.PrintUsage() return } if err := cmd.Execute(os.Args[2:]); err != nil { hasError := false if err != flag.ErrHelp { fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr) hasError = true } for _, line := range cmd.Description().Usage { fmt.Println(line) } if hasError { os.Exit(-1) } } } ================================================ FILE: infra/control/main/targets.bzl ================================================ load("//infra/bazel:build.bzl", "foreign_go_binary") def gen_targets(matrix): pkg = "./infra/control/main" output = "v2ctl" for (os, arch, ver) in matrix: if arch in ["arm"]: bin_name = "v2ctl_" + os + "_" + arch + "_" + ver foreign_go_binary( name = bin_name, pkg = pkg, output = output, os = os, arch = arch, ver = ver, arm = ver, gotags = "confonly", ) else: bin_name = "v2ctl_" + os + "_" + arch foreign_go_binary( name = bin_name, pkg = pkg, output = output, os = os, arch = arch, ver = ver, gotags = "confonly", ) if arch in ["mips", "mipsle"]: bin_name = "v2ctl_" + os + "_" + arch + "_softfloat" foreign_go_binary( name = bin_name, pkg = pkg, output = output + "_softfloat", os = os, arch = arch, ver = ver, mips = "softfloat", gotags = "confonly", ) ================================================ FILE: infra/control/tlsping.go ================================================ package control import ( "crypto/tls" "crypto/x509" "flag" "fmt" "net" "v2ray.com/core/common" ) type TlsPingCommand struct{} func (c *TlsPingCommand) Name() string { return "tlsping" } func (c *TlsPingCommand) Description() Description { return Description{ Short: "Ping the domain with TLS handshake", Usage: []string{"v2ctl tlsping --ip "}, } } func printCertificates(certs []*x509.Certificate) { for _, cert := range certs { if len(cert.DNSNames) == 0 { continue } fmt.Println("Allowed domains: ", cert.DNSNames) } } func (c *TlsPingCommand) Execute(args []string) error { fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) ipStr := fs.String("ip", "", "IP address of the domain") if err := fs.Parse(args); err != nil { return newError("flag parsing").Base(err) } if fs.NArg() < 1 { return newError("domain not specified") } domain := fs.Arg(0) fmt.Println("Tls ping: ", domain) var ip net.IP if len(*ipStr) > 0 { v := net.ParseIP(*ipStr) if v == nil { return newError("invalid IP: ", *ipStr) } ip = v } else { v, err := net.ResolveIPAddr("ip", domain) if err != nil { return newError("resolve IP").Base(err) } ip = v.IP } fmt.Println("Using IP: ", ip.String()) fmt.Println("-------------------") fmt.Println("Pinging without SNI") { tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443}) if err != nil { return newError("dial tcp").Base(err) } tlsConn := tls.Client(tcpConn, &tls.Config{ InsecureSkipVerify: true, NextProtos: []string{"http/1.1"}, MaxVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12, }) err = tlsConn.Handshake() if err != nil { fmt.Println("Handshake failure: ", err) } else { fmt.Println("Handshake succeeded") printCertificates(tlsConn.ConnectionState().PeerCertificates) } tlsConn.Close() } fmt.Println("-------------------") fmt.Println("Pinging with SNI") { tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443}) if err != nil { return newError("dial tcp").Base(err) } tlsConn := tls.Client(tcpConn, &tls.Config{ ServerName: domain, NextProtos: []string{"http/1.1"}, MaxVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12, }) err = tlsConn.Handshake() if err != nil { fmt.Println("handshake failure: ", err) } else { fmt.Println("handshake succeeded") printCertificates(tlsConn.ConnectionState().PeerCertificates) } tlsConn.Close() } fmt.Println("Tls ping finished") return nil } func init() { common.Must(RegisterCommand(&TlsPingCommand{})) } ================================================ FILE: infra/control/uuid.go ================================================ package control import ( "fmt" "v2ray.com/core/common" "v2ray.com/core/common/uuid" ) type UUIDCommand struct{} func (c *UUIDCommand) Name() string { return "uuid" } func (c *UUIDCommand) Description() Description { return Description{ Short: "Generate new UUIDs", Usage: []string{"v2ctl uuid"}, } } func (c *UUIDCommand) Execute([]string) error { u := uuid.New() fmt.Println(u.String()) return nil } func init() { common.Must(RegisterCommand(&UUIDCommand{})) } ================================================ FILE: infra/control/verify.go ================================================ package control import ( "flag" "github.com/xiaokangwang/VSign/signerVerify" "os" "v2ray.com/core/common" ) type VerifyCommand struct{} func (c *VerifyCommand) Name() string { return "verify" } func (c *VerifyCommand) Description() Description { return Description{ Short: "Verify if a binary is officially signed.", Usage: []string{ "v2ctl verify --sig= file...", "Verify the file officially signed by V2Ray.", }, } } func (c *VerifyCommand) Execute(args []string) error { fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) sigFile := fs.String("sig", "", "Path to the signature file") if err := fs.Parse(args); err != nil { return err } target := fs.Arg(0) if target == "" { return newError("empty file path.") } if *sigFile == "" { return newError("empty signature path.") } sigReader, err := os.Open(os.ExpandEnv(*sigFile)) if err != nil { return newError("failed to open file ", *sigFile).Base(err) } files := fs.Args() err = signerVerify.OutputAndJudge(signerVerify.CheckSignaturesV2Fly(sigReader, files)) if err == nil { return nil } return newError("file is not officially signed by V2Ray").Base(err) } func init() { common.Must(RegisterCommand(&VerifyCommand{})) } ================================================ FILE: infra/vprotogen/main.go ================================================ package main import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "v2ray.com/core" "v2ray.com/core/common" ) func main() { pwd, wdErr := os.Getwd() if wdErr != nil { fmt.Println("Can not get current working directory.") os.Exit(1) } GOBIN := common.GetGOBIN() protoc := core.ProtocMap[runtime.GOOS] protoFilesMap := make(map[string][]string) walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Println(err) return err } if info.IsDir() { return nil } dir := filepath.Dir(path) filename := filepath.Base(path) if strings.HasSuffix(filename, ".proto") { protoFilesMap[dir] = append(protoFilesMap[dir], path) } return nil }) if walkErr != nil { fmt.Println(walkErr) os.Exit(1) } for _, files := range protoFilesMap { for _, relProtoFile := range files { var args []string if core.ProtoFilesUsingProtocGenGoFast[relProtoFile] { args = []string{"--gofast_out", pwd, "--plugin", "protoc-gen-gofast=" + GOBIN + "/protoc-gen-gofast"} } else { args = []string{"--go_out", pwd, "--go-grpc_out", pwd, "--plugin", "protoc-gen-go=" + GOBIN + "/protoc-gen-go", "--plugin", "protoc-gen-go-grpc=" + GOBIN + "/protoc-gen-go-grpc"} } args = append(args, relProtoFile) cmd := exec.Command(protoc, args...) cmd.Env = append(cmd.Env, os.Environ()...) cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) output, cmdErr := cmd.CombinedOutput() if len(output) > 0 { fmt.Println(string(output)) } if cmdErr != nil { fmt.Println(cmdErr) os.Exit(1) } } } moduleName, gmnErr := common.GetModuleName(pwd) if gmnErr != nil { fmt.Println(gmnErr) os.Exit(1) } modulePath := filepath.Join(strings.Split(moduleName, "/")...) pbGoFilesMap := make(map[string][]string) walkErr2 := filepath.Walk(modulePath, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Println(err) return err } if info.IsDir() { return nil } dir := filepath.Dir(path) filename := filepath.Base(path) if strings.HasSuffix(filename, ".pb.go") { pbGoFilesMap[dir] = append(pbGoFilesMap[dir], path) } return nil }) if walkErr2 != nil { fmt.Println(walkErr2) os.Exit(1) } var err error for _, srcPbGoFiles := range pbGoFilesMap { for _, srcPbGoFile := range srcPbGoFiles { var dstPbGoFile string dstPbGoFile, err = filepath.Rel(modulePath, srcPbGoFile) if err != nil { fmt.Println(err) continue } err = os.Link(srcPbGoFile, dstPbGoFile) if err != nil { if os.IsNotExist(err) { fmt.Printf("'%s' does not exist\n", srcPbGoFile) continue } if os.IsPermission(err) { fmt.Println(err) continue } if os.IsExist(err) { err = os.Remove(dstPbGoFile) if err != nil { fmt.Printf("Failed to delete file '%s'\n", dstPbGoFile) continue } err = os.Rename(srcPbGoFile, dstPbGoFile) if err != nil { fmt.Printf("Can not move '%s' to '%s'\n", srcPbGoFile, dstPbGoFile) } continue } } err = os.Rename(srcPbGoFile, dstPbGoFile) if err != nil { fmt.Printf("Can not move '%s' to '%s'\n", srcPbGoFile, dstPbGoFile) } continue } } if err == nil { err = os.RemoveAll(strings.Split(modulePath, "/")[0]) if err != nil { fmt.Println(err) } } } ================================================ FILE: main/BUILD ================================================ load("//infra/bazel:matrix.bzl", "SUPPORTED_MATRIX") load("//main:targets.bzl", "gen_targets") package(default_visibility=["//visibility:public"]) gen_targets(SUPPORTED_MATRIX) ================================================ FILE: main/confloader/confloader.go ================================================ package confloader import ( "io" "os" ) type configFileLoader func(string) (io.Reader, error) type extconfigLoader func([]string) (io.Reader, error) var ( EffectiveConfigFileLoader configFileLoader EffectiveExtConfigLoader extconfigLoader ) // LoadConfig reads from a path/url/stdin // actual work is in external module func LoadConfig(file string) (io.Reader, error) { if EffectiveConfigFileLoader == nil { newError("external config module not loaded, reading from stdin").AtInfo().WriteToLog() return os.Stdin, nil } return EffectiveConfigFileLoader(file) } // LoadExtConfig calls v2ctl to handle multiple config // the actual work also in external module func LoadExtConfig(files []string) (io.Reader, error) { if EffectiveExtConfigLoader == nil { return nil, newError("external config module not loaded").AtError() } return EffectiveExtConfigLoader(files) } ================================================ FILE: main/confloader/errors.generated.go ================================================ package confloader import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: main/confloader/external/errors.generated.go ================================================ package external import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: main/confloader/external/external.go ================================================ package external //go:generate go run v2ray.com/core/common/errors/errorgen import ( "bytes" "io" "io/ioutil" "net/http" "net/url" "os" "strings" "time" "v2ray.com/core/common/buf" "v2ray.com/core/common/platform/ctlcmd" "v2ray.com/core/main/confloader" ) func ConfigLoader(arg string) (out io.Reader, err error) { var data []byte if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { data, err = FetchHTTPContent(arg) } else if arg == "stdin:" { data, err = ioutil.ReadAll(os.Stdin) } else { data, err = ioutil.ReadFile(arg) } if err != nil { return } out = bytes.NewBuffer(data) return } func FetchHTTPContent(target string) ([]byte, error) { parsedTarget, err := url.Parse(target) if err != nil { return nil, newError("invalid URL: ", target).Base(err) } if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { return nil, newError("invalid scheme: ", parsedTarget.Scheme) } client := &http.Client{ Timeout: 30 * time.Second, } resp, err := client.Do(&http.Request{ Method: "GET", URL: parsedTarget, Close: true, }) if err != nil { return nil, newError("failed to dial to ", target).Base(err) } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, newError("unexpected HTTP status code: ", resp.StatusCode) } content, err := buf.ReadAllToBytes(resp.Body) if err != nil { return nil, newError("failed to read HTTP response").Base(err) } return content, nil } func ExtConfigLoader(files []string) (io.Reader, error) { buf, err := ctlcmd.Run(append([]string{"config"}, files...), os.Stdin) if err != nil { return nil, err } return strings.NewReader(buf.String()), nil } func init() { confloader.EffectiveConfigFileLoader = ConfigLoader confloader.EffectiveExtConfigLoader = ExtConfigLoader } ================================================ FILE: main/distro/all/all.go ================================================ package all import ( // The following are necessary as they register handlers in their init functions. // Required features. Can't remove unless there is replacements. _ "v2ray.com/core/app/dispatcher" _ "v2ray.com/core/app/proxyman/inbound" _ "v2ray.com/core/app/proxyman/outbound" // Default commander and all its services. This is an optional feature. _ "v2ray.com/core/app/commander" _ "v2ray.com/core/app/log/command" _ "v2ray.com/core/app/proxyman/command" _ "v2ray.com/core/app/stats/command" // Other optional features. _ "v2ray.com/core/app/dns" _ "v2ray.com/core/app/log" _ "v2ray.com/core/app/policy" _ "v2ray.com/core/app/reverse" _ "v2ray.com/core/app/router" _ "v2ray.com/core/app/stats" // Inbound and outbound proxies. _ "v2ray.com/core/proxy/blackhole" _ "v2ray.com/core/proxy/dns" _ "v2ray.com/core/proxy/dokodemo" _ "v2ray.com/core/proxy/freedom" _ "v2ray.com/core/proxy/http" _ "v2ray.com/core/proxy/mtproto" _ "v2ray.com/core/proxy/shadowsocks" _ "v2ray.com/core/proxy/socks" _ "v2ray.com/core/proxy/trojan" _ "v2ray.com/core/proxy/vless/inbound" _ "v2ray.com/core/proxy/vless/outbound" _ "v2ray.com/core/proxy/vmess/inbound" _ "v2ray.com/core/proxy/vmess/outbound" // Transports _ "v2ray.com/core/transport/internet/domainsocket" _ "v2ray.com/core/transport/internet/http" _ "v2ray.com/core/transport/internet/kcp" _ "v2ray.com/core/transport/internet/quic" _ "v2ray.com/core/transport/internet/tcp" _ "v2ray.com/core/transport/internet/tls" _ "v2ray.com/core/transport/internet/udp" _ "v2ray.com/core/transport/internet/websocket" _ "v2ray.com/core/transport/internet/xtls" // Transport headers _ "v2ray.com/core/transport/internet/headers/http" _ "v2ray.com/core/transport/internet/headers/noop" _ "v2ray.com/core/transport/internet/headers/srtp" _ "v2ray.com/core/transport/internet/headers/tls" _ "v2ray.com/core/transport/internet/headers/utp" _ "v2ray.com/core/transport/internet/headers/wechat" _ "v2ray.com/core/transport/internet/headers/wireguard" // JSON config support. Choose only one from the two below. // The following line loads JSON from v2ctl _ "v2ray.com/core/main/json" // The following line loads JSON internally // _ "v2ray.com/core/main/jsonem" // Load config from file or http(s) _ "v2ray.com/core/main/confloader/external" ) ================================================ FILE: main/distro/debug/debug.go ================================================ package debug import _ "net/http/pprof" import "net/http" func init() { go func() { http.ListenAndServe(":6060", nil) }() } ================================================ FILE: main/errors.generated.go ================================================ package main import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: main/json/config_json.go ================================================ package json //go:generate go run v2ray.com/core/common/errors/errorgen import ( "io" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/cmdarg" "v2ray.com/core/infra/conf/serial" "v2ray.com/core/main/confloader" ) func init() { common.Must(core.RegisterConfigLoader(&core.ConfigFormat{ Name: "JSON", Extension: []string{"json"}, Loader: func(input interface{}) (*core.Config, error) { switch v := input.(type) { case cmdarg.Arg: r, err := confloader.LoadExtConfig(v) if err != nil { return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning() } return core.LoadConfig("protobuf", "", r) case io.Reader: return serial.LoadJSONConfig(v) default: return nil, newError("unknow type") } }, })) } ================================================ FILE: main/json/errors.generated.go ================================================ package json import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: main/jsonem/errors.generated.go ================================================ package jsonem import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: main/jsonem/jsonem.go ================================================ package jsonem import ( "io" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/cmdarg" "v2ray.com/core/infra/conf" "v2ray.com/core/infra/conf/serial" "v2ray.com/core/main/confloader" ) func init() { common.Must(core.RegisterConfigLoader(&core.ConfigFormat{ Name: "JSON", Extension: []string{"json"}, Loader: func(input interface{}) (*core.Config, error) { switch v := input.(type) { case cmdarg.Arg: cf := &conf.Config{} for _, arg := range v { newError("Reading config: ", arg).AtInfo().WriteToLog() r, err := confloader.LoadConfig(arg) common.Must(err) c, err := serial.DecodeJSONConfig(r) common.Must(err) cf.Override(c, arg) } return cf.Build() case io.Reader: return serial.LoadJSONConfig(v) default: return nil, newError("unknow type") } }, })) } ================================================ FILE: main/main.go ================================================ package main //go:generate go run v2ray.com/core/common/errors/errorgen import ( "flag" "fmt" "io/ioutil" "log" "os" "os/signal" "path" "path/filepath" "runtime" "strings" "syscall" "v2ray.com/core" "v2ray.com/core/common/cmdarg" "v2ray.com/core/common/platform" _ "v2ray.com/core/main/distro/all" ) var ( configFiles cmdarg.Arg // "Config file for V2Ray.", the option is customed type, parse in main configDir string version = flag.Bool("version", false, "Show current version of V2Ray.") test = flag.Bool("test", false, "Test config file only, without launching V2Ray server.") format = flag.String("format", "json", "Format of input file.") /* We have to do this here because Golang's Test will also need to parse flag, before * main func in this file is run. */ _ = func() error { flag.Var(&configFiles, "config", "Config file for V2Ray. Multiple assign is accepted (only json). Latter ones overrides the former ones.") flag.Var(&configFiles, "c", "Short alias of -config") flag.StringVar(&configDir, "confdir", "", "A dir with multiple json config") return nil }() ) func fileExists(file string) bool { info, err := os.Stat(file) return err == nil && !info.IsDir() } func dirExists(file string) bool { if file == "" { return false } info, err := os.Stat(file) return err == nil && info.IsDir() } func readConfDir(dirPath string) { confs, err := ioutil.ReadDir(dirPath) if err != nil { log.Fatalln(err) } for _, f := range confs { if strings.HasSuffix(f.Name(), ".json") { configFiles.Set(path.Join(dirPath, f.Name())) } } } func getConfigFilePath() (cmdarg.Arg, error) { if dirExists(configDir) { log.Println("Using confdir from arg:", configDir) readConfDir(configDir) } else { if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) { log.Println("Using confdir from env:", envConfDir) readConfDir(envConfDir) } } if len(configFiles) > 0 { return configFiles, nil } if workingDir, err := os.Getwd(); err == nil { configFile := filepath.Join(workingDir, "config.json") if fileExists(configFile) { log.Println("Using default config: ", configFile) return cmdarg.Arg{configFile}, nil } } if configFile := platform.GetConfigurationPath(); fileExists(configFile) { log.Println("Using config from env: ", configFile) return cmdarg.Arg{configFile}, nil } log.Println("Using config from STDIN") return cmdarg.Arg{"stdin:"}, nil } func GetConfigFormat() string { switch strings.ToLower(*format) { case "pb", "protobuf": return "protobuf" default: return "json" } } func startV2Ray() (core.Server, error) { configFiles, err := getConfigFilePath() if err != nil { return nil, err } config, err := core.LoadConfig(GetConfigFormat(), configFiles[0], configFiles) if err != nil { return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err) } server, err := core.New(config) if err != nil { return nil, newError("failed to create server").Base(err) } return server, nil } func printVersion() { version := core.VersionStatement() for _, s := range version { fmt.Println(s) } } func main() { flag.Parse() printVersion() if *version { return } server, err := startV2Ray() if err != nil { fmt.Println(err) // Configuration error. Exit with a special value to prevent systemd from restarting. os.Exit(23) } if *test { fmt.Println("Configuration OK.") os.Exit(0) } if err := server.Start(); err != nil { fmt.Println("Failed to start", err) os.Exit(-1) } defer server.Close() // Explicitly triggering GC to remove garbage from config loading. runtime.GC() { osSignals := make(chan os.Signal, 1) signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM) <-osSignals } } ================================================ FILE: main/main_test.go ================================================ // +build coveragemain package main import ( "testing" ) func TestRunMainForCoverage(t *testing.T) { main() } ================================================ FILE: main/targets.bzl ================================================ load("//infra/bazel:build.bzl", "foreign_go_binary") def gen_targets(matrix): pkg = "./main" output = "v2ray" for (os, arch, ver) in matrix: if arch in ["arm"]: bin_name = "v2ray_" + os + "_" + arch + "_" + ver foreign_go_binary( name = bin_name, pkg = pkg, output = output, os = os, arch = arch, ver = ver, arm = ver, ) if os in ["windows"]: bin_name = "v2ray_" + os + "_" + arch + "_" + ver + "_nowindow" foreign_go_binary( name = bin_name, pkg = pkg, output = "w" + output, os = os, arch = arch, ver = ver, arm = ver, ld = "-H windowsgui", ) else: bin_name = "v2ray_" + os + "_" + arch foreign_go_binary( name = bin_name, pkg = pkg, output = output, os = os, arch = arch, ver = ver, ) if os in ["windows"]: bin_name = "v2ray_" + os + "_" + arch + "_nowindow" foreign_go_binary( name = bin_name, pkg = pkg, output = "w" + output, os = os, arch = arch, ver = ver, ld = "-H windowsgui", ) if arch in ["mips", "mipsle"]: bin_name = "v2ray_" + os + "_" + arch + "_softfloat" foreign_go_binary( name = bin_name, pkg = pkg, output = output + "_softfloat", os = os, arch = arch, ver = ver, mips = "softfloat", ) ================================================ FILE: mocks.go ================================================ package core //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/io.go -mock_names Reader=Reader,Writer=Writer io Reader,Writer //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/log.go -mock_names Handler=LogHandler v2ray.com/core/common/log Handler //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/mux.go -mock_names ClientWorkerFactory=MuxClientWorkerFactory v2ray.com/core/common/mux ClientWorkerFactory //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/dns.go -mock_names Client=DNSClient v2ray.com/core/features/dns Client //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/outbound.go -mock_names Manager=OutboundManager,HandlerSelector=OutboundHandlerSelector v2ray.com/core/features/outbound Manager,HandlerSelector //go:generate go run github.com/golang/mock/mockgen -package mocks -destination testing/mocks/proxy.go -mock_names Inbound=ProxyInbound,Outbound=ProxyOutbound v2ray.com/core/proxy Inbound,Outbound ================================================ FILE: proto.go ================================================ package core //go:generate go install -v google.golang.org/protobuf/cmd/protoc-gen-go //go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc //go:generate go install -v github.com/gogo/protobuf/protoc-gen-gofast //go:generate go run ./infra/vprotogen/main.go import "path/filepath" // ProtoFilesUsingProtocGenGoFast is the map of Proto files // that use `protoc-gen-gofast` to generate pb.go files var ProtoFilesUsingProtocGenGoFast = map[string]bool{"proxy/vless/encoding/addons.proto": true} // ProtocMap is the map of paths to `protoc` binary excutable files of specific platform var ProtocMap = map[string]string{ "windows": filepath.Join(".dev", "protoc", "windows", "protoc.exe"), "darwin": filepath.Join(".dev", "protoc", "macos", "protoc"), "linux": filepath.Join(".dev", "protoc", "linux", "protoc"), } ================================================ FILE: proxy/blackhole/blackhole.go ================================================ // +build !confonly // Package blackhole is an outbound handler that blocks all connections. package blackhole //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "time" "v2ray.com/core/common" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // Handler is an outbound connection that silently swallow the entire payload. type Handler struct { response ResponseConfig } // New creates a new blackhole handler. func New(ctx context.Context, config *Config) (*Handler, error) { response, err := config.GetInternalResponse() if err != nil { return nil, err } return &Handler{ response: response, }, nil } // Process implements OutboundHandler.Dispatch(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { nBytes := h.response.WriteTo(link.Writer) if nBytes > 0 { // Sleep a little here to make sure the response is sent to client. time.Sleep(time.Second) } common.Interrupt(link.Writer) return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) } ================================================ FILE: proxy/blackhole/blackhole_test.go ================================================ package blackhole_test import ( "context" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/blackhole" "v2ray.com/core/transport" "v2ray.com/core/transport/pipe" ) func TestBlackholeHTTPResponse(t *testing.T) { handler, err := blackhole.New(context.Background(), &blackhole.Config{ Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}), }) common.Must(err) reader, writer := pipe.New(pipe.WithoutSizeLimit()) var mb buf.MultiBuffer var rerr error go func() { b, e := reader.ReadMultiBuffer() mb = b rerr = e }() link := transport.Link{ Reader: reader, Writer: writer, } common.Must(handler.Process(context.Background(), &link, nil)) common.Must(rerr) if mb.IsEmpty() { t.Error("expect http response, but nothing") } } ================================================ FILE: proxy/blackhole/config.go ================================================ package blackhole import ( "v2ray.com/core/common" "v2ray.com/core/common/buf" ) const ( http403response = `HTTP/1.1 403 Forbidden Connection: close Cache-Control: max-age=3600, public Content-Length: 0 ` ) // ResponseConfig is the configuration for blackhole responses. type ResponseConfig interface { // WriteTo writes predefined response to the give buffer. WriteTo(buf.Writer) int32 } // WriteTo implements ResponseConfig.WriteTo(). func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 } // WriteTo implements ResponseConfig.WriteTo(). func (*HTTPResponse) WriteTo(writer buf.Writer) int32 { b := buf.New() common.Must2(b.WriteString(http403response)) n := b.Len() writer.WriteMultiBuffer(buf.MultiBuffer{b}) return n } // GetInternalResponse converts response settings from proto to internal data structure. func (c *Config) GetInternalResponse() (ResponseConfig, error) { if c.GetResponse() == nil { return new(NoneResponse), nil } config, err := c.GetResponse().GetInstance() if err != nil { return nil, err } return config.(ResponseConfig), nil } ================================================ FILE: proxy/blackhole/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/blackhole/config.proto package blackhole import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type NoneResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *NoneResponse) Reset() { *x = NoneResponse{} if protoimpl.UnsafeEnabled { mi := &file_proxy_blackhole_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *NoneResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*NoneResponse) ProtoMessage() {} func (x *NoneResponse) ProtoReflect() protoreflect.Message { mi := &file_proxy_blackhole_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead. func (*NoneResponse) Descriptor() ([]byte, []int) { return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0} } type HTTPResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *HTTPResponse) Reset() { *x = HTTPResponse{} if protoimpl.UnsafeEnabled { mi := &file_proxy_blackhole_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HTTPResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HTTPResponse) ProtoMessage() {} func (x *HTTPResponse) ProtoReflect() protoreflect.Message { mi := &file_proxy_blackhole_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead. func (*HTTPResponse) Descriptor() ([]byte, []int) { return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1} } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_blackhole_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_blackhole_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2} } func (x *Config) GetResponse() *serial.TypedMessage { if x != nil { return x.Response } return nil } var File_proxy_blackhole_config_proto protoreflect.FileDescriptor var file_proxy_blackhole_config_proto_rawDesc = []byte{ 0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0e, 0x0a, 0x0c, 0x4e, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0x50, 0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_blackhole_config_proto_rawDescOnce sync.Once file_proxy_blackhole_config_proto_rawDescData = file_proxy_blackhole_config_proto_rawDesc ) func file_proxy_blackhole_config_proto_rawDescGZIP() []byte { file_proxy_blackhole_config_proto_rawDescOnce.Do(func() { file_proxy_blackhole_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_blackhole_config_proto_rawDescData) }) return file_proxy_blackhole_config_proto_rawDescData } var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_blackhole_config_proto_goTypes = []interface{}{ (*NoneResponse)(nil), // 0: v2ray.core.proxy.blackhole.NoneResponse (*HTTPResponse)(nil), // 1: v2ray.core.proxy.blackhole.HTTPResponse (*Config)(nil), // 2: v2ray.core.proxy.blackhole.Config (*serial.TypedMessage)(nil), // 3: v2ray.core.common.serial.TypedMessage } var file_proxy_blackhole_config_proto_depIdxs = []int32{ 3, // 0: v2ray.core.proxy.blackhole.Config.response:type_name -> v2ray.core.common.serial.TypedMessage 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_blackhole_config_proto_init() } func file_proxy_blackhole_config_proto_init() { if File_proxy_blackhole_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_blackhole_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NoneResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_blackhole_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HTTPResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_blackhole_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_blackhole_config_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_blackhole_config_proto_goTypes, DependencyIndexes: file_proxy_blackhole_config_proto_depIdxs, MessageInfos: file_proxy_blackhole_config_proto_msgTypes, }.Build() File_proxy_blackhole_config_proto = out.File file_proxy_blackhole_config_proto_rawDesc = nil file_proxy_blackhole_config_proto_goTypes = nil file_proxy_blackhole_config_proto_depIdxs = nil } ================================================ FILE: proxy/blackhole/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.blackhole; option csharp_namespace = "V2Ray.Core.Proxy.Blackhole"; option go_package = "v2ray.com/core/proxy/blackhole"; option java_package = "com.v2ray.core.proxy.blackhole"; option java_multiple_files = true; import "common/serial/typed_message.proto"; message NoneResponse {} message HTTPResponse {} message Config { v2ray.core.common.serial.TypedMessage response = 1; } ================================================ FILE: proxy/blackhole/config_test.go ================================================ package blackhole_test import ( "bufio" "net/http" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/proxy/blackhole" ) func TestHTTPResponse(t *testing.T) { buffer := buf.New() httpResponse := new(HTTPResponse) httpResponse.WriteTo(buf.NewWriter(buffer)) reader := bufio.NewReader(buffer) response, err := http.ReadResponse(reader, nil) common.Must(err) if response.StatusCode != 403 { t.Error("expected status code 403, but got ", response.StatusCode) } } ================================================ FILE: proxy/blackhole/errors.generated.go ================================================ package blackhole import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/dns/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/dns/config.proto package dns import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Server is the DNS server address. If specified, this address overrides the // original one. Server *net.Endpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_dns_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_dns_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_dns_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetServer() *net.Endpoint { if x != nil { return x.Server } return nil } var File_proxy_dns_config_proto protoreflect.FileDescriptor var file_proxy_dns_config_proto_rawDesc = []byte{ 0x0a, 0x16, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x4d, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x14, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_dns_config_proto_rawDescOnce sync.Once file_proxy_dns_config_proto_rawDescData = file_proxy_dns_config_proto_rawDesc ) func file_proxy_dns_config_proto_rawDescGZIP() []byte { file_proxy_dns_config_proto_rawDescOnce.Do(func() { file_proxy_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_dns_config_proto_rawDescData) }) return file_proxy_dns_config_proto_rawDescData } var file_proxy_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_dns_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.proxy.dns.Config (*net.Endpoint)(nil), // 1: v2ray.core.common.net.Endpoint } var file_proxy_dns_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.proxy.dns.Config.server:type_name -> v2ray.core.common.net.Endpoint 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_dns_config_proto_init() } func file_proxy_dns_config_proto_init() { if File_proxy_dns_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_dns_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_dns_config_proto_goTypes, DependencyIndexes: file_proxy_dns_config_proto_depIdxs, MessageInfos: file_proxy_dns_config_proto_msgTypes, }.Build() File_proxy_dns_config_proto = out.File file_proxy_dns_config_proto_rawDesc = nil file_proxy_dns_config_proto_goTypes = nil file_proxy_dns_config_proto_depIdxs = nil } ================================================ FILE: proxy/dns/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.dns; option csharp_namespace = "V2Ray.Core.Proxy.Dns"; option go_package = "v2ray.com/core/proxy/dns"; option java_package = "com.v2ray.core.proxy.dns"; option java_multiple_files = true; import "common/net/destination.proto"; message Config { // Server is the DNS server address. If specified, this address overrides the // original one. v2ray.core.common.net.Endpoint server = 1; } ================================================ FILE: proxy/dns/dns.go ================================================ // +build !confonly package dns import ( "context" "io" "sync" "golang.org/x/net/dns/dnsmessage" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" dns_proto "v2ray.com/core/common/protocol/dns" "v2ray.com/core/common/session" "v2ray.com/core/common/task" "v2ray.com/core/features/dns" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { h := new(Handler) if err := core.RequireFeatures(ctx, func(dnsClient dns.Client) error { return h.Init(config.(*Config), dnsClient) }); err != nil { return nil, err } return h, nil })) } type ownLinkVerifier interface { IsOwnLink(ctx context.Context) bool } type Handler struct { ipv4Lookup dns.IPv4Lookup ipv6Lookup dns.IPv6Lookup ownLinkVerifier ownLinkVerifier server net.Destination } func (h *Handler) Init(config *Config, dnsClient dns.Client) error { ipv4lookup, ok := dnsClient.(dns.IPv4Lookup) if !ok { return newError("dns.Client doesn't implement IPv4Lookup") } h.ipv4Lookup = ipv4lookup ipv6lookup, ok := dnsClient.(dns.IPv6Lookup) if !ok { return newError("dns.Client doesn't implement IPv6Lookup") } h.ipv6Lookup = ipv6lookup if v, ok := dnsClient.(ownLinkVerifier); ok { h.ownLinkVerifier = v } if config.Server != nil { h.server = config.Server.AsDestination() } return nil } func (h *Handler) isOwnLink(ctx context.Context) bool { return h.ownLinkVerifier != nil && h.ownLinkVerifier.IsOwnLink(ctx) } func parseIPQuery(b []byte) (r bool, domain string, id uint16, qType dnsmessage.Type) { var parser dnsmessage.Parser header, err := parser.Start(b) if err != nil { newError("parser start").Base(err).WriteToLog() return } id = header.ID q, err := parser.Question() if err != nil { newError("question").Base(err).WriteToLog() return } qType = q.Type if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA { return } domain = q.Name.String() r = true return } // Process implements proxy.Outbound. func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("invalid outbound") } srcNetwork := outbound.Target.Network dest := outbound.Target if h.server.Network != net.Network_Unknown { dest.Network = h.server.Network } if h.server.Address != nil { dest.Address = h.server.Address } if h.server.Port != 0 { dest.Port = h.server.Port } newError("handling DNS traffic to ", dest).WriteToLog(session.ExportIDToError(ctx)) conn := &outboundConn{ dialer: func() (internet.Connection, error) { return d.Dial(ctx, dest) }, connReady: make(chan struct{}, 1), } var reader dns_proto.MessageReader var writer dns_proto.MessageWriter if srcNetwork == net.Network_TCP { reader = dns_proto.NewTCPReader(link.Reader) writer = &dns_proto.TCPWriter{ Writer: link.Writer, } } else { reader = &dns_proto.UDPReader{ Reader: link.Reader, } writer = &dns_proto.UDPWriter{ Writer: link.Writer, } } var connReader dns_proto.MessageReader var connWriter dns_proto.MessageWriter if dest.Network == net.Network_TCP { connReader = dns_proto.NewTCPReader(buf.NewReader(conn)) connWriter = &dns_proto.TCPWriter{ Writer: buf.NewWriter(conn), } } else { connReader = &dns_proto.UDPReader{ Reader: buf.NewPacketReader(conn), } connWriter = &dns_proto.UDPWriter{ Writer: buf.NewWriter(conn), } } request := func() error { defer conn.Close() for { b, err := reader.ReadMessage() if err == io.EOF { return nil } if err != nil { return err } if !h.isOwnLink(ctx) { isIPQuery, domain, id, qType := parseIPQuery(b.Bytes()) if isIPQuery { go h.handleIPQuery(id, qType, domain, writer) continue } } if err := connWriter.WriteMessage(b); err != nil { return err } } } response := func() error { for { b, err := connReader.ReadMessage() if err == io.EOF { return nil } if err != nil { return err } if err := writer.WriteMessage(b); err != nil { return err } } } if err := task.Run(ctx, request, response); err != nil { return newError("connection ends").Base(err) } return nil } func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) { var ips []net.IP var err error switch qType { case dnsmessage.TypeA: ips, err = h.ipv4Lookup.LookupIPv4(domain) case dnsmessage.TypeAAAA: ips, err = h.ipv6Lookup.LookupIPv6(domain) } rcode := dns.RCodeFromError(err) if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse { newError("ip query").Base(err).WriteToLog() return } b := buf.New() rawBytes := b.Extend(buf.Size) builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{ ID: id, RCode: dnsmessage.RCode(rcode), RecursionAvailable: true, RecursionDesired: true, Response: true, Authoritative: true, }) builder.EnableCompression() common.Must(builder.StartQuestions()) common.Must(builder.Question(dnsmessage.Question{ Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, Type: qType, })) common.Must(builder.StartAnswers()) rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: 600} for _, ip := range ips { if len(ip) == net.IPv4len { var r dnsmessage.AResource copy(r.A[:], ip) common.Must(builder.AResource(rHeader, r)) } else { var r dnsmessage.AAAAResource copy(r.AAAA[:], ip) common.Must(builder.AAAAResource(rHeader, r)) } } msgBytes, err := builder.Finish() if err != nil { newError("pack message").Base(err).WriteToLog() b.Release() return } b.Resize(0, int32(len(msgBytes))) if err := writer.WriteMessage(b); err != nil { newError("write IP answer").Base(err).WriteToLog() } } type outboundConn struct { access sync.Mutex dialer func() (internet.Connection, error) conn net.Conn connReady chan struct{} } func (c *outboundConn) dial() error { conn, err := c.dialer() if err != nil { return err } c.conn = conn c.connReady <- struct{}{} return nil } func (c *outboundConn) Write(b []byte) (int, error) { c.access.Lock() if c.conn == nil { if err := c.dial(); err != nil { c.access.Unlock() newError("failed to dial outbound connection").Base(err).AtWarning().WriteToLog() return len(b), nil } } c.access.Unlock() return c.conn.Write(b) } func (c *outboundConn) Read(b []byte) (int, error) { var conn net.Conn c.access.Lock() conn = c.conn c.access.Unlock() if conn == nil { _, open := <-c.connReady if !open { return 0, io.EOF } conn = c.conn } return conn.Read(b) } func (c *outboundConn) Close() error { c.access.Lock() close(c.connReady) if c.conn != nil { c.conn.Close() } c.access.Unlock() return nil } ================================================ FILE: proxy/dns/dns_test.go ================================================ package dns_test import ( "strconv" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/miekg/dns" "v2ray.com/core" "v2ray.com/core/app/dispatcher" dnsapp "v2ray.com/core/app/dns" "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" _ "v2ray.com/core/app/proxyman/inbound" _ "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" dns_proxy "v2ray.com/core/proxy/dns" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" ) type staticHandler struct { } func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { ans := new(dns.Msg) ans.Id = r.Id var clientIP net.IP opt := r.IsEdns0() if opt != nil { for _, o := range opt.Option { if o.Option() == dns.EDNS0SUBNET { subnet := o.(*dns.EDNS0_SUBNET) clientIP = subnet.Address } } } for _, q := range r.Question { if q.Name == "google.com." && q.Qtype == dns.TypeA { if clientIP == nil { rr, _ := dns.NewRR("google.com. IN A 8.8.8.8") ans.Answer = append(ans.Answer, rr) } else { rr, _ := dns.NewRR("google.com. IN A 8.8.4.4") ans.Answer = append(ans.Answer, rr) } } else if q.Name == "facebook.com." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("facebook.com. IN A 9.9.9.9") ans.Answer = append(ans.Answer, rr) } else if q.Name == "ipv6.google.com." && q.Qtype == dns.TypeA { rr, err := dns.NewRR("ipv6.google.com. IN A 8.8.8.7") common.Must(err) ans.Answer = append(ans.Answer, rr) } else if q.Name == "ipv6.google.com." && q.Qtype == dns.TypeAAAA { rr, err := dns.NewRR("ipv6.google.com. IN AAAA 2001:4860:4860::8888") common.Must(err) ans.Answer = append(ans.Answer, rr) } else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA { ans.MsgHdr.Rcode = dns.RcodeNameError } } w.WriteMsg(ans) } func TestUDPDNSTunnel(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, UDPSize: 1200, } defer dnsServer.Shutdown() go dnsServer.ListenAndServe() time.Sleep(time.Second) serverPort := udp.PickPort() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dnsapp.Config{ NameServers: []*net.Endpoint{ { Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Inbound: []*core.InboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(port), Networks: []net.Network{net.Network_UDP}, }), ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}), }, }, } v, err := core.New(config) common.Must(err) common.Must(v.Start()) defer v.Close() { m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET} c := new(dns.Client) in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort))) common.Must(err) if len(in.Answer) != 1 { t.Fatal("len(answer): ", len(in.Answer)) } rr, ok := in.Answer[0].(*dns.A) if !ok { t.Fatal("not A record") } if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" { t.Error(r) } } { m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: "ipv4only.google.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET} c := new(dns.Client) c.Timeout = 10 * time.Second in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort))) common.Must(err) if len(in.Answer) != 0 { t.Fatal("len(answer): ", len(in.Answer)) } } { m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: "notexist.google.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET} c := new(dns.Client) in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort))) common.Must(err) if in.Rcode != dns.RcodeNameError { t.Error("expected NameError, but got ", in.Rcode) } } } func TestTCPDNSTunnel(t *testing.T) { port := udp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "udp", Handler: &staticHandler{}, } defer dnsServer.Shutdown() go dnsServer.ListenAndServe() time.Sleep(time.Second) serverPort := tcp.PickPort() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dnsapp.Config{ NameServer: []*dnsapp.NameServer{ { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Inbound: []*core.InboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(port), Networks: []net.Network{net.Network_TCP}, }), ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}), }, }, } v, err := core.New(config) common.Must(err) common.Must(v.Start()) defer v.Close() m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET} c := &dns.Client{ Net: "tcp", } in, _, err := c.Exchange(m1, "127.0.0.1:"+serverPort.String()) common.Must(err) if len(in.Answer) != 1 { t.Fatal("len(answer): ", len(in.Answer)) } rr, ok := in.Answer[0].(*dns.A) if !ok { t.Fatal("not A record") } if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" { t.Error(r) } } func TestUDP2TCPDNSTunnel(t *testing.T) { port := tcp.PickPort() dnsServer := dns.Server{ Addr: "127.0.0.1:" + port.String(), Net: "tcp", Handler: &staticHandler{}, } defer dnsServer.Shutdown() go dnsServer.ListenAndServe() time.Sleep(time.Second) serverPort := tcp.PickPort() config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dnsapp.Config{ NameServer: []*dnsapp.NameServer{ { Address: &net.Endpoint{ Network: net.Network_UDP, Address: &net.IPOrDomain{ Address: &net.IPOrDomain_Ip{ Ip: []byte{127, 0, 0, 1}, }, }, Port: uint32(port), }, }, }, }), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&policy.Config{}), }, Inbound: []*core.InboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(port), Networks: []net.Network{net.Network_TCP}, }), ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{ Server: &net.Endpoint{ Network: net.Network_TCP, }, }), }, }, } v, err := core.New(config) common.Must(err) common.Must(v.Start()) defer v.Close() m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET} c := &dns.Client{ Net: "tcp", } in, _, err := c.Exchange(m1, "127.0.0.1:"+serverPort.String()) common.Must(err) if len(in.Answer) != 1 { t.Fatal("len(answer): ", len(in.Answer)) } rr, ok := in.Answer[0].(*dns.A) if !ok { t.Fatal("not A record") } if r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != "" { t.Error(r) } } ================================================ FILE: proxy/dns/errors.generated.go ================================================ package dns import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/dokodemo/config.go ================================================ package dokodemo import ( "v2ray.com/core/common/net" ) // GetPredefinedAddress returns the defined address from proto config. Null if address is not valid. func (v *Config) GetPredefinedAddress() net.Address { addr := v.Address.AsAddress() if addr == nil { return nil } return addr } ================================================ FILE: proxy/dokodemo/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/dokodemo/config.proto package dokodemo import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` // List of networks that the Dokodemo accepts. // Deprecated. Use networks. // // Deprecated: Do not use. NetworkList *net.NetworkList `protobuf:"bytes,3,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"` // List of networks that the Dokodemo accepts. Networks []net.Network `protobuf:"varint,7,rep,packed,name=networks,proto3,enum=v2ray.core.common.net.Network" json:"networks,omitempty"` // Deprecated: Do not use. Timeout uint32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"` FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"` UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_dokodemo_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_dokodemo_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetAddress() *net.IPOrDomain { if x != nil { return x.Address } return nil } func (x *Config) GetPort() uint32 { if x != nil { return x.Port } return 0 } // Deprecated: Do not use. func (x *Config) GetNetworkList() *net.NetworkList { if x != nil { return x.NetworkList } return nil } func (x *Config) GetNetworks() []net.Network { if x != nil { return x.Networks } return nil } // Deprecated: Do not use. func (x *Config) GetTimeout() uint32 { if x != nil { return x.Timeout } return 0 } func (x *Config) GetFollowRedirect() bool { if x != nil { return x.FollowRedirect } return false } func (x *Config) GetUserLevel() uint32 { if x != nil { return x.UserLevel } return 0 } var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor var file_proxy_dokodemo_config_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x49, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x5c, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0xaa, 0x02, 0x19, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x44, 0x6f, 0x6b, 0x6f, 0x64, 0x65, 0x6d, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_dokodemo_config_proto_rawDescOnce sync.Once file_proxy_dokodemo_config_proto_rawDescData = file_proxy_dokodemo_config_proto_rawDesc ) func file_proxy_dokodemo_config_proto_rawDescGZIP() []byte { file_proxy_dokodemo_config_proto_rawDescOnce.Do(func() { file_proxy_dokodemo_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_dokodemo_config_proto_rawDescData) }) return file_proxy_dokodemo_config_proto_rawDescData } var file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_dokodemo_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.proxy.dokodemo.Config (*net.IPOrDomain)(nil), // 1: v2ray.core.common.net.IPOrDomain (*net.NetworkList)(nil), // 2: v2ray.core.common.net.NetworkList (net.Network)(0), // 3: v2ray.core.common.net.Network } var file_proxy_dokodemo_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.proxy.dokodemo.Config.address:type_name -> v2ray.core.common.net.IPOrDomain 2, // 1: v2ray.core.proxy.dokodemo.Config.network_list:type_name -> v2ray.core.common.net.NetworkList 3, // 2: v2ray.core.proxy.dokodemo.Config.networks:type_name -> v2ray.core.common.net.Network 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_proxy_dokodemo_config_proto_init() } func file_proxy_dokodemo_config_proto_init() { if File_proxy_dokodemo_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_dokodemo_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_dokodemo_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_dokodemo_config_proto_goTypes, DependencyIndexes: file_proxy_dokodemo_config_proto_depIdxs, MessageInfos: file_proxy_dokodemo_config_proto_msgTypes, }.Build() File_proxy_dokodemo_config_proto = out.File file_proxy_dokodemo_config_proto_rawDesc = nil file_proxy_dokodemo_config_proto_goTypes = nil file_proxy_dokodemo_config_proto_depIdxs = nil } ================================================ FILE: proxy/dokodemo/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.dokodemo; option csharp_namespace = "V2Ray.Core.Proxy.Dokodemo"; option go_package = "v2ray.com/core/proxy/dokodemo"; option java_package = "com.v2ray.core.proxy.dokodemo"; option java_multiple_files = true; import "common/net/address.proto"; import "common/net/network.proto"; message Config { v2ray.core.common.net.IPOrDomain address = 1; uint32 port = 2; // List of networks that the Dokodemo accepts. // Deprecated. Use networks. v2ray.core.common.net.NetworkList network_list = 3 [deprecated = true]; // List of networks that the Dokodemo accepts. repeated v2ray.core.common.net.Network networks = 7; uint32 timeout = 4 [deprecated = true]; bool follow_redirect = 5; uint32 user_level = 6; } ================================================ FILE: proxy/dokodemo/dokodemo.go ================================================ // +build !confonly package dokodemo //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "sync/atomic" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" ) func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { d := new(DokodemoDoor) err := core.RequireFeatures(ctx, func(pm policy.Manager) error { return d.Init(config.(*Config), pm, session.SockoptFromContext(ctx)) }) return d, err })) } type DokodemoDoor struct { policyManager policy.Manager config *Config address net.Address port net.Port sockopt *session.Sockopt } // Init initializes the DokodemoDoor instance with necessary parameters. func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error { if (config.NetworkList == nil || len(config.NetworkList.Network) == 0) && len(config.Networks) == 0 { return newError("no network specified") } d.config = config d.address = config.GetPredefinedAddress() d.port = net.Port(config.Port) d.policyManager = pm d.sockopt = sockopt return nil } // Network implements proxy.Inbound. func (d *DokodemoDoor) Network() []net.Network { if len(d.config.Networks) > 0 { return d.config.Networks } return d.config.NetworkList.Network } func (d *DokodemoDoor) policy() policy.Session { config := d.config p := d.policyManager.ForLevel(config.UserLevel) if config.Timeout > 0 && config.UserLevel == 0 { p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second } return p } type hasHandshakeAddress interface { HandshakeAddress() net.Address } // Process implements proxy.Inbound. func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog(session.ExportIDToError(ctx)) dest := net.Destination{ Network: network, Address: d.address, Port: d.port, } destinationOverridden := false if d.config.FollowRedirect { if outbound := session.OutboundFromContext(ctx); outbound != nil && outbound.Target.IsValid() { dest = outbound.Target destinationOverridden = true } else if handshake, ok := conn.(hasHandshakeAddress); ok { addr := handshake.HandshakeAddress() if addr != nil { dest.Address = addr destinationOverridden = true } } } if !dest.IsValid() || dest.Address == nil { return newError("unable to get destination") } if inbound := session.InboundFromContext(ctx); inbound != nil { inbound.User = &protocol.MemoryUser{ Level: d.config.UserLevel, } } ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: conn.RemoteAddr(), To: dest, Status: log.AccessAccepted, Reason: "", }) newError("received request for ", conn.RemoteAddr()).WriteToLog(session.ExportIDToError(ctx)) plcy := d.policy() ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return newError("failed to dispatch request").Base(err) } requestCount := int32(1) requestDone := func() error { defer func() { if atomic.AddInt32(&requestCount, -1) == 0 { timer.SetTimeout(plcy.Timeouts.DownlinkOnly) } }() var reader buf.Reader if dest.Network == net.Network_UDP { reader = buf.NewPacketReader(conn) } else { reader = buf.NewReader(conn) } if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport request").Base(err) } return nil } tproxyRequest := func() error { return nil } var writer buf.Writer if network == net.Network_TCP { writer = buf.NewWriter(conn) } else { //if we are in TPROXY mode, use linux's udp forging functionality if !destinationOverridden { writer = &buf.SequentialWriter{Writer: conn} } else { sockopt := &internet.SocketConfig{ Tproxy: internet.SocketConfig_TProxy, } if dest.Address.Family().IsIP() { sockopt.BindAddress = dest.Address.IP() sockopt.BindPort = uint32(dest.Port) } if d.sockopt != nil { sockopt.Mark = d.sockopt.Mark } tConn, err := internet.DialSystem(ctx, net.DestinationFromAddr(conn.RemoteAddr()), sockopt) if err != nil { return err } defer tConn.Close() writer = &buf.SequentialWriter{Writer: tConn} tReader := buf.NewPacketReader(tConn) requestCount++ tproxyRequest = func() error { defer func() { if atomic.AddInt32(&requestCount, -1) == 0 { timer.SetTimeout(plcy.Timeouts.DownlinkOnly) } }() if err := buf.Copy(tReader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport request (TPROXY conn)").Base(err) } return nil } } } responseDone := func() error { defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport response").Base(err) } return nil } if err := task.Run(ctx, task.OnSuccess(func() error { return task.Run(ctx, requestDone, tproxyRequest) }, task.Close(link.Writer)), responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } ================================================ FILE: proxy/dokodemo/errors.generated.go ================================================ package dokodemo import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/freedom/config.go ================================================ package freedom func (c *Config) useIP() bool { return c.DomainStrategy == Config_USE_IP || c.DomainStrategy == Config_USE_IP4 || c.DomainStrategy == Config_USE_IP6 } ================================================ FILE: proxy/freedom/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/freedom/config.proto package freedom import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config_DomainStrategy int32 const ( Config_AS_IS Config_DomainStrategy = 0 Config_USE_IP Config_DomainStrategy = 1 Config_USE_IP4 Config_DomainStrategy = 2 Config_USE_IP6 Config_DomainStrategy = 3 ) // Enum value maps for Config_DomainStrategy. var ( Config_DomainStrategy_name = map[int32]string{ 0: "AS_IS", 1: "USE_IP", 2: "USE_IP4", 3: "USE_IP6", } Config_DomainStrategy_value = map[string]int32{ "AS_IS": 0, "USE_IP": 1, "USE_IP4": 2, "USE_IP6": 3, } ) func (x Config_DomainStrategy) Enum() *Config_DomainStrategy { p := new(Config_DomainStrategy) *p = x return p } func (x Config_DomainStrategy) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor { return file_proxy_freedom_config_proto_enumTypes[0].Descriptor() } func (Config_DomainStrategy) Type() protoreflect.EnumType { return &file_proxy_freedom_config_proto_enumTypes[0] } func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Config_DomainStrategy.Descriptor instead. func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1, 0} } type DestinationOverride struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` } func (x *DestinationOverride) Reset() { *x = DestinationOverride{} if protoimpl.UnsafeEnabled { mi := &file_proxy_freedom_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DestinationOverride) String() string { return protoimpl.X.MessageStringOf(x) } func (*DestinationOverride) ProtoMessage() {} func (x *DestinationOverride) ProtoReflect() protoreflect.Message { mi := &file_proxy_freedom_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DestinationOverride.ProtoReflect.Descriptor instead. func (*DestinationOverride) Descriptor() ([]byte, []int) { return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0} } func (x *DestinationOverride) GetServer() *protocol.ServerEndpoint { if x != nil { return x.Server } return nil } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields DomainStrategy Config_DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=v2ray.core.proxy.freedom.Config_DomainStrategy" json:"domain_strategy,omitempty"` // Deprecated: Do not use. Timeout uint32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_freedom_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_freedom_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetDomainStrategy() Config_DomainStrategy { if x != nil { return x.DomainStrategy } return Config_AS_IS } // Deprecated: Do not use. func (x *Config) GetTimeout() uint32 { if x != nil { return x.Timeout } return 0 } func (x *Config) GetDestinationOverride() *DestinationOverride { if x != nil { return x.DestinationOverride } return nil } func (x *Config) GetUserLevel() uint32 { if x != nil { return x.UserLevel } return 0 } var File_proxy_freedom_config_proto protoreflect.FileDescriptor var file_proxy_freedom_config_proto_rawDesc = []byte{ 0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x13, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xc4, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x58, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x60, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x42, 0x59, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_freedom_config_proto_rawDescOnce sync.Once file_proxy_freedom_config_proto_rawDescData = file_proxy_freedom_config_proto_rawDesc ) func file_proxy_freedom_config_proto_rawDescGZIP() []byte { file_proxy_freedom_config_proto_rawDescOnce.Do(func() { file_proxy_freedom_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_freedom_config_proto_rawDescData) }) return file_proxy_freedom_config_proto_rawDescData } var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_freedom_config_proto_goTypes = []interface{}{ (Config_DomainStrategy)(0), // 0: v2ray.core.proxy.freedom.Config.DomainStrategy (*DestinationOverride)(nil), // 1: v2ray.core.proxy.freedom.DestinationOverride (*Config)(nil), // 2: v2ray.core.proxy.freedom.Config (*protocol.ServerEndpoint)(nil), // 3: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_freedom_config_proto_depIdxs = []int32{ 3, // 0: v2ray.core.proxy.freedom.DestinationOverride.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 0, // 1: v2ray.core.proxy.freedom.Config.domain_strategy:type_name -> v2ray.core.proxy.freedom.Config.DomainStrategy 1, // 2: v2ray.core.proxy.freedom.Config.destination_override:type_name -> v2ray.core.proxy.freedom.DestinationOverride 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_proxy_freedom_config_proto_init() } func file_proxy_freedom_config_proto_init() { if File_proxy_freedom_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_freedom_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DestinationOverride); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_freedom_config_proto_rawDesc, NumEnums: 1, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_freedom_config_proto_goTypes, DependencyIndexes: file_proxy_freedom_config_proto_depIdxs, EnumInfos: file_proxy_freedom_config_proto_enumTypes, MessageInfos: file_proxy_freedom_config_proto_msgTypes, }.Build() File_proxy_freedom_config_proto = out.File file_proxy_freedom_config_proto_rawDesc = nil file_proxy_freedom_config_proto_goTypes = nil file_proxy_freedom_config_proto_depIdxs = nil } ================================================ FILE: proxy/freedom/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.freedom; option csharp_namespace = "V2Ray.Core.Proxy.Freedom"; option go_package = "v2ray.com/core/proxy/freedom"; option java_package = "com.v2ray.core.proxy.freedom"; option java_multiple_files = true; import "common/protocol/server_spec.proto"; message DestinationOverride { v2ray.core.common.protocol.ServerEndpoint server = 1; } message Config { enum DomainStrategy { AS_IS = 0; USE_IP = 1; USE_IP4 = 2; USE_IP6 = 3; } DomainStrategy domain_strategy = 1; uint32 timeout = 2 [deprecated = true]; DestinationOverride destination_override = 3; uint32 user_level = 4; } ================================================ FILE: proxy/freedom/errors.generated.go ================================================ package freedom import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/freedom/freedom.go ================================================ // +build !confonly package freedom //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/dice" "v2ray.com/core/common/net" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/dns" "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { h := new(Handler) if err := core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) error { return h.Init(config.(*Config), pm, d) }); err != nil { return nil, err } return h, nil })) } // Handler handles Freedom connections. type Handler struct { policyManager policy.Manager dns dns.Client config *Config } // Init initializes the Handler with necessary parameters. func (h *Handler) Init(config *Config, pm policy.Manager, d dns.Client) error { h.config = config h.policyManager = pm h.dns = d return nil } func (h *Handler) policy() policy.Session { p := h.policyManager.ForLevel(h.config.UserLevel) if h.config.Timeout > 0 && h.config.UserLevel == 0 { p.Timeouts.ConnectionIdle = time.Duration(h.config.Timeout) * time.Second } return p } func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address { var lookupFunc func(string) ([]net.IP, error) = h.dns.LookupIP if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) { if lookupIPv4, ok := h.dns.(dns.IPv4Lookup); ok { lookupFunc = lookupIPv4.LookupIPv4 } } else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) { if lookupIPv6, ok := h.dns.(dns.IPv6Lookup); ok { lookupFunc = lookupIPv6.LookupIPv6 } } ips, err := lookupFunc(domain) if err != nil { newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx)) } if len(ips) == 0 { return nil } return net.IPAddress(ips[dice.Roll(len(ips))]) } func isValidAddress(addr *net.IPOrDomain) bool { if addr == nil { return false } a := addr.AsAddress() return a != net.AnyIP } // Process implements proxy.Outbound. func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } destination := outbound.Target if h.config.DestinationOverride != nil { server := h.config.DestinationOverride.Server if isValidAddress(server.Address) { destination.Address = server.Address.AsAddress() } if server.Port != 0 { destination.Port = net.Port(server.Port) } } newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx)) input := link.Reader output := link.Writer var conn internet.Connection err := retry.ExponentialBackoff(5, 100).On(func() error { dialDest := destination if h.config.useIP() && dialDest.Address.Family().IsDomain() { ip := h.resolveIP(ctx, dialDest.Address.Domain(), dialer.Address()) if ip != nil { dialDest = net.Destination{ Network: dialDest.Network, Address: ip, Port: dialDest.Port, } newError("dialing to to ", dialDest).WriteToLog(session.ExportIDToError(ctx)) } } rawConn, err := dialer.Dial(ctx, dialDest) if err != nil { return err } conn = rawConn return nil }) if err != nil { return newError("failed to open connection to ", destination).Base(err) } defer conn.Close() // nolint: errcheck plcy := h.policy() ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) var writer buf.Writer if destination.Network == net.Network_TCP { writer = buf.NewWriter(conn) } else { writer = &buf.SequentialWriter{Writer: conn} } if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to process request").Base(err) } return nil } responseDone := func() error { defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) var reader buf.Reader if destination.Network == net.Network_TCP { reader = buf.NewReader(conn) } else { reader = buf.NewPacketReader(conn) } if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil { return newError("failed to process response").Base(err) } return nil } if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil { return newError("connection ends").Base(err) } return nil } ================================================ FILE: proxy/http/client.go ================================================ // +build !confonly package http import ( "bufio" "context" "encoding/base64" "io" "net/http" "net/url" "sync" "golang.org/x/net/http2" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/bytespool" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" ) type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager } type h2Conn struct { rawConn net.Conn h2Conn *http2.ClientConn } var ( cachedH2Mutex sync.Mutex cachedH2Conns map[net.Destination]h2Conn ) // NewClient create a new http client based on the given config. func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { serverList := protocol.NewServerList() for _, rec := range config.Server { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to get server spec").Base(err) } serverList.AddServer(s) } if serverList.Size() == 0 { return nil, newError("0 target server") } v := core.MustFromContext(ctx) return &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), }, nil } // Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel. func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } target := outbound.Target targetAddr := target.NetAddr() if target.Network == net.Network_UDP { return newError("UDP is not supported by HTTP outbound") } var user *protocol.MemoryUser var conn internet.Connection mbuf, _ := link.Reader.ReadMultiBuffer() len := mbuf.Len() firstPayload := bytespool.Alloc(len) mbuf, _ = buf.SplitBytes(mbuf, firstPayload) firstPayload = firstPayload[:len] buf.ReleaseMulti(mbuf) defer bytespool.Free(firstPayload) if err := retry.ExponentialBackoff(5, 100).On(func() error { server := c.serverPicker.PickServer() dest := server.Destination() user = server.PickUser() netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload) if netConn != nil { conn = internet.Connection(netConn) } return err }); err != nil { return newError("failed to find an available destination").Base(err) } defer func() { if err := conn.Close(); err != nil { newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) } }() p := c.policyManager.ForLevel(0) if user != nil { p = c.policyManager.ForLevel(user.Level) } ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) requestFunc := func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) } responseFunc := func() error { defer timer.SetTimeout(p.Timeouts.UplinkOnly) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return newError("connection ends").Base(err) } return nil } // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) { req := &http.Request{ Method: http.MethodConnect, URL: &url.URL{Host: target}, Header: make(http.Header), Host: target, } if user != nil && user.Account != nil { account := user.Account.(*Account) auth := account.GetUsername() + ":" + account.GetPassword() req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") err := req.Write(rawConn) if err != nil { rawConn.Close() return nil, err } if _, err := rawConn.Write(firstPayload); err != nil { rawConn.Close() return nil, err } resp, err := http.ReadResponse(bufio.NewReader(rawConn), req) if err != nil { rawConn.Close() return nil, err } if resp.StatusCode != http.StatusOK { rawConn.Close() return nil, newError("Proxy responded with non 200 code: " + resp.Status) } return rawConn, nil } connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) { pr, pw := io.Pipe() req.Body = pr var pErr error var wg sync.WaitGroup wg.Add(1) go func() { _, pErr = pw.Write(firstPayload) wg.Done() }() resp, err := h2clientConn.RoundTrip(req) if err != nil { rawConn.Close() return nil, err } wg.Wait() if pErr != nil { rawConn.Close() return nil, pErr } if resp.StatusCode != http.StatusOK { rawConn.Close() return nil, newError("Proxy responded with non 200 code: " + resp.Status) } return newHTTP2Conn(rawConn, pw, resp.Body), nil } cachedH2Mutex.Lock() cachedConn, cachedConnFound := cachedH2Conns[dest] cachedH2Mutex.Unlock() if cachedConnFound { rc, cc := cachedConn.rawConn, cachedConn.h2Conn if cc.CanTakeNewRequest() { proxyConn, err := connectHTTP2(rc, cc) if err != nil { return nil, err } return proxyConn, nil } } rawConn, err := dialer.Dial(ctx, dest) if err != nil { return nil, err } iConn := rawConn if statConn, ok := iConn.(*internet.StatCouterConnection); ok { iConn = statConn.Connection } nextProto := "" if tlsConn, ok := iConn.(*tls.Conn); ok { if err := tlsConn.Handshake(); err != nil { rawConn.Close() return nil, err } nextProto = tlsConn.ConnectionState().NegotiatedProtocol } switch nextProto { case "", "http/1.1": return connectHTTP1(rawConn) case "h2": t := http2.Transport{} h2clientConn, err := t.NewClientConn(rawConn) if err != nil { rawConn.Close() return nil, err } proxyConn, err := connectHTTP2(rawConn, h2clientConn) if err != nil { rawConn.Close() return nil, err } cachedH2Mutex.Lock() if cachedH2Conns == nil { cachedH2Conns = make(map[net.Destination]h2Conn) } cachedH2Conns[dest] = h2Conn{ rawConn: rawConn, h2Conn: h2clientConn, } cachedH2Mutex.Unlock() return proxyConn, err default: return nil, newError("negotiated unsupported application layer protocol: " + nextProto) } } func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn { return &http2Conn{Conn: c, in: pipedReqBody, out: respBody} } type http2Conn struct { net.Conn in *io.PipeWriter out io.ReadCloser } func (h *http2Conn) Read(p []byte) (n int, err error) { return h.out.Read(p) } func (h *http2Conn) Write(p []byte) (n int, err error) { return h.in.Write(p) } func (h *http2Conn) Close() error { h.in.Close() return h.out.Close() } func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewClient(ctx, config.(*ClientConfig)) })) } ================================================ FILE: proxy/http/config.go ================================================ package http import ( "v2ray.com/core/common/protocol" ) func (a *Account) Equals(another protocol.Account) bool { if account, ok := another.(*Account); ok { return a.Username == account.Username } return false } func (a *Account) AsAccount() (protocol.Account, error) { return a, nil } func (sc *ServerConfig) HasAccount(username, password string) bool { if sc.Accounts == nil { return false } p, found := sc.Accounts[username] if !found { return false } return p == password } ================================================ FILE: proxy/http/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/http/config.proto package http import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_http_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_http_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_http_config_proto_rawDescGZIP(), []int{0} } func (x *Account) GetUsername() string { if x != nil { return x.Username } return "" } func (x *Account) GetPassword() string { if x != nil { return x.Password } return "" } // Config for HTTP proxy server. type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Deprecated: Do not use. Timeout uint32 `protobuf:"varint,1,opt,name=timeout,proto3" json:"timeout,omitempty"` Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` AllowTransparent bool `protobuf:"varint,3,opt,name=allow_transparent,json=allowTransparent,proto3" json:"allow_transparent,omitempty"` UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` } func (x *ServerConfig) Reset() { *x = ServerConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_http_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerConfig) ProtoMessage() {} func (x *ServerConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_http_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_http_config_proto_rawDescGZIP(), []int{1} } // Deprecated: Do not use. func (x *ServerConfig) GetTimeout() uint32 { if x != nil { return x.Timeout } return 0 } func (x *ServerConfig) GetAccounts() map[string]string { if x != nil { return x.Accounts } return nil } func (x *ServerConfig) GetAllowTransparent() bool { if x != nil { return x.AllowTransparent } return false } func (x *ServerConfig) GetUserLevel() uint32 { if x != nil { return x.UserLevel } return 0 } // ClientConfig is the protobuf config for HTTP proxy client. type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Sever is a list of HTTP server addresses. Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_http_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ClientConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_http_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { return file_proxy_http_config_proto_rawDescGZIP(), []int{2} } func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { if x != nil { return x.Server } return nil } var File_proxy_http_config_proto protoreflect.FileDescriptor var file_proxy_http_config_proto_rawDesc = []byte{ 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x84, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x4d, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x3b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x52, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x50, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x19, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_http_config_proto_rawDescOnce sync.Once file_proxy_http_config_proto_rawDescData = file_proxy_http_config_proto_rawDesc ) func file_proxy_http_config_proto_rawDescGZIP() []byte { file_proxy_http_config_proto_rawDescOnce.Do(func() { file_proxy_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_http_config_proto_rawDescData) }) return file_proxy_http_config_proto_rawDescData } var file_proxy_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proxy_http_config_proto_goTypes = []interface{}{ (*Account)(nil), // 0: v2ray.core.proxy.http.Account (*ServerConfig)(nil), // 1: v2ray.core.proxy.http.ServerConfig (*ClientConfig)(nil), // 2: v2ray.core.proxy.http.ClientConfig nil, // 3: v2ray.core.proxy.http.ServerConfig.AccountsEntry (*protocol.ServerEndpoint)(nil), // 4: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_http_config_proto_depIdxs = []int32{ 3, // 0: v2ray.core.proxy.http.ServerConfig.accounts:type_name -> v2ray.core.proxy.http.ServerConfig.AccountsEntry 4, // 1: v2ray.core.proxy.http.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proxy_http_config_proto_init() } func file_proxy_http_config_proto_init() { if File_proxy_http_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_http_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_http_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_http_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_http_config_proto_rawDesc, NumEnums: 0, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_http_config_proto_goTypes, DependencyIndexes: file_proxy_http_config_proto_depIdxs, MessageInfos: file_proxy_http_config_proto_msgTypes, }.Build() File_proxy_http_config_proto = out.File file_proxy_http_config_proto_rawDesc = nil file_proxy_http_config_proto_goTypes = nil file_proxy_http_config_proto_depIdxs = nil } ================================================ FILE: proxy/http/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.http; option csharp_namespace = "V2Ray.Core.Proxy.Http"; option go_package = "v2ray.com/core/proxy/http"; option java_package = "com.v2ray.core.proxy.http"; option java_multiple_files = true; import "common/protocol/server_spec.proto"; message Account { string username = 1; string password = 2; } // Config for HTTP proxy server. message ServerConfig { uint32 timeout = 1 [deprecated = true]; map accounts = 2; bool allow_transparent = 3; uint32 user_level = 4; } // ClientConfig is the protobuf config for HTTP proxy client. message ClientConfig { // Sever is a list of HTTP server addresses. repeated v2ray.core.common.protocol.ServerEndpoint server = 1; } ================================================ FILE: proxy/http/errors.generated.go ================================================ package http import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/http/http.go ================================================ package http //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: proxy/http/server.go ================================================ // +build !confonly package http import ( "bufio" "context" "encoding/base64" "io" "net/http" "strings" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" http_proto "v2ray.com/core/common/protocol/http" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" ) // Server is an HTTP proxy server. type Server struct { config *ServerConfig policyManager policy.Manager } // NewServer creates a new HTTP inbound handler. func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { v := core.MustFromContext(ctx) s := &Server{ config: config, policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return s, nil } func (s *Server) policy() policy.Session { config := s.config p := s.policyManager.ForLevel(config.UserLevel) if config.Timeout > 0 && config.UserLevel == 0 { p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second } return p } // Network implements proxy.Inbound. func (*Server) Network() []net.Network { return []net.Network{net.Network_TCP} } func isTimeout(err error) bool { nerr, ok := errors.Cause(err).(net.Error) return ok && nerr.Timeout() } func parseBasicAuth(auth string) (username, password string, ok bool) { const prefix = "Basic " if !strings.HasPrefix(auth, prefix) { return } c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) if err != nil { return } cs := string(c) s := strings.IndexByte(cs, ':') if s < 0 { return } return cs[:s], cs[s+1:], true } type readerOnly struct { io.Reader } func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { inbound := session.InboundFromContext(ctx) if inbound != nil { inbound.User = &protocol.MemoryUser{ Level: s.config.UserLevel, } } reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size) Start: if err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil { newError("failed to set read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } request, err := http.ReadRequest(reader) if err != nil { trace := newError("failed to read http request").Base(err) if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) { trace.AtWarning() // nolint: errcheck } return trace } if len(s.config.Accounts) > 0 { user, pass, ok := parseBasicAuth(request.Header.Get("Proxy-Authorization")) if !ok || !s.config.HasAccount(user, pass) { return common.Error2(conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"proxy\"\r\nConnection: close\r\n\r\n"))) } if inbound != nil { inbound.User.Email = user } } newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]").WriteToLog(session.ExportIDToError(ctx)) if err := conn.SetReadDeadline(time.Time{}); err != nil { newError("failed to clear read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } defaultPort := net.Port(80) if strings.EqualFold(request.URL.Scheme, "https") { defaultPort = net.Port(443) } host := request.Host if host == "" { host = request.URL.Host } dest, err := http_proto.ParseHost(host, defaultPort) if err != nil { return newError("malformed proxy host: ", host).AtWarning().Base(err) } ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: conn.RemoteAddr(), To: request.URL, Status: log.AccessAccepted, Reason: "", }) if strings.EqualFold(request.Method, "CONNECT") { return s.handleConnect(ctx, request, reader, conn, dest, dispatcher) } keepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive") err = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher) if err == errWaitAnother { if keepAlive { goto Start } err = nil } return err } func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher routing.Dispatcher) error { _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) if err != nil { return newError("failed to write back OK response").Base(err) } plcy := s.policy() ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return err } if reader.Buffered() > 0 { payload, err := buf.ReadFrom(io.LimitReader(reader, int64(reader.Buffered()))) if err != nil { return err } if err := link.Writer.WriteMultiBuffer(payload); err != nil { return err } reader = nil } requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } responseDone := func() error { defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(conn) if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil { return err } return nil } var closeWriter = task.OnSuccess(requestDone, task.Close(link.Writer)) if err := task.Run(ctx, closeWriter, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } var errWaitAnother = newError("keep alive") func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error { if !s.config.AllowTransparent && request.URL.Host == "" { // RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy. response := &http.Response{ Status: "Bad Request", StatusCode: 400, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header(make(map[string][]string)), Body: nil, ContentLength: 0, Close: true, } response.Header.Set("Proxy-Connection", "close") response.Header.Set("Connection", "close") return response.Write(writer) } if len(request.URL.Host) > 0 { request.Host = request.URL.Host } http_proto.RemoveHopByHopHeaders(request.Header) // Prevent UA from being set to golang's default ones if request.Header.Get("User-Agent") == "" { request.Header.Set("User-Agent", "") } content := &session.Content{ Protocol: "http/1.1", } content.SetAttribute(":method", strings.ToUpper(request.Method)) content.SetAttribute(":path", request.URL.Path) for key := range request.Header { value := request.Header.Get(key) content.SetAttribute(strings.ToLower(key), value) } ctx = session.ContextWithContent(ctx, content) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return err } // Plain HTTP request is not a stream. The request always finishes before response. Hense request has to be closed later. defer common.Close(link.Writer) // nolint: errcheck var result error = errWaitAnother requestDone := func() error { request.Header.Set("Connection", "close") requestWriter := buf.NewBufferedWriter(link.Writer) common.Must(requestWriter.SetBuffered(false)) if err := request.Write(requestWriter); err != nil { return newError("failed to write whole request").Base(err).AtWarning() } return nil } responseDone := func() error { responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size) response, err := http.ReadResponse(responseReader, request) if err == nil { http_proto.RemoveHopByHopHeaders(response.Header) if response.ContentLength >= 0 { response.Header.Set("Proxy-Connection", "keep-alive") response.Header.Set("Connection", "keep-alive") response.Header.Set("Keep-Alive", "timeout=4") response.Close = false } else { response.Close = true result = nil } } else { newError("failed to read response from ", request.Host).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) response = &http.Response{ Status: "Service Unavailable", StatusCode: 503, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header(make(map[string][]string)), Body: nil, ContentLength: 0, Close: true, } response.Header.Set("Connection", "close") response.Header.Set("Proxy-Connection", "close") } if err := response.Write(writer); err != nil { return newError("failed to write response").Base(err).AtWarning() } return nil } if err := task.Run(ctx, requestDone, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return result } func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewServer(ctx, config.(*ServerConfig)) })) } ================================================ FILE: proxy/mtproto/auth.go ================================================ package mtproto import ( "context" "crypto/rand" "crypto/sha256" "io" "sync" "v2ray.com/core/common" ) const ( HeaderSize = 64 ) type SessionContext struct { ConnectionType [4]byte DataCenterID uint16 } func DefaultSessionContext() SessionContext { return SessionContext{ ConnectionType: [4]byte{0xef, 0xef, 0xef, 0xef}, DataCenterID: 0, } } type contextKey int32 const ( sessionContextKey contextKey = iota ) func ContextWithSessionContext(ctx context.Context, c SessionContext) context.Context { return context.WithValue(ctx, sessionContextKey, c) } func SessionContextFromContext(ctx context.Context) SessionContext { if c := ctx.Value(sessionContextKey); c != nil { return c.(SessionContext) } return DefaultSessionContext() } type Authentication struct { Header [HeaderSize]byte DecodingKey [32]byte EncodingKey [32]byte DecodingNonce [16]byte EncodingNonce [16]byte } func (a *Authentication) DataCenterID() uint16 { x := ((int16(a.Header[61]) << 8) | int16(a.Header[60])) if x < 0 { x = -x } return uint16(x) - 1 } func (a *Authentication) ConnectionType() [4]byte { var x [4]byte copy(x[:], a.Header[56:60]) return x } func (a *Authentication) ApplySecret(b []byte) { a.DecodingKey = sha256.Sum256(append(a.DecodingKey[:], b...)) a.EncodingKey = sha256.Sum256(append(a.EncodingKey[:], b...)) } func generateRandomBytes(random []byte, connType [4]byte) { for { common.Must2(rand.Read(random)) if random[0] == 0xef { continue } val := (uint32(random[3]) << 24) | (uint32(random[2]) << 16) | (uint32(random[1]) << 8) | uint32(random[0]) if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { continue } if (uint32(random[7])<<24)|(uint32(random[6])<<16)|(uint32(random[5])<<8)|uint32(random[4]) == 0x00000000 { continue } copy(random[56:60], connType[:]) return } } func NewAuthentication(sc SessionContext) *Authentication { auth := getAuthenticationObject() random := auth.Header[:] generateRandomBytes(random, sc.ConnectionType) copy(auth.EncodingKey[:], random[8:]) copy(auth.EncodingNonce[:], random[8+32:]) keyivInverse := Inverse(random[8 : 8+32+16]) copy(auth.DecodingKey[:], keyivInverse) copy(auth.DecodingNonce[:], keyivInverse[32:]) return auth } func ReadAuthentication(reader io.Reader) (*Authentication, error) { auth := getAuthenticationObject() if _, err := io.ReadFull(reader, auth.Header[:]); err != nil { putAuthenticationObject(auth) return nil, err } copy(auth.DecodingKey[:], auth.Header[8:]) copy(auth.DecodingNonce[:], auth.Header[8+32:]) keyivInverse := Inverse(auth.Header[8 : 8+32+16]) copy(auth.EncodingKey[:], keyivInverse) copy(auth.EncodingNonce[:], keyivInverse[32:]) return auth, nil } // Inverse returns a new byte array. It is a sequence of bytes when the input is read from end to beginning.Inverse // Visible for testing only. func Inverse(b []byte) []byte { lenb := len(b) b2 := make([]byte, lenb) for i, v := range b { b2[lenb-i-1] = v } return b2 } var ( authPool = sync.Pool{ New: func() interface{} { return new(Authentication) }, } ) func getAuthenticationObject() *Authentication { return authPool.Get().(*Authentication) } func putAuthenticationObject(auth *Authentication) { authPool.Put(auth) } ================================================ FILE: proxy/mtproto/auth_test.go ================================================ package mtproto_test import ( "bytes" "crypto/rand" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/proxy/mtproto" ) func TestInverse(t *testing.T) { const size = 64 b := make([]byte, 64) for b[0] == b[size-1] { common.Must2(rand.Read(b)) } bi := Inverse(b) if b[0] == bi[0] { t.Fatal("seems bytes are not inversed: ", b[0], "vs", bi[0]) } bii := Inverse(bi) if r := cmp.Diff(bii, b); r != "" { t.Fatal(r) } } func TestAuthenticationReadWrite(t *testing.T) { a := NewAuthentication(DefaultSessionContext()) b := bytes.NewReader(a.Header[:]) a2, err := ReadAuthentication(b) common.Must(err) if r := cmp.Diff(a.EncodingKey[:], a2.DecodingKey[:]); r != "" { t.Error("decoding key: ", r) } if r := cmp.Diff(a.EncodingNonce[:], a2.DecodingNonce[:]); r != "" { t.Error("decoding nonce: ", r) } if r := cmp.Diff(a.DecodingKey[:], a2.EncodingKey[:]); r != "" { t.Error("encoding key: ", r) } if r := cmp.Diff(a.DecodingNonce[:], a2.EncodingNonce[:]); r != "" { t.Error("encoding nonce: ", r) } } ================================================ FILE: proxy/mtproto/client.go ================================================ package mtproto import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/common/task" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) type Client struct { } func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { return &Client{}, nil } func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("unknown destination.") } dest := outbound.Target if dest.Network != net.Network_TCP { return newError("not TCP traffic", dest) } conn, err := dialer.Dial(ctx, dest) if err != nil { return newError("failed to dial to ", dest).Base(err).AtWarning() } defer conn.Close() // nolint: errcheck sc := SessionContextFromContext(ctx) auth := NewAuthentication(sc) defer putAuthenticationObject(auth) request := func() error { encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:]) var header [HeaderSize]byte encryptor.XORKeyStream(header[:], auth.Header[:]) copy(header[:56], auth.Header[:]) if _, err := conn.Write(header[:]); err != nil { return newError("failed to write auth header").Base(err) } connWriter := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn)) return buf.Copy(link.Reader, connWriter) } response := func() error { decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:]) connReader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn)) return buf.Copy(connReader, link.Writer) } var responseDoneAndCloseWriter = task.OnSuccess(response, task.Close(link.Writer)) if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil { return newError("connection ends").Base(err) } return nil } func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewClient(ctx, config.(*ClientConfig)) })) } ================================================ FILE: proxy/mtproto/config.go ================================================ package mtproto import ( "v2ray.com/core/common/protocol" ) func (a *Account) Equals(another protocol.Account) bool { aa, ok := another.(*Account) if !ok { return false } if len(a.Secret) != len(aa.Secret) { return false } for i, v := range a.Secret { if v != aa.Secret[i] { return false } } return true } ================================================ FILE: proxy/mtproto/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/mtproto/config.proto package mtproto import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Secret []byte `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_mtproto_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_mtproto_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{0} } func (x *Account) GetSecret() []byte { if x != nil { return x.Secret } return nil } type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // User is a list of users that allowed to connect to this inbound. // Although this is a repeated field, only the first user is effective for // now. User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` } func (x *ServerConfig) Reset() { *x = ServerConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_mtproto_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerConfig) ProtoMessage() {} func (x *ServerConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_mtproto_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{1} } func (x *ServerConfig) GetUser() []*protocol.User { if x != nil { return x.User } return nil } type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_mtproto_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ClientConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_mtproto_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{2} } var File_proxy_mtproto_config_proto protoreflect.FileDescriptor var file_proxy_mtproto_config_proto_rawDesc = []byte{ 0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x44, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x0e, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x59, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x4d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_mtproto_config_proto_rawDescOnce sync.Once file_proxy_mtproto_config_proto_rawDescData = file_proxy_mtproto_config_proto_rawDesc ) func file_proxy_mtproto_config_proto_rawDescGZIP() []byte { file_proxy_mtproto_config_proto_rawDescOnce.Do(func() { file_proxy_mtproto_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_mtproto_config_proto_rawDescData) }) return file_proxy_mtproto_config_proto_rawDescData } var file_proxy_mtproto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_mtproto_config_proto_goTypes = []interface{}{ (*Account)(nil), // 0: v2ray.core.proxy.mtproto.Account (*ServerConfig)(nil), // 1: v2ray.core.proxy.mtproto.ServerConfig (*ClientConfig)(nil), // 2: v2ray.core.proxy.mtproto.ClientConfig (*protocol.User)(nil), // 3: v2ray.core.common.protocol.User } var file_proxy_mtproto_config_proto_depIdxs = []int32{ 3, // 0: v2ray.core.proxy.mtproto.ServerConfig.user:type_name -> v2ray.core.common.protocol.User 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_mtproto_config_proto_init() } func file_proxy_mtproto_config_proto_init() { if File_proxy_mtproto_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_mtproto_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_mtproto_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_mtproto_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_mtproto_config_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_mtproto_config_proto_goTypes, DependencyIndexes: file_proxy_mtproto_config_proto_depIdxs, MessageInfos: file_proxy_mtproto_config_proto_msgTypes, }.Build() File_proxy_mtproto_config_proto = out.File file_proxy_mtproto_config_proto_rawDesc = nil file_proxy_mtproto_config_proto_goTypes = nil file_proxy_mtproto_config_proto_depIdxs = nil } ================================================ FILE: proxy/mtproto/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.mtproto; option csharp_namespace = "V2Ray.Core.Proxy.Mtproto"; option go_package = "v2ray.com/core/proxy/mtproto"; option java_package = "com.v2ray.core.proxy.mtproto"; option java_multiple_files = true; import "common/protocol/user.proto"; message Account { bytes secret = 1; } message ServerConfig { // User is a list of users that allowed to connect to this inbound. // Although this is a repeated field, only the first user is effective for // now. repeated v2ray.core.common.protocol.User user = 1; } message ClientConfig {} ================================================ FILE: proxy/mtproto/errors.generated.go ================================================ package mtproto import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/mtproto/mtproto.go ================================================ package mtproto //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: proxy/mtproto/server.go ================================================ // +build !confonly package mtproto import ( "bytes" "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" ) var ( dcList = []net.Address{ net.ParseAddress("149.154.175.50"), net.ParseAddress("149.154.167.51"), net.ParseAddress("149.154.175.100"), net.ParseAddress("149.154.167.91"), net.ParseAddress("149.154.171.5"), } ) type Server struct { user *protocol.User account *Account policy policy.Manager } func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { if len(config.User) == 0 { return nil, newError("no user configured.") } user := config.User[0] rawAccount, err := config.User[0].GetTypedAccount() if err != nil { return nil, newError("invalid account").Base(err) } account, ok := rawAccount.(*Account) if !ok { return nil, newError("not a MTProto account") } v := core.MustFromContext(ctx) return &Server{ user: user, account: account, policy: v.GetFeature(policy.ManagerType()).(policy.Manager), }, nil } func (s *Server) Network() []net.Network { return []net.Network{net.Network_TCP} } var ctype1 = []byte{0xef, 0xef, 0xef, 0xef} var ctype2 = []byte{0xee, 0xee, 0xee, 0xee} func isValidConnectionType(c [4]byte) bool { if bytes.Equal(c[:], ctype1) { return true } if bytes.Equal(c[:], ctype2) { return true } return false } func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { sPolicy := s.policy.ForLevel(s.user.Level) if err := conn.SetDeadline(time.Now().Add(sPolicy.Timeouts.Handshake)); err != nil { newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } auth, err := ReadAuthentication(conn) if err != nil { return newError("failed to read authentication header").Base(err) } defer putAuthenticationObject(auth) if err := conn.SetDeadline(time.Time{}); err != nil { newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } auth.ApplySecret(s.account.Secret) decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:]) decryptor.XORKeyStream(auth.Header[:], auth.Header[:]) ct := auth.ConnectionType() if !isValidConnectionType(ct) { return newError("invalid connection type: ", ct) } dcID := auth.DataCenterID() if dcID >= uint16(len(dcList)) { return newError("invalid datacenter id: ", dcID) } dest := net.Destination{ Network: net.Network_TCP, Address: dcList[dcID], Port: net.Port(443), } ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sPolicy.Buffer) sc := SessionContext{ ConnectionType: ct, DataCenterID: dcID, } ctx = ContextWithSessionContext(ctx, sc) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return newError("failed to dispatch request to: ", dest).Base(err) } request := func() error { defer timer.SetTimeout(sPolicy.Timeouts.DownlinkOnly) reader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn)) return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } response := func() error { defer timer.SetTimeout(sPolicy.Timeouts.UplinkOnly) encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:]) writer := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn)) return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) } var responseDoneAndCloseWriter = task.OnSuccess(response, task.Close(link.Writer)) if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewServer(ctx, config.(*ServerConfig)) })) } ================================================ FILE: proxy/proxy.go ================================================ // Package proxy contains all proxies used by V2Ray. // // To implement an inbound or outbound proxy, one needs to do the following: // 1. Implement the interface(s) below. // 2. Register a config creator through common.RegisterConfig. package proxy import ( "context" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/features/routing" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // An Inbound processes inbound connections. type Inbound interface { // Network returns a list of networks that this inbound supports. Connections with not-supported networks will not be passed into Process(). Network() []net.Network // Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound. Process(context.Context, net.Network, internet.Connection, routing.Dispatcher) error } // An Outbound process outbound connections. type Outbound interface { // Process processes the given connection. The given dialer may be used to dial a system outbound connection. Process(context.Context, *transport.Link, internet.Dialer) error } // UserManager is the interface for Inbounds and Outbounds that can manage their users. type UserManager interface { // AddUser adds a new user. AddUser(context.Context, *protocol.MemoryUser) error // RemoveUser removes a user by email. RemoveUser(context.Context, string) error } type GetInbound interface { GetInbound() Inbound } type GetOutbound interface { GetOutbound() Outbound } ================================================ FILE: proxy/shadowsocks/client.go ================================================ // +build !confonly package shadowsocks import ( "context" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // Client is a inbound handler for Shadowsocks protocol type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager } // NewClient create a new Shadowsocks client. func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { serverList := protocol.NewServerList() for _, rec := range config.Server { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to parse server spec").Base(err) } serverList.AddServer(s) } if serverList.Size() == 0 { return nil, newError("0 server") } v := core.MustFromContext(ctx) client := &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return client, nil } // Process implements OutboundHandler.Process(). func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified") } destination := outbound.Target network := destination.Network var server *protocol.ServerSpec var conn internet.Connection err := retry.ExponentialBackoff(5, 100).On(func() error { server = c.serverPicker.PickServer() dest := server.Destination() dest.Network = network rawConn, err := dialer.Dial(ctx, dest) if err != nil { return err } conn = rawConn return nil }) if err != nil { return newError("failed to find an available destination").AtWarning().Base(err) } newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog(session.ExportIDToError(ctx)) defer conn.Close() request := &protocol.RequestHeader{ Version: Version, Address: destination.Address, Port: destination.Port, } if destination.Network == net.Network_TCP { request.Command = protocol.RequestCommandTCP } else { request.Command = protocol.RequestCommandUDP } user := server.PickUser() _, ok := user.Account.(*MemoryAccount) if !ok { return newError("user account is not valid") } request.User = user sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) if request.Command == protocol.RequestCommandTCP { bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) bodyWriter, err := WriteTCPRequest(request, bufferedWriter) if err != nil { return newError("failed to write request").Base(err) } if err := bufferedWriter.SetBuffered(false); err != nil { return err } requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) return buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)) } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) responseReader, err := ReadTCPResponse(user, conn) if err != nil { return err } return buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer)) } var responseDoneAndCloseWriter = task.OnSuccess(responseDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil { return newError("connection ends").Base(err) } return nil } if request.Command == protocol.RequestCommandUDP { writer := &buf.SequentialWriter{Writer: &UDPWriter{ Writer: conn, Request: request, }} requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all UDP request").Base(err) } return nil } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) reader := &UDPReader{ Reader: conn, User: user, } if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all UDP response").Base(err) } return nil } var responseDoneAndCloseWriter = task.OnSuccess(responseDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil { return newError("connection ends").Base(err) } return nil } return nil } func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewClient(ctx, config.(*ClientConfig)) })) } ================================================ FILE: proxy/shadowsocks/config.go ================================================ package shadowsocks import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/sha1" "io" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/protocol" ) // MemoryAccount is an account type converted from Account. type MemoryAccount struct { Cipher Cipher Key []byte } // Equals implements protocol.Account.Equals(). func (a *MemoryAccount) Equals(another protocol.Account) bool { if account, ok := another.(*MemoryAccount); ok { return bytes.Equal(a.Key, account.Key) } return false } func createAesGcm(key []byte) cipher.AEAD { block, err := aes.NewCipher(key) common.Must(err) gcm, err := cipher.NewGCM(block) common.Must(err) return gcm } func createChacha20Poly1305(key []byte) cipher.AEAD { chacha20, err := chacha20poly1305.New(key) common.Must(err) return chacha20 } func (a *Account) getCipher() (Cipher, error) { switch a.CipherType { case CipherType_AES_128_CFB: return &AesCfb{KeyBytes: 16}, nil case CipherType_AES_256_CFB: return &AesCfb{KeyBytes: 32}, nil case CipherType_CHACHA20: return &ChaCha20{IVBytes: 8}, nil case CipherType_CHACHA20_IETF: return &ChaCha20{IVBytes: 12}, nil case CipherType_AES_128_GCM: return &AEADCipher{ KeyBytes: 16, IVBytes: 16, AEADAuthCreator: createAesGcm, }, nil case CipherType_AES_256_GCM: return &AEADCipher{ KeyBytes: 32, IVBytes: 32, AEADAuthCreator: createAesGcm, }, nil case CipherType_CHACHA20_POLY1305: return &AEADCipher{ KeyBytes: 32, IVBytes: 32, AEADAuthCreator: createChacha20Poly1305, }, nil case CipherType_NONE: return NoneCipher{}, nil default: return nil, newError("Unsupported cipher.") } } // AsAccount implements protocol.AsAccount. func (a *Account) AsAccount() (protocol.Account, error) { cipher, err := a.getCipher() if err != nil { return nil, newError("failed to get cipher").Base(err) } return &MemoryAccount{ Cipher: cipher, Key: passwordToCipherKey([]byte(a.Password), cipher.KeySize()), }, nil } // Cipher is an interface for all Shadowsocks ciphers. type Cipher interface { KeySize() int32 IVSize() int32 NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) IsAEAD() bool EncodePacket(key []byte, b *buf.Buffer) error DecodePacket(key []byte, b *buf.Buffer) error } // AesCfb represents all AES-CFB ciphers. type AesCfb struct { KeyBytes int32 } func (*AesCfb) IsAEAD() bool { return false } func (v *AesCfb) KeySize() int32 { return v.KeyBytes } func (v *AesCfb) IVSize() int32 { return 16 } func (v *AesCfb) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { stream := crypto.NewAesEncryptionStream(key, iv) return &buf.SequentialWriter{Writer: crypto.NewCryptionWriter(stream, writer)}, nil } func (v *AesCfb) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { stream := crypto.NewAesDecryptionStream(key, iv) return &buf.SingleReader{ Reader: crypto.NewCryptionReader(stream, reader), }, nil } func (v *AesCfb) EncodePacket(key []byte, b *buf.Buffer) error { iv := b.BytesTo(v.IVSize()) stream := crypto.NewAesEncryptionStream(key, iv) stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize())) return nil } func (v *AesCfb) DecodePacket(key []byte, b *buf.Buffer) error { if b.Len() <= v.IVSize() { return newError("insufficient data: ", b.Len()) } iv := b.BytesTo(v.IVSize()) stream := crypto.NewAesDecryptionStream(key, iv) stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize())) b.Advance(v.IVSize()) return nil } type AEADCipher struct { KeyBytes int32 IVBytes int32 AEADAuthCreator func(key []byte) cipher.AEAD } func (*AEADCipher) IsAEAD() bool { return true } func (c *AEADCipher) KeySize() int32 { return c.KeyBytes } func (c *AEADCipher) IVSize() int32 { return c.IVBytes } func (c *AEADCipher) createAuthenticator(key []byte, iv []byte) *crypto.AEADAuthenticator { nonce := crypto.GenerateInitialAEADNonce() subkey := make([]byte, c.KeyBytes) hkdfSHA1(key, iv, subkey) return &crypto.AEADAuthenticator{ AEAD: c.AEADAuthCreator(subkey), NonceGenerator: nonce, } } func (c *AEADCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { auth := c.createAuthenticator(key, iv) return crypto.NewAuthenticationWriter(auth, &crypto.AEADChunkSizeParser{ Auth: auth, }, writer, protocol.TransferTypeStream, nil), nil } func (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { auth := c.createAuthenticator(key, iv) return crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{ Auth: auth, }, reader, protocol.TransferTypeStream, nil), nil } func (c *AEADCipher) EncodePacket(key []byte, b *buf.Buffer) error { ivLen := c.IVSize() payloadLen := b.Len() auth := c.createAuthenticator(key, b.BytesTo(ivLen)) b.Extend(int32(auth.Overhead())) _, err := auth.Seal(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen)) return err } func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error { if b.Len() <= c.IVSize() { return newError("insufficient data: ", b.Len()) } ivLen := c.IVSize() payloadLen := b.Len() auth := c.createAuthenticator(key, b.BytesTo(ivLen)) bbb, err := auth.Open(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen)) if err != nil { return err } b.Resize(ivLen, int32(len(bbb))) return nil } type ChaCha20 struct { IVBytes int32 } func (*ChaCha20) IsAEAD() bool { return false } func (v *ChaCha20) KeySize() int32 { return 32 } func (v *ChaCha20) IVSize() int32 { return v.IVBytes } func (v *ChaCha20) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { stream := crypto.NewChaCha20Stream(key, iv) return &buf.SequentialWriter{Writer: crypto.NewCryptionWriter(stream, writer)}, nil } func (v *ChaCha20) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { stream := crypto.NewChaCha20Stream(key, iv) return &buf.SingleReader{Reader: crypto.NewCryptionReader(stream, reader)}, nil } func (v *ChaCha20) EncodePacket(key []byte, b *buf.Buffer) error { iv := b.BytesTo(v.IVSize()) stream := crypto.NewChaCha20Stream(key, iv) stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize())) return nil } func (v *ChaCha20) DecodePacket(key []byte, b *buf.Buffer) error { if b.Len() <= v.IVSize() { return newError("insufficient data: ", b.Len()) } iv := b.BytesTo(v.IVSize()) stream := crypto.NewChaCha20Stream(key, iv) stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize())) b.Advance(v.IVSize()) return nil } type NoneCipher struct{} func (NoneCipher) KeySize() int32 { return 0 } func (NoneCipher) IVSize() int32 { return 0 } func (NoneCipher) IsAEAD() bool { return true // to avoid OTA } func (NoneCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { return buf.NewReader(reader), nil } func (NoneCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { return buf.NewWriter(writer), nil } func (NoneCipher) EncodePacket(key []byte, b *buf.Buffer) error { return nil } func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error { return nil } func passwordToCipherKey(password []byte, keySize int32) []byte { key := make([]byte, 0, keySize) md5Sum := md5.Sum(password) key = append(key, md5Sum[:]...) for int32(len(key)) < keySize { md5Hash := md5.New() common.Must2(md5Hash.Write(md5Sum[:])) common.Must2(md5Hash.Write(password)) md5Hash.Sum(md5Sum[:0]) key = append(key, md5Sum[:]...) } return key } func hkdfSHA1(secret, salt, outkey []byte) { r := hkdf.New(sha1.New, secret, salt, []byte("ss-subkey")) common.Must2(io.ReadFull(r, outkey)) } ================================================ FILE: proxy/shadowsocks/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/shadowsocks/config.proto package shadowsocks import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type CipherType int32 const ( CipherType_UNKNOWN CipherType = 0 CipherType_AES_128_CFB CipherType = 1 CipherType_AES_256_CFB CipherType = 2 CipherType_CHACHA20 CipherType = 3 CipherType_CHACHA20_IETF CipherType = 4 CipherType_AES_128_GCM CipherType = 5 CipherType_AES_256_GCM CipherType = 6 CipherType_CHACHA20_POLY1305 CipherType = 7 CipherType_NONE CipherType = 8 ) // Enum value maps for CipherType. var ( CipherType_name = map[int32]string{ 0: "UNKNOWN", 1: "AES_128_CFB", 2: "AES_256_CFB", 3: "CHACHA20", 4: "CHACHA20_IETF", 5: "AES_128_GCM", 6: "AES_256_GCM", 7: "CHACHA20_POLY1305", 8: "NONE", } CipherType_value = map[string]int32{ "UNKNOWN": 0, "AES_128_CFB": 1, "AES_256_CFB": 2, "CHACHA20": 3, "CHACHA20_IETF": 4, "AES_128_GCM": 5, "AES_256_GCM": 6, "CHACHA20_POLY1305": 7, "NONE": 8, } ) func (x CipherType) Enum() *CipherType { p := new(CipherType) *p = x return p } func (x CipherType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (CipherType) Descriptor() protoreflect.EnumDescriptor { return file_proxy_shadowsocks_config_proto_enumTypes[0].Descriptor() } func (CipherType) Type() protoreflect.EnumType { return &file_proxy_shadowsocks_config_proto_enumTypes[0] } func (x CipherType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use CipherType.Descriptor instead. func (CipherType) EnumDescriptor() ([]byte, []int) { return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{0} } type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` CipherType CipherType `protobuf:"varint,2,opt,name=cipher_type,json=cipherType,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"cipher_type,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_shadowsocks_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_shadowsocks_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{0} } func (x *Account) GetPassword() string { if x != nil { return x.Password } return "" } func (x *Account) GetCipherType() CipherType { if x != nil { return x.CipherType } return CipherType_UNKNOWN } type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // UdpEnabled specified whether or not to enable UDP for Shadowsocks. // Deprecated. Use 'network' field. // // Deprecated: Do not use. UdpEnabled bool `protobuf:"varint,1,opt,name=udp_enabled,json=udpEnabled,proto3" json:"udp_enabled,omitempty"` User *protocol.User `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` Network []net.Network `protobuf:"varint,3,rep,packed,name=network,proto3,enum=v2ray.core.common.net.Network" json:"network,omitempty"` } func (x *ServerConfig) Reset() { *x = ServerConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_shadowsocks_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerConfig) ProtoMessage() {} func (x *ServerConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_shadowsocks_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{1} } // Deprecated: Do not use. func (x *ServerConfig) GetUdpEnabled() bool { if x != nil { return x.UdpEnabled } return false } func (x *ServerConfig) GetUser() *protocol.User { if x != nil { return x.User } return nil } func (x *ServerConfig) GetNetwork() []net.Network { if x != nil { return x.Network } return nil } type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_shadowsocks_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ClientConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_shadowsocks_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{2} } func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { if x != nil { return x.Server } return nil } var File_proxy_shadowsocks_config_proto protoreflect.FileDescriptor var file_proxy_shadowsocks_config_proto_rawDesc = []byte{ 0x0a, 0x1e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x70, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x49, 0x0a, 0x0b, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x22, 0xa3, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0b, 0x75, 0x64, 0x70, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x52, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2a, 0x9f, 0x01, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x43, 0x46, 0x42, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x43, 0x46, 0x42, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x49, 0x45, 0x54, 0x46, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x47, 0x43, 0x4d, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x10, 0x07, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x08, 0x42, 0x65, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x50, 0x01, 0x5a, 0x20, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0xaa, 0x02, 0x1c, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_shadowsocks_config_proto_rawDescOnce sync.Once file_proxy_shadowsocks_config_proto_rawDescData = file_proxy_shadowsocks_config_proto_rawDesc ) func file_proxy_shadowsocks_config_proto_rawDescGZIP() []byte { file_proxy_shadowsocks_config_proto_rawDescOnce.Do(func() { file_proxy_shadowsocks_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_shadowsocks_config_proto_rawDescData) }) return file_proxy_shadowsocks_config_proto_rawDescData } var file_proxy_shadowsocks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_proxy_shadowsocks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_shadowsocks_config_proto_goTypes = []interface{}{ (CipherType)(0), // 0: v2ray.core.proxy.shadowsocks.CipherType (*Account)(nil), // 1: v2ray.core.proxy.shadowsocks.Account (*ServerConfig)(nil), // 2: v2ray.core.proxy.shadowsocks.ServerConfig (*ClientConfig)(nil), // 3: v2ray.core.proxy.shadowsocks.ClientConfig (*protocol.User)(nil), // 4: v2ray.core.common.protocol.User (net.Network)(0), // 5: v2ray.core.common.net.Network (*protocol.ServerEndpoint)(nil), // 6: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_shadowsocks_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.proxy.shadowsocks.Account.cipher_type:type_name -> v2ray.core.proxy.shadowsocks.CipherType 4, // 1: v2ray.core.proxy.shadowsocks.ServerConfig.user:type_name -> v2ray.core.common.protocol.User 5, // 2: v2ray.core.proxy.shadowsocks.ServerConfig.network:type_name -> v2ray.core.common.net.Network 6, // 3: v2ray.core.proxy.shadowsocks.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name 4, // [4:4] is the sub-list for extension extendee 0, // [0:4] is the sub-list for field type_name } func init() { file_proxy_shadowsocks_config_proto_init() } func file_proxy_shadowsocks_config_proto_init() { if File_proxy_shadowsocks_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_shadowsocks_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_shadowsocks_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_shadowsocks_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_shadowsocks_config_proto_rawDesc, NumEnums: 1, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_shadowsocks_config_proto_goTypes, DependencyIndexes: file_proxy_shadowsocks_config_proto_depIdxs, EnumInfos: file_proxy_shadowsocks_config_proto_enumTypes, MessageInfos: file_proxy_shadowsocks_config_proto_msgTypes, }.Build() File_proxy_shadowsocks_config_proto = out.File file_proxy_shadowsocks_config_proto_rawDesc = nil file_proxy_shadowsocks_config_proto_goTypes = nil file_proxy_shadowsocks_config_proto_depIdxs = nil } ================================================ FILE: proxy/shadowsocks/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.shadowsocks; option csharp_namespace = "V2Ray.Core.Proxy.Shadowsocks"; option go_package = "v2ray.com/core/proxy/shadowsocks"; option java_package = "com.v2ray.core.proxy.shadowsocks"; option java_multiple_files = true; import "common/net/network.proto"; import "common/protocol/user.proto"; import "common/protocol/server_spec.proto"; message Account { string password = 1; CipherType cipher_type = 2; } enum CipherType { UNKNOWN = 0; AES_128_CFB = 1; AES_256_CFB = 2; CHACHA20 = 3; CHACHA20_IETF = 4; AES_128_GCM = 5; AES_256_GCM = 6; CHACHA20_POLY1305 = 7; NONE = 8; } message ServerConfig { // UdpEnabled specified whether or not to enable UDP for Shadowsocks. // Deprecated. Use 'network' field. bool udp_enabled = 1 [deprecated = true]; v2ray.core.common.protocol.User user = 2; repeated v2ray.core.common.net.Network network = 3; } message ClientConfig { repeated v2ray.core.common.protocol.ServerEndpoint server = 1; } ================================================ FILE: proxy/shadowsocks/config_test.go ================================================ package shadowsocks_test import ( "crypto/rand" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/proxy/shadowsocks" ) func TestAEADCipherUDP(t *testing.T) { rawAccount := &shadowsocks.Account{ CipherType: shadowsocks.CipherType_AES_128_GCM, Password: "test", } account, err := rawAccount.AsAccount() common.Must(err) cipher := account.(*shadowsocks.MemoryAccount).Cipher key := make([]byte, cipher.KeySize()) common.Must2(rand.Read(key)) payload := make([]byte, 1024) common.Must2(rand.Read(payload)) b1 := buf.New() common.Must2(b1.ReadFullFrom(rand.Reader, cipher.IVSize())) common.Must2(b1.Write(payload)) common.Must(cipher.EncodePacket(key, b1)) common.Must(cipher.DecodePacket(key, b1)) if diff := cmp.Diff(b1.Bytes(), payload); diff != "" { t.Error(diff) } } ================================================ FILE: proxy/shadowsocks/errors.generated.go ================================================ package shadowsocks import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/shadowsocks/protocol.go ================================================ // +build !confonly package shadowsocks import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "hash" "hash/crc32" "io" "io/ioutil" "v2ray.com/core/common/dice" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) const ( Version = 1 ) var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), protocol.WithAddressTypeParser(func(b byte) byte { return b & 0x0F }), ) // ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts. func ReadTCPSession(user *protocol.MemoryUser, reader io.Reader) (*protocol.RequestHeader, buf.Reader, error) { account := user.Account.(*MemoryAccount) hashkdf := hmac.New(func() hash.Hash { return sha256.New() }, []byte("SSBSKDF")) hashkdf.Write(account.Key) behaviorSeed := crc32.ChecksumIEEE(hashkdf.Sum(nil)) behaviorRand := dice.NewDeterministicDice(int64(behaviorSeed)) BaseDrainSize := behaviorRand.Roll(3266) RandDrainMax := behaviorRand.Roll(64) + 1 RandDrainRolled := dice.Roll(RandDrainMax) DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled readSizeRemain := DrainSize buffer := buf.New() defer buffer.Release() ivLen := account.Cipher.IVSize() var iv []byte if ivLen > 0 { if _, err := buffer.ReadFullFrom(reader, ivLen); err != nil { readSizeRemain -= int(buffer.Len()) DrainConnN(reader, readSizeRemain) return nil, nil, newError("failed to read IV").Base(err) } iv = append([]byte(nil), buffer.BytesTo(ivLen)...) } r, err := account.Cipher.NewDecryptionReader(account.Key, iv, reader) if err != nil { readSizeRemain -= int(buffer.Len()) DrainConnN(reader, readSizeRemain) return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError() } br := &buf.BufferedReader{Reader: r} request := &protocol.RequestHeader{ Version: Version, User: user, Command: protocol.RequestCommandTCP, } readSizeRemain -= int(buffer.Len()) buffer.Clear() addr, port, err := addrParser.ReadAddressPort(buffer, br) if err != nil { readSizeRemain -= int(buffer.Len()) DrainConnN(reader, readSizeRemain) return nil, nil, newError("failed to read address").Base(err) } request.Address = addr request.Port = port if request.Address == nil { readSizeRemain -= int(buffer.Len()) DrainConnN(reader, readSizeRemain) return nil, nil, newError("invalid remote address.") } return request, br, nil } func DrainConnN(reader io.Reader, n int) error { _, err := io.CopyN(ioutil.Discard, reader, int64(n)) return err } // WriteTCPRequest writes Shadowsocks request into the given writer, and returns a writer for body. func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) { user := request.User account := user.Account.(*MemoryAccount) var iv []byte if account.Cipher.IVSize() > 0 { iv = make([]byte, account.Cipher.IVSize()) common.Must2(rand.Read(iv)) if err := buf.WriteAllBytes(writer, iv); err != nil { return nil, newError("failed to write IV") } } w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer) if err != nil { return nil, newError("failed to create encoding stream").Base(err).AtError() } header := buf.New() if err := addrParser.WriteAddressPort(header, request.Address, request.Port); err != nil { return nil, newError("failed to write address").Base(err) } if err := w.WriteMultiBuffer(buf.MultiBuffer{header}); err != nil { return nil, newError("failed to write header").Base(err) } return w, nil } func ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, error) { account := user.Account.(*MemoryAccount) var iv []byte if account.Cipher.IVSize() > 0 { iv = make([]byte, account.Cipher.IVSize()) if _, err := io.ReadFull(reader, iv); err != nil { return nil, newError("failed to read IV").Base(err) } } return account.Cipher.NewDecryptionReader(account.Key, iv, reader) } func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) { user := request.User account := user.Account.(*MemoryAccount) var iv []byte if account.Cipher.IVSize() > 0 { iv = make([]byte, account.Cipher.IVSize()) common.Must2(rand.Read(iv)) if err := buf.WriteAllBytes(writer, iv); err != nil { return nil, newError("failed to write IV.").Base(err) } } return account.Cipher.NewEncryptionWriter(account.Key, iv, writer) } func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buffer, error) { user := request.User account := user.Account.(*MemoryAccount) buffer := buf.New() ivLen := account.Cipher.IVSize() if ivLen > 0 { common.Must2(buffer.ReadFullFrom(rand.Reader, ivLen)) } if err := addrParser.WriteAddressPort(buffer, request.Address, request.Port); err != nil { return nil, newError("failed to write address").Base(err) } buffer.Write(payload) if err := account.Cipher.EncodePacket(account.Key, buffer); err != nil { return nil, newError("failed to encrypt UDP payload").Base(err) } return buffer, nil } func DecodeUDPPacket(user *protocol.MemoryUser, payload *buf.Buffer) (*protocol.RequestHeader, *buf.Buffer, error) { account := user.Account.(*MemoryAccount) var iv []byte if !account.Cipher.IsAEAD() && account.Cipher.IVSize() > 0 { // Keep track of IV as it gets removed from payload in DecodePacket. iv = make([]byte, account.Cipher.IVSize()) copy(iv, payload.BytesTo(account.Cipher.IVSize())) } if err := account.Cipher.DecodePacket(account.Key, payload); err != nil { return nil, nil, newError("failed to decrypt UDP payload").Base(err) } request := &protocol.RequestHeader{ Version: Version, User: user, Command: protocol.RequestCommandUDP, } payload.SetByte(0, payload.Byte(0)&0x0F) addr, port, err := addrParser.ReadAddressPort(nil, payload) if err != nil { return nil, nil, newError("failed to parse address").Base(err) } request.Address = addr request.Port = port return request, payload, nil } type UDPReader struct { Reader io.Reader User *protocol.MemoryUser } func (v *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) { buffer := buf.New() _, err := buffer.ReadFrom(v.Reader) if err != nil { buffer.Release() return nil, err } _, payload, err := DecodeUDPPacket(v.User, buffer) if err != nil { buffer.Release() return nil, err } return buf.MultiBuffer{payload}, nil } type UDPWriter struct { Writer io.Writer Request *protocol.RequestHeader } // Write implements io.Writer. func (w *UDPWriter) Write(payload []byte) (int, error) { packet, err := EncodeUDPPacket(w.Request, payload) if err != nil { return 0, err } _, err = w.Writer.Write(packet.Bytes()) packet.Release() return len(payload), err } ================================================ FILE: proxy/shadowsocks/protocol_test.go ================================================ package shadowsocks_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" . "v2ray.com/core/proxy/shadowsocks" ) func toAccount(a *Account) protocol.Account { account, err := a.AsAccount() common.Must(err) return account } func TestUDPEncoding(t *testing.T) { request := &protocol.RequestHeader{ Version: Version, Command: protocol.RequestCommandUDP, Address: net.LocalHostIP, Port: 1234, User: &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "shadowsocks-password", CipherType: CipherType_AES_128_CFB, }), }, } data := buf.New() common.Must2(data.WriteString("test string")) encodedData, err := EncodeUDPPacket(request, data.Bytes()) common.Must(err) decodedRequest, decodedData, err := DecodeUDPPacket(request.User, encodedData) common.Must(err) if r := cmp.Diff(decodedData.Bytes(), data.Bytes()); r != "" { t.Error("data: ", r) } if r := cmp.Diff(decodedRequest, request); r != "" { t.Error("request: ", r) } } func TestTCPRequest(t *testing.T) { cases := []struct { request *protocol.RequestHeader payload []byte }{ { request: &protocol.RequestHeader{ Version: Version, Command: protocol.RequestCommandTCP, Address: net.LocalHostIP, Port: 1234, User: &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "tcp-password", CipherType: CipherType_CHACHA20, }), }, }, payload: []byte("test string"), }, { request: &protocol.RequestHeader{ Version: Version, Command: protocol.RequestCommandTCP, Address: net.LocalHostIPv6, Port: 1234, User: &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "password", CipherType: CipherType_AES_256_CFB, }), }, }, payload: []byte("test string"), }, { request: &protocol.RequestHeader{ Version: Version, Command: protocol.RequestCommandTCP, Address: net.DomainAddress("v2ray.com"), Port: 1234, User: &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "password", CipherType: CipherType_CHACHA20_IETF, }), }, }, payload: []byte("test string"), }, } runTest := func(request *protocol.RequestHeader, payload []byte) { data := buf.New() common.Must2(data.Write(payload)) cache := buf.New() defer cache.Release() writer, err := WriteTCPRequest(request, cache) common.Must(err) common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data})) decodedRequest, reader, err := ReadTCPSession(request.User, cache) common.Must(err) if r := cmp.Diff(decodedRequest, request); r != "" { t.Error("request: ", r) } decodedData, err := reader.ReadMultiBuffer() common.Must(err) if r := cmp.Diff(decodedData[0].Bytes(), payload); r != "" { t.Error("data: ", r) } } for _, test := range cases { runTest(test.request, test.payload) } } func TestUDPReaderWriter(t *testing.T) { user := &protocol.MemoryUser{ Account: toAccount(&Account{ Password: "test-password", CipherType: CipherType_CHACHA20_IETF, }), } cache := buf.New() defer cache.Release() writer := &buf.SequentialWriter{Writer: &UDPWriter{ Writer: cache, Request: &protocol.RequestHeader{ Version: Version, Address: net.DomainAddress("v2ray.com"), Port: 123, User: user, }, }} reader := &UDPReader{ Reader: cache, User: user, } { b := buf.New() common.Must2(b.WriteString("test payload")) common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b})) payload, err := reader.ReadMultiBuffer() common.Must(err) if payload[0].String() != "test payload" { t.Error("unexpected output: ", payload[0].String()) } } { b := buf.New() common.Must2(b.WriteString("test payload 2")) common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b})) payload, err := reader.ReadMultiBuffer() common.Must(err) if payload[0].String() != "test payload 2" { t.Error("unexpected output: ", payload[0].String()) } } } ================================================ FILE: proxy/shadowsocks/server.go ================================================ // +build !confonly package shadowsocks import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" udp_proto "v2ray.com/core/common/protocol/udp" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/udp" ) type Server struct { config *ServerConfig user *protocol.MemoryUser policyManager policy.Manager } // NewServer create a new Shadowsocks server. func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { if config.GetUser() == nil { return nil, newError("user is not specified") } mUser, err := config.User.ToMemoryUser() if err != nil { return nil, newError("failed to parse user account").Base(err) } v := core.MustFromContext(ctx) s := &Server{ config: config, user: mUser, policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return s, nil } func (s *Server) Network() []net.Network { list := s.config.Network if len(list) == 0 { list = append(list, net.Network_TCP) } if s.config.UdpEnabled { list = append(list, net.Network_UDP) } return list } func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { switch network { case net.Network_TCP: return s.handleConnection(ctx, conn, dispatcher) case net.Network_UDP: return s.handlerUDPPayload(ctx, conn, dispatcher) default: return newError("unknown network: ", network) } } func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error { udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { request := protocol.RequestHeaderFromContext(ctx) if request == nil { return } payload := packet.Payload data, err := EncodeUDPPacket(request, payload.Bytes()) payload.Release() if err != nil { newError("failed to encode UDP packet").Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) return } defer data.Release() conn.Write(data.Bytes()) }) inbound := session.InboundFromContext(ctx) if inbound == nil { panic("no inbound metadata") } inbound.User = s.user reader := buf.NewPacketReader(conn) for { mpayload, err := reader.ReadMultiBuffer() if err != nil { break } for _, payload := range mpayload { request, data, err := DecodeUDPPacket(s.user, payload) if err != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() { newError("dropping invalid UDP packet from: ", inbound.Source).Base(err).WriteToLog(session.ExportIDToError(ctx)) log.Record(&log.AccessMessage{ From: inbound.Source, To: "", Status: log.AccessRejected, Reason: err, }) } payload.Release() continue } currentPacketCtx := ctx dest := request.Destination() if inbound.Source.IsValid() { currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: inbound.Source, To: dest, Status: log.AccessAccepted, Reason: "", Email: request.User.Email, }) } newError("tunnelling request to ", dest).WriteToLog(session.ExportIDToError(currentPacketCtx)) currentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request) udpServer.Dispatch(currentPacketCtx, dest, data) } } return nil } func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error { sessionPolicy := s.policyManager.ForLevel(s.user.Level) conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)) bufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)} request, bodyReader, err := ReadTCPSession(s.user, &bufferedReader) if err != nil { log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) return newError("failed to create request from: ", conn.RemoteAddr()).Base(err) } conn.SetReadDeadline(time.Time{}) inbound := session.InboundFromContext(ctx) if inbound == nil { panic("no inbound metadata") } inbound.User = s.user dest := request.Destination() ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: conn.RemoteAddr(), To: dest, Status: log.AccessAccepted, Reason: "", Email: request.User.Email, }) newError("tunnelling request to ", dest).WriteToLog(session.ExportIDToError(ctx)) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return err } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) responseWriter, err := WriteTCPResponse(request, bufferedWriter) if err != nil { return newError("failed to write response").Base(err) } { payload, err := link.Reader.ReadMultiBuffer() if err != nil { return err } if err := responseWriter.WriteMultiBuffer(payload); err != nil { return err } } if err := bufferedWriter.SetBuffered(false); err != nil { return err } if err := buf.Copy(link.Reader, responseWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP response").Base(err) } return nil } requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP request").Base(err) } return nil } var requestDoneAndCloseWriter = task.OnSuccess(requestDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDoneAndCloseWriter, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewServer(ctx, config.(*ServerConfig)) })) } ================================================ FILE: proxy/shadowsocks/shadowsocks.go ================================================ // Package shadowsocks provides compatible functionality to Shadowsocks. // // Shadowsocks client and server are implemented as outbound and inbound respectively in V2Ray's term. // // R.I.P Shadowsocks package shadowsocks //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: proxy/socks/client.go ================================================ // +build !confonly package socks import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // Client is a Socks5 client. type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager } // NewClient create a new Socks5 client based on the given config. func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { serverList := protocol.NewServerList() for _, rec := range config.Server { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to get server spec").Base(err) } serverList.AddServer(s) } if serverList.Size() == 0 { return nil, newError("0 target server") } v := core.MustFromContext(ctx) return &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), }, nil } // Process implements proxy.Outbound.Process. func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } destination := outbound.Target var server *protocol.ServerSpec var conn internet.Connection if err := retry.ExponentialBackoff(5, 100).On(func() error { server = c.serverPicker.PickServer() dest := server.Destination() rawConn, err := dialer.Dial(ctx, dest) if err != nil { return err } conn = rawConn return nil }); err != nil { return newError("failed to find an available destination").Base(err) } defer func() { if err := conn.Close(); err != nil { newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) } }() p := c.policyManager.ForLevel(0) request := &protocol.RequestHeader{ Version: socks5Version, Command: protocol.RequestCommandTCP, Address: destination.Address, Port: destination.Port, } if destination.Network == net.Network_UDP { request.Command = protocol.RequestCommandUDP } user := server.PickUser() if user != nil { request.User = user p = c.policyManager.ForLevel(user.Level) } if err := conn.SetDeadline(time.Now().Add(p.Timeouts.Handshake)); err != nil { newError("failed to set deadline for handshake").Base(err).WriteToLog(session.ExportIDToError(ctx)) } udpRequest, err := ClientHandshake(request, conn, conn) if err != nil { return newError("failed to establish connection to server").AtWarning().Base(err) } if err := conn.SetDeadline(time.Time{}); err != nil { newError("failed to clear deadline after handshake").Base(err).WriteToLog(session.ExportIDToError(ctx)) } ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) var requestFunc func() error var responseFunc func() error if request.Command == protocol.RequestCommandTCP { requestFunc = func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) } responseFunc = func() error { defer timer.SetTimeout(p.Timeouts.UplinkOnly) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } } else if request.Command == protocol.RequestCommandUDP { udpConn, err := dialer.Dial(ctx, udpRequest.Destination()) if err != nil { return newError("failed to create UDP connection").Base(err) } defer udpConn.Close() // nolint: errcheck requestFunc = func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) return buf.Copy(link.Reader, &buf.SequentialWriter{Writer: NewUDPWriter(request, udpConn)}, buf.UpdateActivity(timer)) } responseFunc = func() error { defer timer.SetTimeout(p.Timeouts.UplinkOnly) reader := &UDPReader{reader: udpConn} return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } } var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return newError("connection ends").Base(err) } return nil } func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewClient(ctx, config.(*ClientConfig)) })) } ================================================ FILE: proxy/socks/config.go ================================================ // +build !confonly package socks import "v2ray.com/core/common/protocol" func (a *Account) Equals(another protocol.Account) bool { if account, ok := another.(*Account); ok { return a.Username == account.Username } return false } func (a *Account) AsAccount() (protocol.Account, error) { return a, nil } func (c *ServerConfig) HasAccount(username, password string) bool { if c.Accounts == nil { return false } storedPassed, found := c.Accounts[username] if !found { return false } return storedPassed == password } ================================================ FILE: proxy/socks/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/socks/config.proto package socks import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" net "v2ray.com/core/common/net" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // AuthType is the authentication type of Socks proxy. type AuthType int32 const ( // NO_AUTH is for anounymous authentication. AuthType_NO_AUTH AuthType = 0 // PASSWORD is for username/password authentication. AuthType_PASSWORD AuthType = 1 ) // Enum value maps for AuthType. var ( AuthType_name = map[int32]string{ 0: "NO_AUTH", 1: "PASSWORD", } AuthType_value = map[string]int32{ "NO_AUTH": 0, "PASSWORD": 1, } ) func (x AuthType) Enum() *AuthType { p := new(AuthType) *p = x return p } func (x AuthType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AuthType) Descriptor() protoreflect.EnumDescriptor { return file_proxy_socks_config_proto_enumTypes[0].Descriptor() } func (AuthType) Type() protoreflect.EnumType { return &file_proxy_socks_config_proto_enumTypes[0] } func (x AuthType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AuthType.Descriptor instead. func (AuthType) EnumDescriptor() ([]byte, []int) { return file_proxy_socks_config_proto_rawDescGZIP(), []int{0} } // Account represents a Socks account. type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_socks_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_socks_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_socks_config_proto_rawDescGZIP(), []int{0} } func (x *Account) GetUsername() string { if x != nil { return x.Username } return "" } func (x *Account) GetPassword() string { if x != nil { return x.Password } return "" } // ServerConfig is the protobuf config for Socks server. type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AuthType AuthType `protobuf:"varint,1,opt,name=auth_type,json=authType,proto3,enum=v2ray.core.proxy.socks.AuthType" json:"auth_type,omitempty"` Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Address *net.IPOrDomain `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` UdpEnabled bool `protobuf:"varint,4,opt,name=udp_enabled,json=udpEnabled,proto3" json:"udp_enabled,omitempty"` // Deprecated: Do not use. Timeout uint32 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` } func (x *ServerConfig) Reset() { *x = ServerConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_socks_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerConfig) ProtoMessage() {} func (x *ServerConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_socks_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_socks_config_proto_rawDescGZIP(), []int{1} } func (x *ServerConfig) GetAuthType() AuthType { if x != nil { return x.AuthType } return AuthType_NO_AUTH } func (x *ServerConfig) GetAccounts() map[string]string { if x != nil { return x.Accounts } return nil } func (x *ServerConfig) GetAddress() *net.IPOrDomain { if x != nil { return x.Address } return nil } func (x *ServerConfig) GetUdpEnabled() bool { if x != nil { return x.UdpEnabled } return false } // Deprecated: Do not use. func (x *ServerConfig) GetTimeout() uint32 { if x != nil { return x.Timeout } return 0 } func (x *ServerConfig) GetUserLevel() uint32 { if x != nil { return x.UserLevel } return 0 } // ClientConfig is the protobuf config for Socks client. type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Sever is a list of Socks server addresses. Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_socks_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ClientConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_socks_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { return file_proxy_socks_config_proto_rawDescGZIP(), []int{2} } func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { if x != nil { return x.Server } return nil } var File_proxy_socks_config_proto protoreflect.FileDescriptor var file_proxy_socks_config_proto_rawDesc = []byte{ 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xf5, 0x02, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3d, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x64, 0x70, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x3b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x52, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2a, 0x25, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x01, 0x42, 0x53, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x50, 0x01, 0x5a, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0xaa, 0x02, 0x16, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_socks_config_proto_rawDescOnce sync.Once file_proxy_socks_config_proto_rawDescData = file_proxy_socks_config_proto_rawDesc ) func file_proxy_socks_config_proto_rawDescGZIP() []byte { file_proxy_socks_config_proto_rawDescOnce.Do(func() { file_proxy_socks_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_socks_config_proto_rawDescData) }) return file_proxy_socks_config_proto_rawDescData } var file_proxy_socks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_proxy_socks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proxy_socks_config_proto_goTypes = []interface{}{ (AuthType)(0), // 0: v2ray.core.proxy.socks.AuthType (*Account)(nil), // 1: v2ray.core.proxy.socks.Account (*ServerConfig)(nil), // 2: v2ray.core.proxy.socks.ServerConfig (*ClientConfig)(nil), // 3: v2ray.core.proxy.socks.ClientConfig nil, // 4: v2ray.core.proxy.socks.ServerConfig.AccountsEntry (*net.IPOrDomain)(nil), // 5: v2ray.core.common.net.IPOrDomain (*protocol.ServerEndpoint)(nil), // 6: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_socks_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.proxy.socks.ServerConfig.auth_type:type_name -> v2ray.core.proxy.socks.AuthType 4, // 1: v2ray.core.proxy.socks.ServerConfig.accounts:type_name -> v2ray.core.proxy.socks.ServerConfig.AccountsEntry 5, // 2: v2ray.core.proxy.socks.ServerConfig.address:type_name -> v2ray.core.common.net.IPOrDomain 6, // 3: v2ray.core.proxy.socks.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name 4, // [4:4] is the sub-list for extension extendee 0, // [0:4] is the sub-list for field type_name } func init() { file_proxy_socks_config_proto_init() } func file_proxy_socks_config_proto_init() { if File_proxy_socks_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_socks_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_socks_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_socks_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_socks_config_proto_rawDesc, NumEnums: 1, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_socks_config_proto_goTypes, DependencyIndexes: file_proxy_socks_config_proto_depIdxs, EnumInfos: file_proxy_socks_config_proto_enumTypes, MessageInfos: file_proxy_socks_config_proto_msgTypes, }.Build() File_proxy_socks_config_proto = out.File file_proxy_socks_config_proto_rawDesc = nil file_proxy_socks_config_proto_goTypes = nil file_proxy_socks_config_proto_depIdxs = nil } ================================================ FILE: proxy/socks/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.socks; option csharp_namespace = "V2Ray.Core.Proxy.Socks"; option go_package = "v2ray.com/core/proxy/socks"; option java_package = "com.v2ray.core.proxy.socks"; option java_multiple_files = true; import "common/net/address.proto"; import "common/protocol/server_spec.proto"; // Account represents a Socks account. message Account { string username = 1; string password = 2; } // AuthType is the authentication type of Socks proxy. enum AuthType { // NO_AUTH is for anounymous authentication. NO_AUTH = 0; // PASSWORD is for username/password authentication. PASSWORD = 1; } // ServerConfig is the protobuf config for Socks server. message ServerConfig { AuthType auth_type = 1; map accounts = 2; v2ray.core.common.net.IPOrDomain address = 3; bool udp_enabled = 4; uint32 timeout = 5 [deprecated = true]; uint32 user_level = 6; } // ClientConfig is the protobuf config for Socks client. message ClientConfig { // Sever is a list of Socks server addresses. repeated v2ray.core.common.protocol.ServerEndpoint server = 1; } ================================================ FILE: proxy/socks/errors.generated.go ================================================ package socks import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/socks/protocol.go ================================================ // +build !confonly package socks import ( "encoding/binary" "io" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) const ( socks5Version = 0x05 socks4Version = 0x04 cmdTCPConnect = 0x01 cmdTCPBind = 0x02 cmdUDPPort = 0x03 cmdTorResolve = 0xF0 cmdTorResolvePTR = 0xF1 socks4RequestGranted = 90 socks4RequestRejected = 91 authNotRequired = 0x00 //authGssAPI = 0x01 authPassword = 0x02 authNoMatchingMethod = 0xFF statusSuccess = 0x00 statusCmdNotSupport = 0x07 ) var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), ) type ServerSession struct { config *ServerConfig port net.Port } func (s *ServerSession) handshake4(cmd byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { if s.config.AuthType == AuthType_PASSWORD { writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0)) // nolint: errcheck return nil, newError("socks 4 is not allowed when auth is required.") } var port net.Port var address net.Address { buffer := buf.StackNew() if _, err := buffer.ReadFullFrom(reader, 6); err != nil { buffer.Release() return nil, newError("insufficient header").Base(err) } port = net.PortFromBytes(buffer.BytesRange(0, 2)) address = net.IPAddress(buffer.BytesRange(2, 6)) buffer.Release() } if _, err := ReadUntilNull(reader); /* user id */ err != nil { return nil, err } if address.IP()[0] == 0x00 { domain, err := ReadUntilNull(reader) if err != nil { return nil, newError("failed to read domain for socks 4a").Base(err) } address = net.DomainAddress(domain) } switch cmd { case cmdTCPConnect: request := &protocol.RequestHeader{ Command: protocol.RequestCommandTCP, Address: address, Port: port, Version: socks4Version, } if err := writeSocks4Response(writer, socks4RequestGranted, net.AnyIP, net.Port(0)); err != nil { return nil, err } return request, nil default: writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0)) // nolint: errcheck return nil, newError("unsupported command: ", cmd) } } func (s *ServerSession) auth5(nMethod byte, reader io.Reader, writer io.Writer) (username string, err error) { buffer := buf.StackNew() defer buffer.Release() if _, err = buffer.ReadFullFrom(reader, int32(nMethod)); err != nil { return "", newError("failed to read auth methods").Base(err) } var expectedAuth byte = authNotRequired if s.config.AuthType == AuthType_PASSWORD { expectedAuth = authPassword } if !hasAuthMethod(expectedAuth, buffer.BytesRange(0, int32(nMethod))) { writeSocks5AuthenticationResponse(writer, socks5Version, authNoMatchingMethod) // nolint: errcheck return "", newError("no matching auth method") } if err := writeSocks5AuthenticationResponse(writer, socks5Version, expectedAuth); err != nil { return "", newError("failed to write auth response").Base(err) } if expectedAuth == authPassword { username, password, err := ReadUsernamePassword(reader) if err != nil { return "", newError("failed to read username and password for authentication").Base(err) } if !s.config.HasAccount(username, password) { writeSocks5AuthenticationResponse(writer, 0x01, 0xFF) // nolint: errcheck return "", newError("invalid username or password") } if err := writeSocks5AuthenticationResponse(writer, 0x01, 0x00); err != nil { return "", newError("failed to write auth response").Base(err) } return username, nil } return "", nil } func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { var ( username string err error ) if username, err = s.auth5(nMethod, reader, writer); err != nil { return nil, err } var cmd byte { buffer := buf.StackNew() if _, err := buffer.ReadFullFrom(reader, 3); err != nil { buffer.Release() return nil, newError("failed to read request").Base(err) } cmd = buffer.Byte(1) buffer.Release() } request := new(protocol.RequestHeader) if username != "" { request.User = &protocol.MemoryUser{Email: username} } switch cmd { case cmdTCPConnect, cmdTorResolve, cmdTorResolvePTR: // We don't have a solution for Tor case now. Simply treat it as connect command. request.Command = protocol.RequestCommandTCP case cmdUDPPort: if !s.config.UdpEnabled { writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) // nolint: errcheck return nil, newError("UDP is not enabled.") } request.Command = protocol.RequestCommandUDP case cmdTCPBind: writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) // nolint: errcheck return nil, newError("TCP bind is not supported.") default: writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) // nolint: errcheck return nil, newError("unknown command ", cmd) } request.Version = socks5Version addr, port, err := addrParser.ReadAddressPort(nil, reader) if err != nil { return nil, newError("failed to read address").Base(err) } request.Address = addr request.Port = port responseAddress := net.AnyIP responsePort := net.Port(1717) if request.Command == protocol.RequestCommandUDP { addr := s.config.Address.AsAddress() if addr == nil { addr = net.LocalHostIP } responseAddress = addr responsePort = s.port } if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil { return nil, err } return request, nil } // Handshake performs a Socks4/4a/5 handshake. func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { buffer := buf.StackNew() if _, err := buffer.ReadFullFrom(reader, 2); err != nil { buffer.Release() return nil, newError("insufficient header").Base(err) } version := buffer.Byte(0) cmd := buffer.Byte(1) buffer.Release() switch version { case socks4Version: return s.handshake4(cmd, reader, writer) case socks5Version: return s.handshake5(cmd, reader, writer) default: return nil, newError("unknown Socks version: ", version) } } // ReadUsernamePassword reads Socks 5 username/password message from the given reader. // +----+------+----------+------+----------+ // |VER | ULEN | UNAME | PLEN | PASSWD | // +----+------+----------+------+----------+ // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | // +----+------+----------+------+----------+ func ReadUsernamePassword(reader io.Reader) (string, string, error) { buffer := buf.StackNew() defer buffer.Release() if _, err := buffer.ReadFullFrom(reader, 2); err != nil { return "", "", err } nUsername := int32(buffer.Byte(1)) buffer.Clear() if _, err := buffer.ReadFullFrom(reader, nUsername); err != nil { return "", "", err } username := buffer.String() buffer.Clear() if _, err := buffer.ReadFullFrom(reader, 1); err != nil { return "", "", err } nPassword := int32(buffer.Byte(0)) buffer.Clear() if _, err := buffer.ReadFullFrom(reader, nPassword); err != nil { return "", "", err } password := buffer.String() return username, password, nil } // ReadUntilNull reads content from given reader, until a null (0x00) byte. func ReadUntilNull(reader io.Reader) (string, error) { b := buf.StackNew() defer b.Release() for { _, err := b.ReadFullFrom(reader, 1) if err != nil { return "", err } if b.Byte(b.Len()-1) == 0x00 { b.Resize(0, b.Len()-1) return b.String(), nil } if b.IsFull() { return "", newError("buffer overrun") } } } func hasAuthMethod(expectedAuth byte, authCandidates []byte) bool { for _, a := range authCandidates { if a == expectedAuth { return true } } return false } func writeSocks5AuthenticationResponse(writer io.Writer, version byte, auth byte) error { return buf.WriteAllBytes(writer, []byte{version, auth}) } func writeSocks5Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error { buffer := buf.New() defer buffer.Release() common.Must2(buffer.Write([]byte{socks5Version, errCode, 0x00 /* reserved */})) if err := addrParser.WriteAddressPort(buffer, address, port); err != nil { return err } return buf.WriteAllBytes(writer, buffer.Bytes()) } func writeSocks4Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error { buffer := buf.StackNew() defer buffer.Release() common.Must(buffer.WriteByte(0x00)) common.Must(buffer.WriteByte(errCode)) portBytes := buffer.Extend(2) binary.BigEndian.PutUint16(portBytes, port.Value()) common.Must2(buffer.Write(address.IP())) return buf.WriteAllBytes(writer, buffer.Bytes()) } func DecodeUDPPacket(packet *buf.Buffer) (*protocol.RequestHeader, error) { if packet.Len() < 5 { return nil, newError("insufficient length of packet.") } request := &protocol.RequestHeader{ Version: socks5Version, Command: protocol.RequestCommandUDP, } // packet[0] and packet[1] are reserved if packet.Byte(2) != 0 /* fragments */ { return nil, newError("discarding fragmented payload.") } packet.Advance(3) addr, port, err := addrParser.ReadAddressPort(nil, packet) if err != nil { return nil, newError("failed to read UDP header").Base(err) } request.Address = addr request.Port = port return request, nil } func EncodeUDPPacket(request *protocol.RequestHeader, data []byte) (*buf.Buffer, error) { b := buf.New() common.Must2(b.Write([]byte{0, 0, 0 /* Fragment */})) if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil { b.Release() return nil, err } common.Must2(b.Write(data)) return b, nil } type UDPReader struct { reader io.Reader } func NewUDPReader(reader io.Reader) *UDPReader { return &UDPReader{reader: reader} } func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) { b := buf.New() if _, err := b.ReadFrom(r.reader); err != nil { return nil, err } if _, err := DecodeUDPPacket(b); err != nil { return nil, err } return buf.MultiBuffer{b}, nil } type UDPWriter struct { request *protocol.RequestHeader writer io.Writer } func NewUDPWriter(request *protocol.RequestHeader, writer io.Writer) *UDPWriter { return &UDPWriter{ request: request, writer: writer, } } // Write implements io.Writer. func (w *UDPWriter) Write(b []byte) (int, error) { eb, err := EncodeUDPPacket(w.request, b) if err != nil { return 0, err } defer eb.Release() if _, err := w.writer.Write(eb.Bytes()); err != nil { return 0, err } return len(b), nil } func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { authByte := byte(authNotRequired) if request.User != nil { authByte = byte(authPassword) } b := buf.New() defer b.Release() common.Must2(b.Write([]byte{socks5Version, 0x01, authByte})) if authByte == authPassword { account := request.User.Account.(*Account) common.Must(b.WriteByte(0x01)) common.Must(b.WriteByte(byte(len(account.Username)))) common.Must2(b.WriteString(account.Username)) common.Must(b.WriteByte(byte(len(account.Password)))) common.Must2(b.WriteString(account.Password)) } if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil { return nil, err } b.Clear() if _, err := b.ReadFullFrom(reader, 2); err != nil { return nil, err } if b.Byte(0) != socks5Version { return nil, newError("unexpected server version: ", b.Byte(0)).AtWarning() } if b.Byte(1) != authByte { return nil, newError("auth method not supported.").AtWarning() } if authByte == authPassword { b.Clear() if _, err := b.ReadFullFrom(reader, 2); err != nil { return nil, err } if b.Byte(1) != 0x00 { return nil, newError("server rejects account: ", b.Byte(1)) } } b.Clear() command := byte(cmdTCPConnect) if request.Command == protocol.RequestCommandUDP { command = byte(cmdUDPPort) } common.Must2(b.Write([]byte{socks5Version, command, 0x00 /* reserved */})) if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil { return nil, err } if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil { return nil, err } b.Clear() if _, err := b.ReadFullFrom(reader, 3); err != nil { return nil, err } resp := b.Byte(1) if resp != 0x00 { return nil, newError("server rejects request: ", resp) } b.Clear() address, port, err := addrParser.ReadAddressPort(b, reader) if err != nil { return nil, err } if request.Command == protocol.RequestCommandUDP { udpRequest := &protocol.RequestHeader{ Version: socks5Version, Command: protocol.RequestCommandUDP, Address: address, Port: port, } return udpRequest, nil } return nil, nil } ================================================ FILE: proxy/socks/protocol_test.go ================================================ package socks_test import ( "bytes" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" . "v2ray.com/core/proxy/socks" ) func TestUDPEncoding(t *testing.T) { b := buf.New() request := &protocol.RequestHeader{ Address: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), Port: 1024, } writer := &buf.SequentialWriter{Writer: NewUDPWriter(request, b)} content := []byte{'a'} payload := buf.New() payload.Write(content) common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{payload})) reader := NewUDPReader(b) decodedPayload, err := reader.ReadMultiBuffer() common.Must(err) if r := cmp.Diff(decodedPayload[0].Bytes(), content); r != "" { t.Error(r) } } func TestReadUsernamePassword(t *testing.T) { testCases := []struct { Input []byte Username string Password string Error bool }{ { Input: []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'}, Username: "a", Password: "bc", }, { Input: []byte{0x05, 0x18, 'a', 0x02, 'b', 'c'}, Error: true, }, } for _, testCase := range testCases { reader := bytes.NewReader(testCase.Input) username, password, err := ReadUsernamePassword(reader) if testCase.Error { if err == nil { t.Error("for input: ", testCase.Input, " expect error, but actually nil") } } else { if err != nil { t.Error("for input: ", testCase.Input, " expect no error, but actually ", err.Error()) } if testCase.Username != username { t.Error("for input: ", testCase.Input, " expect username ", testCase.Username, " but actually ", username) } if testCase.Password != password { t.Error("for input: ", testCase.Input, " expect passowrd ", testCase.Password, " but actually ", password) } } } } func TestReadUntilNull(t *testing.T) { testCases := []struct { Input []byte Output string Error bool }{ { Input: []byte{'a', 'b', 0x00}, Output: "ab", }, { Input: []byte{'a'}, Error: true, }, } for _, testCase := range testCases { reader := bytes.NewReader(testCase.Input) value, err := ReadUntilNull(reader) if testCase.Error { if err == nil { t.Error("for input: ", testCase.Input, " expect error, but actually nil") } } else { if err != nil { t.Error("for input: ", testCase.Input, " expect no error, but actually ", err.Error()) } if testCase.Output != value { t.Error("for input: ", testCase.Input, " expect output ", testCase.Output, " but actually ", value) } } } } func BenchmarkReadUsernamePassword(b *testing.B) { input := []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'} buffer := buf.New() buffer.Write(input) b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := ReadUsernamePassword(buffer) common.Must(err) buffer.Clear() buffer.Extend(int32(len(input))) } } ================================================ FILE: proxy/socks/server.go ================================================ // +build !confonly package socks import ( "context" "io" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" udp_proto "v2ray.com/core/common/protocol/udp" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/udp" ) // Server is a SOCKS 5 proxy server type Server struct { config *ServerConfig policyManager policy.Manager } // NewServer creates a new Server object. func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { v := core.MustFromContext(ctx) s := &Server{ config: config, policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return s, nil } func (s *Server) policy() policy.Session { config := s.config p := s.policyManager.ForLevel(config.UserLevel) if config.Timeout > 0 { features.PrintDeprecatedFeatureWarning("Socks timeout") } if config.Timeout > 0 && config.UserLevel == 0 { p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second } return p } // Network implements proxy.Inbound. func (s *Server) Network() []net.Network { list := []net.Network{net.Network_TCP} if s.config.UdpEnabled { list = append(list, net.Network_UDP) } return list } // Process implements proxy.Inbound. func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { if inbound := session.InboundFromContext(ctx); inbound != nil { inbound.User = &protocol.MemoryUser{ Level: s.config.UserLevel, } } switch network { case net.Network_TCP: return s.processTCP(ctx, conn, dispatcher) case net.Network_UDP: return s.handleUDPPayload(ctx, conn, dispatcher) default: return newError("unknown network: ", network) } } func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error { plcy := s.policy() if err := conn.SetReadDeadline(time.Now().Add(plcy.Timeouts.Handshake)); err != nil { newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } inbound := session.InboundFromContext(ctx) if inbound == nil || !inbound.Gateway.IsValid() { return newError("inbound gateway not specified") } svrSession := &ServerSession{ config: s.config, port: inbound.Gateway.Port, } reader := &buf.BufferedReader{Reader: buf.NewReader(conn)} request, err := svrSession.Handshake(reader, conn) if err != nil { if inbound != nil && inbound.Source.IsValid() { log.Record(&log.AccessMessage{ From: inbound.Source, To: "", Status: log.AccessRejected, Reason: err, }) } return newError("failed to read request").Base(err) } if request.User != nil { inbound.User.Email = request.User.Email } if err := conn.SetReadDeadline(time.Time{}); err != nil { newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } if request.Command == protocol.RequestCommandTCP { dest := request.Destination() newError("TCP Connect request to ", dest).WriteToLog(session.ExportIDToError(ctx)) if inbound != nil && inbound.Source.IsValid() { ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: inbound.Source, To: dest, Status: log.AccessAccepted, Reason: "", }) } return s.transport(ctx, reader, conn, dest, dispatcher) } if request.Command == protocol.RequestCommandUDP { return s.handleUDP(conn) } return nil } func (*Server) handleUDP(c io.Reader) error { // The TCP connection closes after this method returns. We need to wait until // the client closes it. return common.Error2(io.Copy(buf.DiscardBytes, c)) } func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error { ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, s.policy().Timeouts.ConnectionIdle) plcy := s.policy() ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer) link, err := dispatcher.Dispatch(ctx, dest) if err != nil { return err } requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) if err := buf.Copy(buf.NewReader(reader), link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP request").Base(err) } return nil } responseDone := func() error { defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(writer) if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP response").Base(err) } return nil } var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDonePost, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } func (s *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error { udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { payload := packet.Payload newError("writing back UDP response with ", payload.Len(), " bytes").AtDebug().WriteToLog(session.ExportIDToError(ctx)) request := protocol.RequestHeaderFromContext(ctx) if request == nil { return } udpMessage, err := EncodeUDPPacket(request, payload.Bytes()) payload.Release() defer udpMessage.Release() if err != nil { newError("failed to write UDP response").AtWarning().Base(err).WriteToLog(session.ExportIDToError(ctx)) } conn.Write(udpMessage.Bytes()) // nolint: errcheck }) if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() { newError("client UDP connection from ", inbound.Source).WriteToLog(session.ExportIDToError(ctx)) } reader := buf.NewPacketReader(conn) for { mpayload, err := reader.ReadMultiBuffer() if err != nil { return err } for _, payload := range mpayload { request, err := DecodeUDPPacket(payload) if err != nil { newError("failed to parse UDP request").Base(err).WriteToLog(session.ExportIDToError(ctx)) payload.Release() continue } if payload.IsEmpty() { payload.Release() continue } currentPacketCtx := ctx newError("send packet to ", request.Destination(), " with ", payload.Len(), " bytes").AtDebug().WriteToLog(session.ExportIDToError(ctx)) if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() { currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: inbound.Source, To: request.Destination(), Status: log.AccessAccepted, Reason: "", }) } currentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request) udpServer.Dispatch(currentPacketCtx, request.Destination(), payload) } } } func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewServer(ctx, config.(*ServerConfig)) })) } ================================================ FILE: proxy/socks/socks.go ================================================ // Package socks provides implements of Socks protocol 4, 4a and 5. package socks //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: proxy/trojan/client.go ================================================ // +build !confonly package trojan import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // Client is a inbound handler for trojan protocol type Client struct { serverPicker protocol.ServerPicker policyManager policy.Manager } // NewClient create a new trojan client. func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { serverList := protocol.NewServerList() for _, rec := range config.Server { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to parse server spec").Base(err) } serverList.AddServer(s) } if serverList.Size() == 0 { return nil, newError("0 server") } v := core.MustFromContext(ctx) client := &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return client, nil } // Process implements OutboundHandler.Process(). func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { // nolint: funlen outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified") } destination := outbound.Target network := destination.Network var server *protocol.ServerSpec var conn internet.Connection err := retry.ExponentialBackoff(5, 100).On(func() error { // nolint: gomnd server = c.serverPicker.PickServer() rawConn, err := dialer.Dial(ctx, server.Destination()) if err != nil { return err } conn = rawConn return nil }) if err != nil { return newError("failed to find an available destination").AtWarning().Base(err) } newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog(session.ExportIDToError(ctx)) defer conn.Close() user := server.PickUser() account, ok := user.Account.(*MemoryAccount) if !ok { return newError("user account is not valid") } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) var bodyWriter buf.Writer bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) connWriter := &ConnWriter{Writer: bufferWriter, Target: destination, Account: account} if destination.Network == net.Network_UDP { bodyWriter = &PacketWriter{Writer: connWriter, Target: destination} } else { bodyWriter = connWriter } // write some request payload to buffer if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { // nolint: lll,gomnd return newError("failed to write A reqeust payload").Base(err).AtWarning() } // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer if err = bufferWriter.SetBuffered(false); err != nil { return newError("failed to flush payload").Base(err).AtWarning() } if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request payload").Base(err).AtInfo() } return nil } getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) var reader buf.Reader if network == net.Network_UDP { reader = &PacketReader{ Reader: conn, } } else { reader = buf.NewReader(conn) } return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } var responseDoneAndCloseWriter = task.OnSuccess(getResponse, task.Close(link.Writer)) if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil { return newError("connection ends").Base(err) } return nil } func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { // nolint: lll return NewClient(ctx, config.(*ClientConfig)) })) } ================================================ FILE: proxy/trojan/config.go ================================================ package trojan import ( "crypto/sha256" "encoding/hex" fmt "fmt" "v2ray.com/core/common" "v2ray.com/core/common/protocol" ) // MemoryAccount is an account type converted from Account. type MemoryAccount struct { Password string Key []byte } // AsAccount implements protocol.AsAccount. func (a *Account) AsAccount() (protocol.Account, error) { password := a.GetPassword() key := hexSha224(password) return &MemoryAccount{ Password: password, Key: key, }, nil } // Equals implements protocol.Account.Equals(). func (a *MemoryAccount) Equals(another protocol.Account) bool { if account, ok := another.(*MemoryAccount); ok { return a.Password == account.Password } return false } func hexSha224(password string) []byte { buf := make([]byte, 56) hash := sha256.New224() common.Must2(hash.Write([]byte(password))) hex.Encode(buf, hash.Sum(nil)) return buf } func hexString(data []byte) string { str := "" for _, v := range data { str += fmt.Sprintf("%02x", v) } return str } ================================================ FILE: proxy/trojan/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/trojan/config.proto package trojan import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_trojan_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_trojan_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_trojan_config_proto_rawDescGZIP(), []int{0} } func (x *Account) GetPassword() string { if x != nil { return x.Password } return "" } type Fallback struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Alpn string `protobuf:"bytes,1,opt,name=alpn,proto3" json:"alpn,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` Dest string `protobuf:"bytes,4,opt,name=dest,proto3" json:"dest,omitempty"` Xver uint64 `protobuf:"varint,5,opt,name=xver,proto3" json:"xver,omitempty"` } func (x *Fallback) Reset() { *x = Fallback{} if protoimpl.UnsafeEnabled { mi := &file_proxy_trojan_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Fallback) String() string { return protoimpl.X.MessageStringOf(x) } func (*Fallback) ProtoMessage() {} func (x *Fallback) ProtoReflect() protoreflect.Message { mi := &file_proxy_trojan_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Fallback.ProtoReflect.Descriptor instead. func (*Fallback) Descriptor() ([]byte, []int) { return file_proxy_trojan_config_proto_rawDescGZIP(), []int{1} } func (x *Fallback) GetAlpn() string { if x != nil { return x.Alpn } return "" } func (x *Fallback) GetPath() string { if x != nil { return x.Path } return "" } func (x *Fallback) GetType() string { if x != nil { return x.Type } return "" } func (x *Fallback) GetDest() string { if x != nil { return x.Dest } return "" } func (x *Fallback) GetXver() uint64 { if x != nil { return x.Xver } return 0 } type ClientConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"` } func (x *ClientConfig) Reset() { *x = ClientConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_trojan_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ClientConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_trojan_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { return file_proxy_trojan_config_proto_rawDescGZIP(), []int{2} } func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint { if x != nil { return x.Server } return nil } type ServerConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` } func (x *ServerConfig) Reset() { *x = ServerConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_trojan_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ServerConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ServerConfig) ProtoMessage() {} func (x *ServerConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_trojan_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_trojan_config_proto_rawDescGZIP(), []int{3} } func (x *ServerConfig) GetUsers() []*protocol.User { if x != nil { return x.Users } return nil } func (x *ServerConfig) GetFallbacks() []*Fallback { if x != nil { return x.Fallbacks } return nil } var File_proxy_trojan_config_proto protoreflect.FileDescriptor var file_proxy_trojan_config_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x6e, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x52, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x87, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x3f, 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x42, 0x56, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x1b, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x17, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_trojan_config_proto_rawDescOnce sync.Once file_proxy_trojan_config_proto_rawDescData = file_proxy_trojan_config_proto_rawDesc ) func file_proxy_trojan_config_proto_rawDescGZIP() []byte { file_proxy_trojan_config_proto_rawDescOnce.Do(func() { file_proxy_trojan_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_trojan_config_proto_rawDescData) }) return file_proxy_trojan_config_proto_rawDescData } var file_proxy_trojan_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proxy_trojan_config_proto_goTypes = []interface{}{ (*Account)(nil), // 0: v2ray.core.proxy.trojan.Account (*Fallback)(nil), // 1: v2ray.core.proxy.trojan.Fallback (*ClientConfig)(nil), // 2: v2ray.core.proxy.trojan.ClientConfig (*ServerConfig)(nil), // 3: v2ray.core.proxy.trojan.ServerConfig (*protocol.ServerEndpoint)(nil), // 4: v2ray.core.common.protocol.ServerEndpoint (*protocol.User)(nil), // 5: v2ray.core.common.protocol.User } var file_proxy_trojan_config_proto_depIdxs = []int32{ 4, // 0: v2ray.core.proxy.trojan.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint 5, // 1: v2ray.core.proxy.trojan.ServerConfig.users:type_name -> v2ray.core.common.protocol.User 1, // 2: v2ray.core.proxy.trojan.ServerConfig.fallbacks:type_name -> v2ray.core.proxy.trojan.Fallback 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_proxy_trojan_config_proto_init() } func file_proxy_trojan_config_proto_init() { if File_proxy_trojan_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_trojan_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_trojan_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Fallback); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_trojan_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClientConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_trojan_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_trojan_config_proto_rawDesc, NumEnums: 0, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_trojan_config_proto_goTypes, DependencyIndexes: file_proxy_trojan_config_proto_depIdxs, MessageInfos: file_proxy_trojan_config_proto_msgTypes, }.Build() File_proxy_trojan_config_proto = out.File file_proxy_trojan_config_proto_rawDesc = nil file_proxy_trojan_config_proto_goTypes = nil file_proxy_trojan_config_proto_depIdxs = nil } ================================================ FILE: proxy/trojan/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.trojan; option csharp_namespace = "V2Ray.Core.Proxy.Trojan"; option go_package = "v2ray.com/core/proxy/trojan"; option java_package = "com.v2ray.core.proxy.trojan"; option java_multiple_files = true; import "common/protocol/user.proto"; import "common/protocol/server_spec.proto"; message Account { string password = 1; } message Fallback { string alpn = 1; string path = 2; string type = 3; string dest = 4; uint64 xver = 5; } message ClientConfig { repeated v2ray.core.common.protocol.ServerEndpoint server = 1; } message ServerConfig { repeated v2ray.core.common.protocol.User users = 1; repeated Fallback fallbacks = 3; } ================================================ FILE: proxy/trojan/errors.generated.go ================================================ package trojan import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/trojan/protocol.go ================================================ package trojan import ( "encoding/binary" "io" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) var ( crlf = []byte{'\r', '\n'} addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), // nolint: gomnd protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), // nolint: gomnd protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), // nolint: gomnd ) ) const ( maxLength = 8192 commandTCP byte = 1 commandUDP byte = 3 ) // ConnWriter is TCP Connection Writer Wrapper for trojan protocol type ConnWriter struct { io.Writer Target net.Destination Account *MemoryAccount headerSent bool } // Write implements io.Writer func (c *ConnWriter) Write(p []byte) (n int, err error) { if !c.headerSent { if err := c.writeHeader(); err != nil { return 0, newError("failed to write request header").Base(err) } } return c.Writer.Write(p) } // WriteMultiBuffer implements buf.Writer func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { defer buf.ReleaseMulti(mb) for _, b := range mb { if !b.IsEmpty() { if _, err := c.Write(b.Bytes()); err != nil { return err } } } return nil } func (c *ConnWriter) writeHeader() error { buffer := buf.StackNew() defer buffer.Release() command := commandTCP if c.Target.Network == net.Network_UDP { command = commandUDP } if _, err := buffer.Write(c.Account.Key); err != nil { return err } if _, err := buffer.Write(crlf); err != nil { return err } if err := buffer.WriteByte(command); err != nil { return err } if err := addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port); err != nil { return err } if _, err := buffer.Write(crlf); err != nil { return err } _, err := c.Writer.Write(buffer.Bytes()) if err == nil { c.headerSent = true } return err } // PacketWriter UDP Connection Writer Wrapper for trojan protocol type PacketWriter struct { io.Writer Target net.Destination } // WriteMultiBuffer implements buf.Writer func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { b := make([]byte, maxLength) for !mb.IsEmpty() { var length int mb, length = buf.SplitBytes(mb, b) if _, err := w.writePacket(b[:length], w.Target); err != nil { buf.ReleaseMulti(mb) return err } } return nil } // WriteMultiBufferWithMetadata writes udp packet with destination specified func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error { b := make([]byte, maxLength) for !mb.IsEmpty() { var length int mb, length = buf.SplitBytes(mb, b) if _, err := w.writePacket(b[:length], dest); err != nil { buf.ReleaseMulti(mb) return err } } return nil } func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) { // nolint: unparam buffer := buf.StackNew() defer buffer.Release() length := len(payload) lengthBuf := [2]byte{} binary.BigEndian.PutUint16(lengthBuf[:], uint16(length)) if err := addrParser.WriteAddressPort(&buffer, dest.Address, dest.Port); err != nil { return 0, err } if _, err := buffer.Write(lengthBuf[:]); err != nil { return 0, err } if _, err := buffer.Write(crlf); err != nil { return 0, err } if _, err := buffer.Write(payload); err != nil { return 0, err } _, err := w.Write(buffer.Bytes()) if err != nil { return 0, err } return length, nil } // ConnReader is TCP Connection Reader Wrapper for trojan protocol type ConnReader struct { io.Reader Target net.Destination headerParsed bool } // ParseHeader parses the trojan protocol header func (c *ConnReader) ParseHeader() error { var crlf [2]byte var command [1]byte var hash [56]byte if _, err := io.ReadFull(c.Reader, hash[:]); err != nil { return newError("failed to read user hash").Base(err) } if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil { return newError("failed to read crlf").Base(err) } if _, err := io.ReadFull(c.Reader, command[:]); err != nil { return newError("failed to read command").Base(err) } network := net.Network_TCP if command[0] == commandUDP { network = net.Network_UDP } addr, port, err := addrParser.ReadAddressPort(nil, c.Reader) if err != nil { return newError("failed to read address and port").Base(err) } c.Target = net.Destination{Network: network, Address: addr, Port: port} if _, err := io.ReadFull(c.Reader, crlf[:]); err != nil { return newError("failed to read crlf").Base(err) } c.headerParsed = true return nil } // Read implements io.Reader func (c *ConnReader) Read(p []byte) (int, error) { if !c.headerParsed { if err := c.ParseHeader(); err != nil { return 0, err } } return c.Reader.Read(p) } // ReadMultiBuffer implements buf.Reader func (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) { b := buf.New() _, err := b.ReadFrom(c) return buf.MultiBuffer{b}, err } // PacketPayload combines udp payload and destination type PacketPayload struct { Target net.Destination Buffer buf.MultiBuffer } // PacketReader is UDP Connection Reader Wrapper for trojan protocol type PacketReader struct { io.Reader } // ReadMultiBuffer implements buf.Reader func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { p, err := r.ReadMultiBufferWithMetadata() if p != nil { return p.Buffer, err } return nil, err } // ReadMultiBufferWithMetadata reads udp packet with destination func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) { addr, port, err := addrParser.ReadAddressPort(nil, r) if err != nil { return nil, newError("failed to read address and port").Base(err) } var lengthBuf [2]byte if _, err := io.ReadFull(r, lengthBuf[:]); err != nil { return nil, newError("failed to read payload length").Base(err) } remain := int(binary.BigEndian.Uint16(lengthBuf[:])) if remain > maxLength { return nil, newError("oversize payload") } var crlf [2]byte if _, err := io.ReadFull(r, crlf[:]); err != nil { return nil, newError("failed to read crlf").Base(err) } dest := net.UDPDestination(addr, port) var mb buf.MultiBuffer for remain > 0 { length := buf.Size if remain < length { length = remain } b := buf.New() mb = append(mb, b) n, err := b.ReadFullFrom(r, int32(length)) if err != nil { buf.ReleaseMulti(mb) return nil, newError("failed to read payload").Base(err) } remain -= int(n) } return &PacketPayload{Target: dest, Buffer: mb}, nil } ================================================ FILE: proxy/trojan/protocol_test.go ================================================ package trojan_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" . "v2ray.com/core/proxy/trojan" ) func toAccount(a *Account) protocol.Account { account, err := a.AsAccount() common.Must(err) return account } func TestTCPRequest(t *testing.T) { user := &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "password", }), } payload := []byte("test string") data := buf.New() common.Must2(data.Write(payload)) buffer := buf.New() defer buffer.Release() destination := net.Destination{Network: net.Network_TCP, Address: net.LocalHostIP, Port: 1234} writer := &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)} common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data})) reader := &ConnReader{Reader: buffer} common.Must(reader.ParseHeader()) if r := cmp.Diff(reader.Target, destination); r != "" { t.Error("destination: ", r) } decodedData, err := reader.ReadMultiBuffer() common.Must(err) if r := cmp.Diff(decodedData[0].Bytes(), payload); r != "" { t.Error("data: ", r) } } func TestUDPRequest(t *testing.T) { user := &protocol.MemoryUser{ Email: "love@v2ray.com", Account: toAccount(&Account{ Password: "password", }), } payload := []byte("test string") data := buf.New() common.Must2(data.Write(payload)) buffer := buf.New() defer buffer.Release() destination := net.Destination{Network: net.Network_UDP, Address: net.LocalHostIP, Port: 1234} writer := &PacketWriter{Writer: &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}, Target: destination} common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data})) connReader := &ConnReader{Reader: buffer} common.Must(connReader.ParseHeader()) packetReader := &PacketReader{Reader: connReader} p, err := packetReader.ReadMultiBufferWithMetadata() common.Must(err) if p.Buffer.IsEmpty() { t.Error("no request data") } if r := cmp.Diff(p.Target, destination); r != "" { t.Error("destination: ", r) } mb, decoded := buf.SplitFirst(p.Buffer) buf.ReleaseMulti(mb) if r := cmp.Diff(decoded.Bytes(), payload); r != "" { t.Error("data: ", r) } } ================================================ FILE: proxy/trojan/server.go ================================================ // +build !confonly package trojan import ( "context" "crypto/tls" "io" "strconv" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" udp_proto "v2ray.com/core/common/protocol/udp" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/internet/xtls" ) func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { // nolint: lll return NewServer(ctx, config.(*ServerConfig)) })) } // Server is an inbound connection handler that handles messages in trojan protocol. type Server struct { policyManager policy.Manager validator *Validator fallbacks map[string]map[string]*Fallback // or nil } // NewServer creates a new trojan inbound handler. func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { validator := new(Validator) for _, user := range config.Users { u, err := user.ToMemoryUser() if err != nil { return nil, newError("failed to get trojan user").Base(err).AtError() } if err := validator.Add(u); err != nil { return nil, newError("failed to add user").Base(err).AtError() } } v := core.MustFromContext(ctx) server := &Server{ policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), validator: validator, } if config.Fallbacks != nil { server.fallbacks = make(map[string]map[string]*Fallback) for _, fb := range config.Fallbacks { if server.fallbacks[fb.Alpn] == nil { server.fallbacks[fb.Alpn] = make(map[string]*Fallback) } server.fallbacks[fb.Alpn][fb.Path] = fb } if server.fallbacks[""] != nil { for alpn, pfb := range server.fallbacks { if alpn != "" { // && alpn != "h2" { for path, fb := range server.fallbacks[""] { if pfb[path] == nil { pfb[path] = fb } } } } } } return server, nil } // Network implements proxy.Inbound.Network(). func (s *Server) Network() []net.Network { return []net.Network{net.Network_TCP} } // Process implements proxy.Inbound.Process(). func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { // nolint: funlen,lll sid := session.ExportIDToError(ctx) iConn := conn if statConn, ok := iConn.(*internet.StatCouterConnection); ok { iConn = statConn.Connection } sessionPolicy := s.policyManager.ForLevel(0) if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { return newError("unable to set read deadline").Base(err).AtWarning() } first := buf.New() defer first.Release() firstLen, err := first.ReadFrom(conn) if err != nil { return newError("failed to read first request").Base(err) } newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid) bufferedReader := &buf.BufferedReader{ Reader: buf.NewReader(conn), Buffer: buf.MultiBuffer{first}, } var user *protocol.MemoryUser apfb := s.fallbacks isfb := apfb != nil shouldFallback := false if firstLen < 58 || first.Byte(56) != '\r' { // nolint: gomnd // invalid protocol err = newError("not trojan protocol") log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) shouldFallback = true } else { user = s.validator.Get(hexString(first.BytesTo(56))) // nolint: gomnd if user == nil { // invalid user, let's fallback err = newError("not a valid user") log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) shouldFallback = true } } if isfb && shouldFallback { return s.fallback(ctx, sid, err, sessionPolicy, conn, iConn, apfb, first, firstLen, bufferedReader) } else if shouldFallback { return newError("invalid protocol or invalid user") } clientReader := &ConnReader{Reader: bufferedReader} if err := clientReader.ParseHeader(); err != nil { log.Record(&log.AccessMessage{ From: conn.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) return newError("failed to create request from: ", conn.RemoteAddr()).Base(err) } destination := clientReader.Target if err := conn.SetReadDeadline(time.Time{}); err != nil { return newError("unable to set read deadline").Base(err).AtWarning() } inbound := session.InboundFromContext(ctx) if inbound == nil { panic("no inbound metadata") } inbound.User = user sessionPolicy = s.policyManager.ForLevel(user.Level) if destination.Network == net.Network_UDP { // handle udp request return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher) } // handle tcp request ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: conn.RemoteAddr(), To: destination, Status: log.AccessAccepted, Reason: "", Email: user.Email, }) newError("received request for ", destination).WriteToLog(sid) return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher) } func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error { // nolint: lll udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { common.Must(clientWriter.WriteMultiBufferWithMetadata(buf.MultiBuffer{packet.Payload}, packet.Source)) }) inbound := session.InboundFromContext(ctx) user := inbound.User for { select { case <-ctx.Done(): return nil default: p, err := clientReader.ReadMultiBufferWithMetadata() if err != nil { if errors.Cause(err) != io.EOF { return newError("unexpected EOF").Base(err) } return nil } ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: inbound.Source, To: p.Target, Status: log.AccessAccepted, Reason: "", Email: user.Email, }) newError("tunnelling request to ", p.Target).WriteToLog(session.ExportIDToError(ctx)) for _, b := range p.Buffer { udpServer.Dispatch(ctx, p.Target, b) } } } } func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session, destination net.Destination, clientReader buf.Reader, clientWriter buf.Writer, dispatcher routing.Dispatcher) error { ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return newError("failed to dispatch request to ", destination).Base(err) } requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) if err := buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request").Base(err) } return nil } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) if err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to write response").Base(err) } return nil } var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDonePost, responseDone); err != nil { common.Must(common.Interrupt(link.Reader)) common.Must(common.Interrupt(link.Writer)) return newError("connection ends").Base(err) } return nil } func (s *Server) fallback(ctx context.Context, sid errors.ExportOption, err error, sessionPolicy policy.Session, connection internet.Connection, iConn internet.Connection, apfb map[string]map[string]*Fallback, first *buf.Buffer, firstLen int64, reader buf.Reader) error { // nolint: lll if err := connection.SetReadDeadline(time.Time{}); err != nil { newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid) } newError("fallback starts").Base(err).AtInfo().WriteToLog(sid) alpn := "" if len(apfb) > 1 || apfb[""] == nil { if tlsConn, ok := iConn.(*tls.Conn); ok { alpn = tlsConn.ConnectionState().NegotiatedProtocol newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { alpn = xtlsConn.ConnectionState().NegotiatedProtocol newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) } if apfb[alpn] == nil { alpn = "" } } pfb := apfb[alpn] if pfb == nil { return newError(`failed to find the default "alpn" config`).AtWarning() } path := "" if len(pfb) > 1 || pfb[""] == nil { if firstLen >= 18 && first.Byte(4) != '*' { // not h2c firstBytes := first.Bytes() for i := 4; i <= 8; i++ { // 5 -> 9 if firstBytes[i] == '/' && firstBytes[i-1] == ' ' { search := len(firstBytes) if search > 64 { search = 64 // up to about 60 } for j := i + 1; j < search; j++ { k := firstBytes[j] if k == '\r' || k == '\n' { // avoid logging \r or \n break } if k == ' ' { path = string(firstBytes[i:j]) newError("realPath = " + path).AtInfo().WriteToLog(sid) if pfb[path] == nil { path = "" } break } } break } } } } fb := pfb[path] if fb == nil { return newError(`failed to find the default "path" config`).AtWarning() } ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) var conn net.Conn if err := retry.ExponentialBackoff(5, 100).On(func() error { var dialer net.Dialer conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest) if err != nil { return err } return nil }); err != nil { return newError("failed to dial to " + fb.Dest).Base(err).AtWarning() } defer conn.Close() serverReader := buf.NewReader(conn) serverWriter := buf.NewWriter(conn) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) if fb.Xver != 0 { remoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String()) if err != nil { return err } localAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String()) if err != nil { return err } ipv4 := true for i := 0; i < len(remoteAddr); i++ { if remoteAddr[i] == ':' { ipv4 = false break } } pro := buf.New() defer pro.Release() switch fb.Xver { case 1: if ipv4 { common.Must2(pro.Write([]byte("PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))) } else { common.Must2(pro.Write([]byte("PROXY TCP6 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n"))) } case 2: common.Must2(pro.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21"))) // signature + v2 + PROXY if ipv4 { common.Must2(pro.Write([]byte("\x11\x00\x0C"))) // AF_INET + STREAM + 12 bytes common.Must2(pro.Write(net.ParseIP(remoteAddr).To4())) common.Must2(pro.Write(net.ParseIP(localAddr).To4())) } else { common.Must2(pro.Write([]byte("\x21\x00\x24"))) // AF_INET6 + STREAM + 36 bytes common.Must2(pro.Write(net.ParseIP(remoteAddr).To16())) common.Must2(pro.Write(net.ParseIP(localAddr).To16())) } p1, _ := strconv.ParseUint(remotePort, 10, 16) p2, _ := strconv.ParseUint(localPort, 10, 16) common.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})) } if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil { return newError("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning() } } if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to fallback request payload").Base(err).AtInfo() } return nil } writer := buf.NewWriter(connection) getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to deliver response payload").Base(err).AtInfo() } return nil } if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { common.Must(common.Interrupt(serverReader)) common.Must(common.Interrupt(serverWriter)) return newError("fallback ends").Base(err).AtInfo() } return nil } ================================================ FILE: proxy/trojan/trojan.go ================================================ package trojan ================================================ FILE: proxy/trojan/validator.go ================================================ package trojan import ( "sync" "v2ray.com/core/common/protocol" ) // Validator stores valid trojan users type Validator struct { users sync.Map } // Add a trojan user func (v *Validator) Add(u *protocol.MemoryUser) error { user := u.Account.(*MemoryAccount) v.users.Store(hexString(user.Key), u) return nil } // Get user with hashed key, nil if user doesn't exist. func (v *Validator) Get(hash string) *protocol.MemoryUser { u, _ := v.users.Load(hash) if u != nil { return u.(*protocol.MemoryUser) } return nil } ================================================ FILE: proxy/vless/account.go ================================================ // +build !confonly package vless import ( "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" ) // AsAccount implements protocol.Account.AsAccount(). func (a *Account) AsAccount() (protocol.Account, error) { id, err := uuid.ParseString(a.Id) if err != nil { return nil, newError("failed to parse ID").Base(err).AtError() } return &MemoryAccount{ ID: protocol.NewID(id), Flow: a.Flow, // needs parser here? Encryption: a.Encryption, // needs parser here? }, nil } // MemoryAccount is an in-memory form of VLess account. type MemoryAccount struct { // ID of the account. ID *protocol.ID // Flow of the account. May be "xtls-rprx-origin". Flow string // Encryption of the account. Used for client connections, and only accepts "none" for now. Encryption string } // Equals implements protocol.Account.Equals(). func (a *MemoryAccount) Equals(account protocol.Account) bool { vlessAccount, ok := account.(*MemoryAccount) if !ok { return false } return a.ID.Equals(vlessAccount.ID) } ================================================ FILE: proxy/vless/account.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vless/account.proto package vless import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Flow settings. May be "xtls-rprx-origin". Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` // Encryption settings. Only applies to client side, and only accepts "none" for now. Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vless_account_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_vless_account_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_vless_account_proto_rawDescGZIP(), []int{0} } func (x *Account) GetId() string { if x != nil { return x.Id } return "" } func (x *Account) GetFlow() string { if x != nil { return x.Flow } return "" } func (x *Account) GetEncryption() string { if x != nil { return x.Encryption } return "" } var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x53, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x16, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vless_account_proto_rawDescOnce sync.Once file_proxy_vless_account_proto_rawDescData = file_proxy_vless_account_proto_rawDesc ) func file_proxy_vless_account_proto_rawDescGZIP() []byte { file_proxy_vless_account_proto_rawDescOnce.Do(func() { file_proxy_vless_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_account_proto_rawDescData) }) return file_proxy_vless_account_proto_rawDescData } var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_vless_account_proto_goTypes = []interface{}{ (*Account)(nil), // 0: v2ray.core.proxy.vless.Account } var file_proxy_vless_account_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_proxy_vless_account_proto_init() } func file_proxy_vless_account_proto_init() { if File_proxy_vless_account_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vless_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vless_account_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vless_account_proto_goTypes, DependencyIndexes: file_proxy_vless_account_proto_depIdxs, MessageInfos: file_proxy_vless_account_proto_msgTypes, }.Build() File_proxy_vless_account_proto = out.File file_proxy_vless_account_proto_rawDesc = nil file_proxy_vless_account_proto_goTypes = nil file_proxy_vless_account_proto_depIdxs = nil } ================================================ FILE: proxy/vless/account.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vless; option csharp_namespace = "V2Ray.Core.Proxy.Vless"; option go_package = "v2ray.com/core/proxy/vless"; option java_package = "com.v2ray.core.proxy.vless"; option java_multiple_files = true; message Account { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; // Flow settings. May be "xtls-rprx-origin". string flow = 2; // Encryption settings. Only applies to client side, and only accepts "none" for now. string encryption = 3; } ================================================ FILE: proxy/vless/encoding/addons.go ================================================ // +build !confonly package encoding import ( "io" "github.com/golang/protobuf/proto" "v2ray.com/core/common/buf" "v2ray.com/core/common/protocol" "v2ray.com/core/proxy/vless" ) func EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error { switch addons.Flow { case vless.XRO, vless.XRD: bytes, err := proto.Marshal(addons) if err != nil { return newError("failed to marshal addons protobuf value").Base(err) } if err := buffer.WriteByte(byte(len(bytes))); err != nil { return newError("failed to write addons protobuf length").Base(err) } if _, err := buffer.Write(bytes); err != nil { return newError("failed to write addons protobuf value").Base(err) } default: if err := buffer.WriteByte(0); err != nil { return newError("failed to write addons protobuf length").Base(err) } } return nil } func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) { addons := new(Addons) buffer.Clear() if _, err := buffer.ReadFullFrom(reader, 1); err != nil { return nil, newError("failed to read addons protobuf length").Base(err) } if length := int32(buffer.Byte(0)); length != 0 { buffer.Clear() if _, err := buffer.ReadFullFrom(reader, length); err != nil { return nil, newError("failed to read addons protobuf value").Base(err) } if err := proto.Unmarshal(buffer.Bytes(), addons); err != nil { return nil, newError("failed to unmarshal addons protobuf value").Base(err) } // Verification. switch addons.Flow { default: } } return addons, nil } // EncodeBodyAddons returns a Writer that auto-encrypt content written by caller. func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, addons *Addons) buf.Writer { switch addons.Flow { default: if request.Command == protocol.RequestCommandUDP { return NewMultiLengthPacketWriter(writer.(buf.Writer)) } } return buf.NewWriter(writer) } // DecodeBodyAddons returns a Reader from which caller can fetch decrypted body. func DecodeBodyAddons(reader io.Reader, request *protocol.RequestHeader, addons *Addons) buf.Reader { switch addons.Flow { default: if request.Command == protocol.RequestCommandUDP { return NewLengthPacketReader(reader) } } return buf.NewReader(reader) } func NewMultiLengthPacketWriter(writer buf.Writer) *MultiLengthPacketWriter { return &MultiLengthPacketWriter{ Writer: writer, } } type MultiLengthPacketWriter struct { buf.Writer } func (w *MultiLengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { defer buf.ReleaseMulti(mb) mb2Write := make(buf.MultiBuffer, 0, len(mb)+1) for _, b := range mb { length := b.Len() if length == 0 || length+2 > buf.Size { continue } eb := buf.New() if err := eb.WriteByte(byte(length >> 8)); err != nil { eb.Release() continue } if err := eb.WriteByte(byte(length)); err != nil { eb.Release() continue } if _, err := eb.Write(b.Bytes()); err != nil { eb.Release() continue } mb2Write = append(mb2Write, eb) } if mb2Write.IsEmpty() { return nil } return w.Writer.WriteMultiBuffer(mb2Write) } func NewLengthPacketWriter(writer io.Writer) *LengthPacketWriter { return &LengthPacketWriter{ Writer: writer, cache: make([]byte, 0, 65536), } } type LengthPacketWriter struct { io.Writer cache []byte } func (w *LengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { length := mb.Len() // none of mb is nil //fmt.Println("Write", length) if length == 0 { return nil } defer func() { w.cache = w.cache[:0] }() w.cache = append(w.cache, byte(length>>8), byte(length)) for i, b := range mb { w.cache = append(w.cache, b.Bytes()...) b.Release() mb[i] = nil } if _, err := w.Write(w.cache); err != nil { return newError("failed to write a packet").Base(err) } return nil } func NewLengthPacketReader(reader io.Reader) *LengthPacketReader { return &LengthPacketReader{ Reader: reader, cache: make([]byte, 2), } } type LengthPacketReader struct { io.Reader cache []byte } func (r *LengthPacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { if _, err := io.ReadFull(r.Reader, r.cache); err != nil { // maybe EOF return nil, newError("failed to read packet length").Base(err) } length := int32(r.cache[0])<<8 | int32(r.cache[1]) //fmt.Println("Read", length) mb := make(buf.MultiBuffer, 0, length/buf.Size+1) for length > 0 { size := length if size > buf.Size { size = buf.Size } length -= size b := buf.New() if _, err := b.ReadFullFrom(r.Reader, size); err != nil { return nil, newError("failed to read packet payload").Base(err) } mb = append(mb, b) } return mb, nil } ================================================ FILE: proxy/vless/encoding/addons.pb.go ================================================ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: proxy/vless/encoding/addons.proto package encoding import ( fmt "fmt" proto "github.com/golang/protobuf/proto" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type Addons struct { Flow string `protobuf:"bytes,1,opt,name=Flow,proto3" json:"Flow,omitempty"` Seed []byte `protobuf:"bytes,2,opt,name=Seed,proto3" json:"Seed,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Addons) Reset() { *m = Addons{} } func (m *Addons) String() string { return proto.CompactTextString(m) } func (*Addons) ProtoMessage() {} func (*Addons) Descriptor() ([]byte, []int) { return fileDescriptor_75ab671b0ca8b1cc, []int{0} } func (m *Addons) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Addons) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Addons.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *Addons) XXX_Merge(src proto.Message) { xxx_messageInfo_Addons.Merge(m, src) } func (m *Addons) XXX_Size() int { return m.Size() } func (m *Addons) XXX_DiscardUnknown() { xxx_messageInfo_Addons.DiscardUnknown(m) } var xxx_messageInfo_Addons proto.InternalMessageInfo func (m *Addons) GetFlow() string { if m != nil { return m.Flow } return "" } func (m *Addons) GetSeed() []byte { if m != nil { return m.Seed } return nil } func init() { proto.RegisterType((*Addons)(nil), "v2ray.core.proxy.vless.encoding.Addons") } func init() { proto.RegisterFile("proxy/vless/encoding/addons.proto", fileDescriptor_75ab671b0ca8b1cc) } var fileDescriptor_75ab671b0ca8b1cc = []byte{ // 186 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2c, 0x28, 0xca, 0xaf, 0xa8, 0xd4, 0x2f, 0xcb, 0x49, 0x2d, 0x2e, 0xd6, 0x4f, 0xcd, 0x4b, 0xce, 0x4f, 0xc9, 0xcc, 0x4b, 0xd7, 0x4f, 0x4c, 0x49, 0xc9, 0xcf, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x2f, 0x33, 0x2a, 0x4a, 0xac, 0xd4, 0x4b, 0xce, 0x2f, 0x4a, 0xd5, 0x03, 0xab, 0xd6, 0x03, 0xab, 0xd6, 0x83, 0xa9, 0x56, 0x32, 0xe0, 0x62, 0x73, 0x04, 0x6b, 0x10, 0x12, 0xe2, 0x62, 0x71, 0xcb, 0xc9, 0x2f, 0x97, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x41, 0x62, 0xc1, 0xa9, 0xa9, 0x29, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x60, 0xb6, 0x53, 0xdd, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe3, 0xb1, 0x1c, 0x03, 0x97, 0x72, 0x72, 0x7e, 0xae, 0x1e, 0x01, 0x8b, 0x02, 0x18, 0xa3, 0x94, 0x61, 0x4a, 0x72, 0xf5, 0x41, 0xca, 0xf4, 0xb1, 0xb9, 0x7e, 0x15, 0x93, 0x7c, 0x98, 0x51, 0x50, 0x62, 0xa5, 0x9e, 0x33, 0xc8, 0xa0, 0x00, 0xb0, 0x41, 0x61, 0x60, 0x83, 0x5c, 0xa1, 0x2a, 0x92, 0xd8, 0xc0, 0x3e, 0x33, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x36, 0x32, 0x14, 0x7c, 0xfe, 0x00, 0x00, 0x00, } func (m *Addons) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Addons) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Addons) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Seed) > 0 { i -= len(m.Seed) copy(dAtA[i:], m.Seed) i = encodeVarintAddons(dAtA, i, uint64(len(m.Seed))) i-- dAtA[i] = 0x12 } if len(m.Flow) > 0 { i -= len(m.Flow) copy(dAtA[i:], m.Flow) i = encodeVarintAddons(dAtA, i, uint64(len(m.Flow))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintAddons(dAtA []byte, offset int, v uint64) int { offset -= sovAddons(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *Addons) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Flow) if l > 0 { n += 1 + l + sovAddons(uint64(l)) } l = len(m.Seed) if l > 0 { n += 1 + l + sovAddons(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func sovAddons(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozAddons(x uint64) (n int) { return sovAddons(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *Addons) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAddons } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: Addons: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Addons: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Flow", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAddons } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthAddons } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthAddons } if postIndex > l { return io.ErrUnexpectedEOF } m.Flow = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Seed", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAddons } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthAddons } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthAddons } if postIndex > l { return io.ErrUnexpectedEOF } m.Seed = append(m.Seed[:0], dAtA[iNdEx:postIndex]...) if m.Seed == nil { m.Seed = []byte{} } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAddons(dAtA[iNdEx:]) if err != nil { return err } if skippy < 0 { return ErrInvalidLengthAddons } if (iNdEx + skippy) < 0 { return ErrInvalidLengthAddons } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipAddons(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowAddons } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowAddons } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowAddons } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthAddons } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupAddons } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthAddons } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthAddons = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowAddons = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupAddons = fmt.Errorf("proto: unexpected end of group") ) ================================================ FILE: proxy/vless/encoding/addons.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vless.encoding; option csharp_namespace = "V2Ray.Core.Proxy.Vless.Encoding"; option go_package = "v2ray.com/core/proxy/vless/encoding"; option java_package = "com.v2ray.core.proxy.vless.encoding"; option java_multiple_files = true; message Addons { string Flow = 1; bytes Seed = 2; } ================================================ FILE: proxy/vless/encoding/encoding.go ================================================ // +build !confonly package encoding //go:generate go run v2ray.com/core/common/errors/errorgen import ( "io" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/proxy/vless" ) const ( Version = byte(0) ) var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6), protocol.PortThenAddress(), ) // EncodeRequestHeader writes encoded request header into the given writer. func EncodeRequestHeader(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons) error { buffer := buf.StackNew() defer buffer.Release() if err := buffer.WriteByte(request.Version); err != nil { return newError("failed to write request version").Base(err) } if _, err := buffer.Write(request.User.Account.(*vless.MemoryAccount).ID.Bytes()); err != nil { return newError("failed to write request user id").Base(err) } if err := EncodeHeaderAddons(&buffer, requestAddons); err != nil { return newError("failed to encode request header addons").Base(err) } if err := buffer.WriteByte(byte(request.Command)); err != nil { return newError("failed to write request command").Base(err) } if request.Command != protocol.RequestCommandMux { if err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil { return newError("failed to write request address and port").Base(err) } } if _, err := writer.Write(buffer.Bytes()); err != nil { return newError("failed to write request header").Base(err) } return nil } // DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream. func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validator *vless.Validator) (*protocol.RequestHeader, *Addons, error, bool) { buffer := buf.StackNew() defer buffer.Release() request := new(protocol.RequestHeader) if isfb { request.Version = first.Byte(0) } else { if _, err := buffer.ReadFullFrom(reader, 1); err != nil { return nil, nil, newError("failed to read request version").Base(err), false } request.Version = buffer.Byte(0) } switch request.Version { case 0: var id [16]byte if isfb { copy(id[:], first.BytesRange(1, 17)) } else { buffer.Clear() if _, err := buffer.ReadFullFrom(reader, 16); err != nil { return nil, nil, newError("failed to read request user id").Base(err), false } copy(id[:], buffer.Bytes()) } if request.User = validator.Get(id); request.User == nil { return nil, nil, newError("invalid request user id"), isfb } if isfb { first.Advance(17) } requestAddons, err := DecodeHeaderAddons(&buffer, reader) if err != nil { return nil, nil, newError("failed to decode request header addons").Base(err), false } buffer.Clear() if _, err := buffer.ReadFullFrom(reader, 1); err != nil { return nil, nil, newError("failed to read request command").Base(err), false } request.Command = protocol.RequestCommand(buffer.Byte(0)) switch request.Command { case protocol.RequestCommandMux: request.Address = net.DomainAddress("v1.mux.cool") request.Port = 0 case protocol.RequestCommandTCP, protocol.RequestCommandUDP: if addr, port, err := addrParser.ReadAddressPort(&buffer, reader); err == nil { request.Address = addr request.Port = port } } if request.Address == nil { return nil, nil, newError("invalid request address"), false } return request, requestAddons, nil, false default: return nil, nil, newError("invalid request version"), isfb } } // EncodeResponseHeader writes encoded response header into the given writer. func EncodeResponseHeader(writer io.Writer, request *protocol.RequestHeader, responseAddons *Addons) error { buffer := buf.StackNew() defer buffer.Release() if err := buffer.WriteByte(request.Version); err != nil { return newError("failed to write response version").Base(err) } if err := EncodeHeaderAddons(&buffer, responseAddons); err != nil { return newError("failed to encode response header addons").Base(err) } if _, err := writer.Write(buffer.Bytes()); err != nil { return newError("failed to write response header").Base(err) } return nil } // DecodeResponseHeader decodes and returns (if successful) a ResponseHeader from an input stream. func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*Addons, error) { buffer := buf.StackNew() defer buffer.Release() if _, err := buffer.ReadFullFrom(reader, 1); err != nil { return nil, newError("failed to read response version").Base(err) } if buffer.Byte(0) != request.Version { return nil, newError("unexpected response version. Expecting ", int(request.Version), " but actually ", int(buffer.Byte(0))) } responseAddons, err := DecodeHeaderAddons(&buffer, reader) if err != nil { return nil, newError("failed to decode response header addons").Base(err) } return responseAddons, nil } ================================================ FILE: proxy/vless/encoding/encoding_test.go ================================================ package encoding_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/vless" . "v2ray.com/core/proxy/vless/encoding" ) func toAccount(a *vless.Account) protocol.Account { account, err := a.AsAccount() common.Must(err) return account } func TestRequestSerialization(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2fly.org", } id := uuid.New() account := &vless.Account{ Id: id.String(), } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: Version, User: user, Command: protocol.RequestCommandTCP, Address: net.DomainAddress("www.v2fly.org"), Port: net.Port(443), } expectedAddons := &Addons{} buffer := buf.StackNew() common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) Validator := new(vless.Validator) Validator.Add(user) actualRequest, actualAddons, err, _ := DecodeRequestHeader(false, nil, &buffer, Validator) common.Must(err) if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { t.Error(r) } if r := cmp.Diff(actualAddons, expectedAddons); r != "" { t.Error(r) } } func TestInvalidRequest(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2fly.org", } id := uuid.New() account := &vless.Account{ Id: id.String(), } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: Version, User: user, Command: protocol.RequestCommand(100), Address: net.DomainAddress("www.v2fly.org"), Port: net.Port(443), } expectedAddons := &Addons{} buffer := buf.StackNew() common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) Validator := new(vless.Validator) Validator.Add(user) _, _, err, _ := DecodeRequestHeader(false, nil, &buffer, Validator) if err == nil { t.Error("nil error") } } func TestMuxRequest(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2fly.org", } id := uuid.New() account := &vless.Account{ Id: id.String(), } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: Version, User: user, Command: protocol.RequestCommandMux, Address: net.DomainAddress("v1.mux.cool"), } expectedAddons := &Addons{} buffer := buf.StackNew() common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) Validator := new(vless.Validator) Validator.Add(user) actualRequest, actualAddons, err, _ := DecodeRequestHeader(false, nil, &buffer, Validator) common.Must(err) if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { t.Error(r) } if r := cmp.Diff(actualAddons, expectedAddons); r != "" { t.Error(r) } } ================================================ FILE: proxy/vless/encoding/errors.generated.go ================================================ package encoding import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vless/errors.generated.go ================================================ package vless import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vless/inbound/config.go ================================================ // +build !confonly package inbound ================================================ FILE: proxy/vless/inbound/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vless/inbound/config.proto package inbound import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Fallback struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Alpn string `protobuf:"bytes,1,opt,name=alpn,proto3" json:"alpn,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` Dest string `protobuf:"bytes,4,opt,name=dest,proto3" json:"dest,omitempty"` Xver uint64 `protobuf:"varint,5,opt,name=xver,proto3" json:"xver,omitempty"` } func (x *Fallback) Reset() { *x = Fallback{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vless_inbound_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Fallback) String() string { return protoimpl.X.MessageStringOf(x) } func (*Fallback) ProtoMessage() {} func (x *Fallback) ProtoReflect() protoreflect.Message { mi := &file_proxy_vless_inbound_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Fallback.ProtoReflect.Descriptor instead. func (*Fallback) Descriptor() ([]byte, []int) { return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{0} } func (x *Fallback) GetAlpn() string { if x != nil { return x.Alpn } return "" } func (x *Fallback) GetPath() string { if x != nil { return x.Path } return "" } func (x *Fallback) GetType() string { if x != nil { return x.Type } return "" } func (x *Fallback) GetDest() string { if x != nil { return x.Dest } return "" } func (x *Fallback) GetXver() uint64 { if x != nil { return x.Xver } return 0 } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` // Decryption settings. Only applies to server side, and only accepts "none" // for now. Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"` Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vless_inbound_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_vless_inbound_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetClients() []*protocol.User { if x != nil { return x.Clients } return nil } func (x *Config) GetDecryption() string { if x != nil { return x.Decryption } return "" } func (x *Config) GetFallbacks() []*Fallback { if x != nil { return x.Fallbacks } return nil } var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6e, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xac, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x42, 0x6b, 0x0a, 0x22, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1e, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vless_inbound_config_proto_rawDescOnce sync.Once file_proxy_vless_inbound_config_proto_rawDescData = file_proxy_vless_inbound_config_proto_rawDesc ) func file_proxy_vless_inbound_config_proto_rawDescGZIP() []byte { file_proxy_vless_inbound_config_proto_rawDescOnce.Do(func() { file_proxy_vless_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_inbound_config_proto_rawDescData) }) return file_proxy_vless_inbound_config_proto_rawDescData } var file_proxy_vless_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_vless_inbound_config_proto_goTypes = []interface{}{ (*Fallback)(nil), // 0: v2ray.core.proxy.vless.inbound.Fallback (*Config)(nil), // 1: v2ray.core.proxy.vless.inbound.Config (*protocol.User)(nil), // 2: v2ray.core.common.protocol.User } var file_proxy_vless_inbound_config_proto_depIdxs = []int32{ 2, // 0: v2ray.core.proxy.vless.inbound.Config.clients:type_name -> v2ray.core.common.protocol.User 0, // 1: v2ray.core.proxy.vless.inbound.Config.fallbacks:type_name -> v2ray.core.proxy.vless.inbound.Fallback 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_proxy_vless_inbound_config_proto_init() } func file_proxy_vless_inbound_config_proto_init() { if File_proxy_vless_inbound_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vless_inbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Fallback); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_vless_inbound_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vless_inbound_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vless_inbound_config_proto_goTypes, DependencyIndexes: file_proxy_vless_inbound_config_proto_depIdxs, MessageInfos: file_proxy_vless_inbound_config_proto_msgTypes, }.Build() File_proxy_vless_inbound_config_proto = out.File file_proxy_vless_inbound_config_proto_rawDesc = nil file_proxy_vless_inbound_config_proto_goTypes = nil file_proxy_vless_inbound_config_proto_depIdxs = nil } ================================================ FILE: proxy/vless/inbound/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vless.inbound; option csharp_namespace = "V2Ray.Core.Proxy.Vless.Inbound"; option go_package = "v2ray.com/core/proxy/vless/inbound"; option java_package = "com.v2ray.core.proxy.vless.inbound"; option java_multiple_files = true; import "common/protocol/user.proto"; message Fallback { string alpn = 1; string path = 2; string type = 3; string dest = 4; uint64 xver = 5; } message Config { repeated v2ray.core.common.protocol.User clients = 1; // Decryption settings. Only applies to server side, and only accepts "none" // for now. string decryption = 2; repeated Fallback fallbacks = 3; } ================================================ FILE: proxy/vless/inbound/errors.generated.go ================================================ package inbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vless/inbound/inbound.go ================================================ // +build !confonly package inbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "io" "strconv" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/dns" feature_inbound "v2ray.com/core/features/inbound" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/proxy/vless" "v2ray.com/core/proxy/vless/encoding" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) var ( xtls_show = false ) func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { var dc dns.Client if err := core.RequireFeatures(ctx, func(d dns.Client) error { dc = d return nil }); err != nil { return nil, err } return New(ctx, config.(*Config), dc) })) const defaultFlagValue = "NOT_DEFINED_AT_ALL" xtlsShow := platform.NewEnvFlag("v2ray.vless.xtls.show").GetValue(func() string { return defaultFlagValue }) if xtlsShow == "true" { xtls_show = true } } // Handler is an inbound connection handler that handles messages in VLess protocol. type Handler struct { inboundHandlerManager feature_inbound.Manager policyManager policy.Manager validator *vless.Validator dns dns.Client fallbacks map[string]map[string]*Fallback // or nil //regexps map[string]*regexp.Regexp // or nil } // New creates a new VLess inbound handler. func New(ctx context.Context, config *Config, dc dns.Client) (*Handler, error) { v := core.MustFromContext(ctx) handler := &Handler{ inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), validator: new(vless.Validator), dns: dc, } for _, user := range config.Clients { u, err := user.ToMemoryUser() if err != nil { return nil, newError("failed to get VLESS user").Base(err).AtError() } if err := handler.AddUser(ctx, u); err != nil { return nil, newError("failed to initiate user").Base(err).AtError() } } if config.Fallbacks != nil { handler.fallbacks = make(map[string]map[string]*Fallback) //handler.regexps = make(map[string]*regexp.Regexp) for _, fb := range config.Fallbacks { if handler.fallbacks[fb.Alpn] == nil { handler.fallbacks[fb.Alpn] = make(map[string]*Fallback) } handler.fallbacks[fb.Alpn][fb.Path] = fb /* if fb.Path != "" { if r, err := regexp.Compile(fb.Path); err != nil { return nil, newError("invalid path regexp").Base(err).AtError() } else { handler.regexps[fb.Path] = r } } */ } if handler.fallbacks[""] != nil { for alpn, pfb := range handler.fallbacks { if alpn != "" { // && alpn != "h2" { for path, fb := range handler.fallbacks[""] { if pfb[path] == nil { pfb[path] = fb } } } } } } return handler, nil } // Close implements common.Closable.Close(). func (h *Handler) Close() error { return errors.Combine(common.Close(h.validator)) } // AddUser implements proxy.UserManager.AddUser(). func (h *Handler) AddUser(ctx context.Context, u *protocol.MemoryUser) error { return h.validator.Add(u) } // RemoveUser implements proxy.UserManager.RemoveUser(). func (h *Handler) RemoveUser(ctx context.Context, e string) error { return h.validator.Del(e) } // Network implements proxy.Inbound.Network(). func (*Handler) Network() []net.Network { return []net.Network{net.Network_TCP} } // Process implements proxy.Inbound.Process(). func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher routing.Dispatcher) error { sid := session.ExportIDToError(ctx) iConn := connection if statConn, ok := iConn.(*internet.StatCouterConnection); ok { iConn = statConn.Connection } sessionPolicy := h.policyManager.ForLevel(0) if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { return newError("unable to set read deadline").Base(err).AtWarning() } first := buf.New() defer first.Release() firstLen, _ := first.ReadFrom(connection) newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid) reader := &buf.BufferedReader{ Reader: buf.NewReader(connection), Buffer: buf.MultiBuffer{first}, } var request *protocol.RequestHeader var requestAddons *encoding.Addons var err error apfb := h.fallbacks isfb := apfb != nil if isfb && firstLen < 18 { err = newError("fallback directly") } else { request, requestAddons, err, isfb = encoding.DecodeRequestHeader(isfb, first, reader, h.validator) } if err != nil { if isfb { if err := connection.SetReadDeadline(time.Time{}); err != nil { newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid) } newError("fallback starts").Base(err).AtInfo().WriteToLog(sid) alpn := "" if len(apfb) > 1 || apfb[""] == nil { if tlsConn, ok := iConn.(*tls.Conn); ok { alpn = tlsConn.ConnectionState().NegotiatedProtocol newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { alpn = xtlsConn.ConnectionState().NegotiatedProtocol newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) } if apfb[alpn] == nil { alpn = "" } } pfb := apfb[alpn] if pfb == nil { return newError(`failed to find the default "alpn" config`).AtWarning() } path := "" if len(pfb) > 1 || pfb[""] == nil { /* if lines := bytes.Split(firstBytes, []byte{'\r', '\n'}); len(lines) > 1 { if s := bytes.Split(lines[0], []byte{' '}); len(s) == 3 { if len(s[0]) < 8 && len(s[1]) > 0 && len(s[2]) == 8 { newError("realPath = " + string(s[1])).AtInfo().WriteToLog(sid) for _, fb := range pfb { if fb.Path != "" && h.regexps[fb.Path].Match(s[1]) { path = fb.Path break } } } } } */ if firstLen >= 18 && first.Byte(4) != '*' { // not h2c firstBytes := first.Bytes() for i := 4; i <= 8; i++ { // 5 -> 9 if firstBytes[i] == '/' && firstBytes[i-1] == ' ' { search := len(firstBytes) if search > 64 { search = 64 // up to about 60 } for j := i + 1; j < search; j++ { k := firstBytes[j] if k == '\r' || k == '\n' { // avoid logging \r or \n break } if k == ' ' { path = string(firstBytes[i:j]) newError("realPath = " + path).AtInfo().WriteToLog(sid) if pfb[path] == nil { path = "" } break } } break } } } } fb := pfb[path] if fb == nil { return newError(`failed to find the default "path" config`).AtWarning() } ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) var conn net.Conn if err := retry.ExponentialBackoff(5, 100).On(func() error { var dialer net.Dialer conn, err = dialer.DialContext(ctx, fb.Type, fb.Dest) if err != nil { return err } return nil }); err != nil { return newError("failed to dial to " + fb.Dest).Base(err).AtWarning() } defer conn.Close() // nolint: errcheck serverReader := buf.NewReader(conn) serverWriter := buf.NewWriter(conn) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) if fb.Xver != 0 { remoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String()) if err != nil { return err } localAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String()) if err != nil { return err } ipv4 := true for i := 0; i < len(remoteAddr); i++ { if remoteAddr[i] == ':' { ipv4 = false break } } pro := buf.New() defer pro.Release() switch fb.Xver { case 1: if ipv4 { pro.Write([]byte("PROXY TCP4 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n")) } else { pro.Write([]byte("PROXY TCP6 " + remoteAddr + " " + localAddr + " " + remotePort + " " + localPort + "\r\n")) } case 2: pro.Write([]byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21")) // signature + v2 + PROXY if ipv4 { pro.Write([]byte("\x11\x00\x0C")) // AF_INET + STREAM + 12 bytes pro.Write(net.ParseIP(remoteAddr).To4()) pro.Write(net.ParseIP(localAddr).To4()) } else { pro.Write([]byte("\x21\x00\x24")) // AF_INET6 + STREAM + 36 bytes pro.Write(net.ParseIP(remoteAddr).To16()) pro.Write(net.ParseIP(localAddr).To16()) } p1, _ := strconv.ParseUint(remotePort, 10, 16) p2, _ := strconv.ParseUint(localPort, 10, 16) pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)}) } if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil { return newError("failed to set PROXY protocol v", fb.Xver).Base(err).AtWarning() } } if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to fallback request payload").Base(err).AtInfo() } return nil } writer := buf.NewWriter(connection) getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to deliver response payload").Base(err).AtInfo() } return nil } if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { common.Interrupt(serverReader) common.Interrupt(serverWriter) return newError("fallback ends").Base(err).AtInfo() } return nil } if errors.Cause(err) != io.EOF { log.Record(&log.AccessMessage{ From: connection.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) err = newError("invalid request from ", connection.RemoteAddr()).Base(err).AtWarning() } return err } if err := connection.SetReadDeadline(time.Time{}); err != nil { newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid) } newError("received request for ", request.Destination()).AtInfo().WriteToLog(sid) inbound := session.InboundFromContext(ctx) if inbound == nil { panic("no inbound metadata") } inbound.User = request.User account := request.User.Account.(*vless.MemoryAccount) responseAddons := &encoding.Addons{ //Flow: requestAddons.Flow, } switch requestAddons.Flow { case vless.XRO, vless.XRD: if account.Flow == requestAddons.Flow { switch request.Command { case protocol.RequestCommandMux: return newError(requestAddons.Flow + " doesn't support Mux").AtWarning() case protocol.RequestCommandUDP: return newError(requestAddons.Flow + " doesn't support UDP").AtWarning() case protocol.RequestCommandTCP: if xtlsConn, ok := iConn.(*xtls.Conn); ok { xtlsConn.RPRX = true xtlsConn.SHOW = xtls_show xtlsConn.MARK = "XTLS" if requestAddons.Flow == vless.XRD { xtlsConn.DirectMode = true } } else { return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning() } } } else { return newError(account.ID.String() + " is not able to use " + requestAddons.Flow).AtWarning() } case "": default: return newError("unknown request flow " + requestAddons.Flow).AtWarning() } if request.Command != protocol.RequestCommandMux { ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: connection.RemoteAddr(), To: request.Destination(), Status: log.AccessAccepted, Reason: "", Email: request.User.Email, }) } sessionPolicy = h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) link, err := dispatcher.Dispatch(ctx, request.Destination()) if err != nil { return newError("failed to dispatch request to ", request.Destination()).Base(err).AtWarning() } serverReader := link.Reader // .(*pipe.Reader) serverWriter := link.Writer // .(*pipe.Writer) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) // default: clientReader := reader clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons) // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request payload").Base(err).AtInfo() } return nil } getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection)) if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil { return newError("failed to encode response header").Base(err).AtWarning() } // default: clientWriter := bufferWriter clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, responseAddons) { multiBuffer, err := serverReader.ReadMultiBuffer() if err != nil { return err // ... } if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil { return err // ... } } // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer if err := bufferWriter.SetBuffered(false); err != nil { return newError("failed to write A response payload").Base(err).AtWarning() } // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer response payload").Base(err).AtInfo() } // Indicates the end of response payload. switch responseAddons.Flow { default: } return nil } if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), getResponse); err != nil { common.Interrupt(serverReader) common.Interrupt(serverWriter) return newError("connection ends").Base(err).AtInfo() } return nil } ================================================ FILE: proxy/vless/outbound/config.go ================================================ // +build !confonly package outbound ================================================ FILE: proxy/vless/outbound/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vless/outbound/config.proto package outbound import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Vnext []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=vnext,proto3" json:"vnext,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vless_outbound_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_vless_outbound_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_vless_outbound_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetVnext() []*protocol.ServerEndpoint { if x != nil { return x.Vnext } return nil } var File_proxy_vless_outbound_config_proto protoreflect.FileDescriptor var file_proxy_vless_outbound_config_proto_rawDesc = []byte{ 0x0a, 0x21, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x76, 0x6e, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x6e, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x23, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1f, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vless_outbound_config_proto_rawDescOnce sync.Once file_proxy_vless_outbound_config_proto_rawDescData = file_proxy_vless_outbound_config_proto_rawDesc ) func file_proxy_vless_outbound_config_proto_rawDescGZIP() []byte { file_proxy_vless_outbound_config_proto_rawDescOnce.Do(func() { file_proxy_vless_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vless_outbound_config_proto_rawDescData) }) return file_proxy_vless_outbound_config_proto_rawDescData } var file_proxy_vless_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_vless_outbound_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.proxy.vless.outbound.Config (*protocol.ServerEndpoint)(nil), // 1: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_vless_outbound_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.proxy.vless.outbound.Config.vnext:type_name -> v2ray.core.common.protocol.ServerEndpoint 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_vless_outbound_config_proto_init() } func file_proxy_vless_outbound_config_proto_init() { if File_proxy_vless_outbound_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vless_outbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vless_outbound_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vless_outbound_config_proto_goTypes, DependencyIndexes: file_proxy_vless_outbound_config_proto_depIdxs, MessageInfos: file_proxy_vless_outbound_config_proto_msgTypes, }.Build() File_proxy_vless_outbound_config_proto = out.File file_proxy_vless_outbound_config_proto_rawDesc = nil file_proxy_vless_outbound_config_proto_goTypes = nil file_proxy_vless_outbound_config_proto_depIdxs = nil } ================================================ FILE: proxy/vless/outbound/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vless.outbound; option csharp_namespace = "V2Ray.Core.Proxy.Vless.Outbound"; option go_package = "v2ray.com/core/proxy/vless/outbound"; option java_package = "com.v2ray.core.proxy.vless.outbound"; option java_multiple_files = true; import "common/protocol/server_spec.proto"; message Config { repeated v2ray.core.common.protocol.ServerEndpoint vnext = 1; } ================================================ FILE: proxy/vless/outbound/errors.generated.go ================================================ package outbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vless/outbound/outbound.go ================================================ // +build !confonly package outbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/proxy/vless" "v2ray.com/core/proxy/vless/encoding" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/xtls" ) var ( xtls_show = false ) func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) const defaultFlagValue = "NOT_DEFINED_AT_ALL" xtlsShow := platform.NewEnvFlag("v2ray.vless.xtls.show").GetValue(func() string { return defaultFlagValue }) if xtlsShow == "true" { xtls_show = true } } // Handler is an outbound connection handler for VLess protocol. type Handler struct { serverList *protocol.ServerList serverPicker protocol.ServerPicker policyManager policy.Manager } // New creates a new VLess outbound handler. func New(ctx context.Context, config *Config) (*Handler, error) { serverList := protocol.NewServerList() for _, rec := range config.Vnext { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to parse server spec").Base(err).AtError() } serverList.AddServer(s) } v := core.MustFromContext(ctx) handler := &Handler{ serverList: serverList, serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return handler, nil } // Process implements proxy.Outbound.Process(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { var rec *protocol.ServerSpec var conn internet.Connection if err := retry.ExponentialBackoff(5, 200).On(func() error { rec = h.serverPicker.PickServer() var err error conn, err = dialer.Dial(ctx, rec.Destination()) if err != nil { return err } return nil }); err != nil { return newError("failed to find an available destination").Base(err).AtWarning() } defer conn.Close() // nolint: errcheck iConn := conn if statConn, ok := iConn.(*internet.StatCouterConnection); ok { iConn = statConn.Connection } outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified").AtError() } target := outbound.Target newError("tunneling request to ", target, " via ", rec.Destination()).AtInfo().WriteToLog(session.ExportIDToError(ctx)) command := protocol.RequestCommandTCP if target.Network == net.Network_UDP { command = protocol.RequestCommandUDP } if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" { command = protocol.RequestCommandMux } request := &protocol.RequestHeader{ Version: encoding.Version, User: rec.PickUser(), Command: command, Address: target.Address, Port: target.Port, } account := request.User.Account.(*vless.MemoryAccount) requestAddons := &encoding.Addons{ Flow: account.Flow, } allowUDP443 := false switch requestAddons.Flow { case vless.XRO + "-udp443", vless.XRD + "-udp443": allowUDP443 = true requestAddons.Flow = requestAddons.Flow[:16] fallthrough case vless.XRO, vless.XRD: switch request.Command { case protocol.RequestCommandMux: return newError(requestAddons.Flow + " doesn't support Mux").AtWarning() case protocol.RequestCommandUDP: if !allowUDP443 && request.Port == 443 { return newError(requestAddons.Flow + " stopped UDP/443").AtInfo() } requestAddons.Flow = "" case protocol.RequestCommandTCP: if xtlsConn, ok := iConn.(*xtls.Conn); ok { xtlsConn.RPRX = true xtlsConn.SHOW = xtls_show xtlsConn.MARK = "XTLS" if requestAddons.Flow == vless.XRD { xtlsConn.DirectMode = true } } else { return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning() } } default: if _, ok := iConn.(*xtls.Conn); ok { panic(`To avoid misunderstanding, you must fill in VLESS "flow" when using XTLS.`) } } sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) clientReader := link.Reader // .(*pipe.Reader) clientWriter := link.Writer // .(*pipe.Writer) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) if err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil { return newError("failed to encode request header").Base(err).AtWarning() } // default: serverWriter := bufferWriter serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons) if err := buf.CopyOnceTimeout(clientReader, serverWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { return err // ... } // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer if err := bufferWriter.SetBuffered(false); err != nil { return newError("failed to write A request payload").Base(err).AtWarning() } // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request payload").Base(err).AtInfo() } // Indicates the end of request payload. switch requestAddons.Flow { default: } return nil } getResponse := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) responseAddons, err := encoding.DecodeResponseHeader(conn, request) if err != nil { return newError("failed to decode response header").Base(err).AtWarning() } // default: serverReader := buf.NewReader(conn) serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons) // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer response payload").Base(err).AtInfo() } return nil } if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { return newError("connection ends").Base(err).AtInfo() } return nil } ================================================ FILE: proxy/vless/validator.go ================================================ // +build !confonly package vless import ( "strings" "sync" "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" ) type Validator struct { // Considering email's usage here, map + sync.Mutex/RWMutex may have better performance. email sync.Map users sync.Map } func (v *Validator) Add(u *protocol.MemoryUser) error { if u.Email != "" { _, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u) if loaded { return newError("User ", u.Email, " already exists.") } } v.users.Store(u.Account.(*MemoryAccount).ID.UUID(), u) return nil } func (v *Validator) Del(e string) error { if e == "" { return newError("Email must not be empty.") } le := strings.ToLower(e) u, _ := v.email.Load(le) if u == nil { return newError("User ", e, " not found.") } v.email.Delete(le) v.users.Delete(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID()) return nil } func (v *Validator) Get(id uuid.UUID) *protocol.MemoryUser { u, _ := v.users.Load(id) if u != nil { return u.(*protocol.MemoryUser) } return nil } ================================================ FILE: proxy/vless/vless.go ================================================ // Package vless contains the implementation of VLess protocol and transportation. // // VLess contains both inbound and outbound connections. VLess inbound is usually used on servers // together with 'freedom' to talk to final destination, while VLess outbound is usually used on // clients with 'socks' for proxying. package vless //go:generate go run v2ray.com/core/common/errors/errorgen const ( XRO = "xtls-rprx-origin" XRD = "xtls-rprx-direct" ) ================================================ FILE: proxy/vmess/account.go ================================================ // +build !confonly package vmess import ( "v2ray.com/core/common/dice" "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" ) // MemoryAccount is an in-memory form of VMess account. type MemoryAccount struct { // ID is the main ID of the account. ID *protocol.ID // AlterIDs are the alternative IDs of the account. AlterIDs []*protocol.ID // Security type of the account. Used for client connections. Security protocol.SecurityType } // AnyValidID returns an ID that is either the main ID or one of the alternative IDs if any. func (a *MemoryAccount) AnyValidID() *protocol.ID { if len(a.AlterIDs) == 0 { return a.ID } return a.AlterIDs[dice.Roll(len(a.AlterIDs))] } // Equals implements protocol.Account. func (a *MemoryAccount) Equals(account protocol.Account) bool { vmessAccount, ok := account.(*MemoryAccount) if !ok { return false } // TODO: handle AlterIds difference return a.ID.Equals(vmessAccount.ID) } // AsAccount implements protocol.Account. func (a *Account) AsAccount() (protocol.Account, error) { id, err := uuid.ParseString(a.Id) if err != nil { return nil, newError("failed to parse ID").Base(err).AtError() } protoID := protocol.NewID(id) return &MemoryAccount{ ID: protoID, AlterIDs: protocol.NewAlterIDs(protoID, uint16(a.AlterId)), Security: a.SecuritySettings.GetSecurityType(), }, nil } ================================================ FILE: proxy/vmess/account.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vmess/account.proto package vmess import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // ID of the account, in the form of a UUID, e.g., // "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Number of alternative IDs. Client and server must share the same number. AlterId uint32 `protobuf:"varint,2,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"` // Security settings. Only applies to client side. SecuritySettings *protocol.SecurityConfig `protobuf:"bytes,3,opt,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"` // Define tests enabled for this account TestsEnabled string `protobuf:"bytes,4,opt,name=tests_enabled,json=testsEnabled,proto3" json:"tests_enabled,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vmess_account_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_proxy_vmess_account_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_proxy_vmess_account_proto_rawDescGZIP(), []int{0} } func (x *Account) GetId() string { if x != nil { return x.Id } return "" } func (x *Account) GetAlterId() uint32 { if x != nil { return x.AlterId } return 0 } func (x *Account) GetSecuritySettings() *protocol.SecurityConfig { if x != nil { return x.SecuritySettings } return nil } func (x *Account) GetTestsEnabled() string { if x != nil { return x.TestsEnabled } return "" } var File_proxy_vmess_account_proto protoreflect.FileDescriptor var file_proxy_vmess_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x1a, 0x1d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x64, 0x12, 0x57, 0x0a, 0x11, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x53, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x16, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vmess_account_proto_rawDescOnce sync.Once file_proxy_vmess_account_proto_rawDescData = file_proxy_vmess_account_proto_rawDesc ) func file_proxy_vmess_account_proto_rawDescGZIP() []byte { file_proxy_vmess_account_proto_rawDescOnce.Do(func() { file_proxy_vmess_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vmess_account_proto_rawDescData) }) return file_proxy_vmess_account_proto_rawDescData } var file_proxy_vmess_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_vmess_account_proto_goTypes = []interface{}{ (*Account)(nil), // 0: v2ray.core.proxy.vmess.Account (*protocol.SecurityConfig)(nil), // 1: v2ray.core.common.protocol.SecurityConfig } var file_proxy_vmess_account_proto_depIdxs = []int32{ 1, // 0: v2ray.core.proxy.vmess.Account.security_settings:type_name -> v2ray.core.common.protocol.SecurityConfig 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_vmess_account_proto_init() } func file_proxy_vmess_account_proto_init() { if File_proxy_vmess_account_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vmess_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vmess_account_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vmess_account_proto_goTypes, DependencyIndexes: file_proxy_vmess_account_proto_depIdxs, MessageInfos: file_proxy_vmess_account_proto_msgTypes, }.Build() File_proxy_vmess_account_proto = out.File file_proxy_vmess_account_proto_rawDesc = nil file_proxy_vmess_account_proto_goTypes = nil file_proxy_vmess_account_proto_depIdxs = nil } ================================================ FILE: proxy/vmess/account.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vmess; option csharp_namespace = "V2Ray.Core.Proxy.Vmess"; option go_package = "v2ray.com/core/proxy/vmess"; option java_package = "com.v2ray.core.proxy.vmess"; option java_multiple_files = true; import "common/protocol/headers.proto"; message Account { // ID of the account, in the form of a UUID, e.g., // "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; // Number of alternative IDs. Client and server must share the same number. uint32 alter_id = 2; // Security settings. Only applies to client side. v2ray.core.common.protocol.SecurityConfig security_settings = 3; // Define tests enabled for this account string tests_enabled = 4; } ================================================ FILE: proxy/vmess/aead/authid.go ================================================ package aead import ( "bytes" "crypto/aes" "crypto/cipher" rand3 "crypto/rand" "encoding/binary" "errors" "hash/crc32" "io" "math" "time" "v2ray.com/core/common" antiReplayWindow "v2ray.com/core/common/antireplay" ) func CreateAuthID(cmdKey []byte, time int64) [16]byte { buf := bytes.NewBuffer(nil) common.Must(binary.Write(buf, binary.BigEndian, time)) var zero uint32 common.Must2(io.CopyN(buf, rand3.Reader, 4)) zero = crc32.ChecksumIEEE(buf.Bytes()) common.Must(binary.Write(buf, binary.BigEndian, zero)) aesBlock := NewCipherFromKey(cmdKey) if buf.Len() != 16 { panic("Size unexpected") } var result [16]byte aesBlock.Encrypt(result[:], buf.Bytes()) return result } func NewCipherFromKey(cmdKey []byte) cipher.Block { aesBlock, err := aes.NewCipher(KDF16(cmdKey, KDFSaltConst_AuthIDEncryptionKey)) if err != nil { panic(err) } return aesBlock } type AuthIDDecoder struct { s cipher.Block } func NewAuthIDDecoder(cmdKey []byte) *AuthIDDecoder { return &AuthIDDecoder{NewCipherFromKey(cmdKey)} } func (aidd *AuthIDDecoder) Decode(data [16]byte) (int64, uint32, int32, []byte) { aidd.s.Decrypt(data[:], data[:]) var t int64 var zero uint32 var rand int32 reader := bytes.NewReader(data[:]) common.Must(binary.Read(reader, binary.BigEndian, &t)) common.Must(binary.Read(reader, binary.BigEndian, &rand)) common.Must(binary.Read(reader, binary.BigEndian, &zero)) return t, zero, rand, data[:] } func NewAuthIDDecoderHolder() *AuthIDDecoderHolder { return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antiReplayWindow.NewAntiReplayWindow(120)} } type AuthIDDecoderHolder struct { aidhi map[string]*AuthIDDecoderItem apw *antiReplayWindow.AntiReplayWindow } type AuthIDDecoderItem struct { dec *AuthIDDecoder ticket interface{} } func NewAuthIDDecoderItem(key [16]byte, ticket interface{}) *AuthIDDecoderItem { return &AuthIDDecoderItem{ dec: NewAuthIDDecoder(key[:]), ticket: ticket, } } func (a *AuthIDDecoderHolder) AddUser(key [16]byte, ticket interface{}) { a.aidhi[string(key[:])] = NewAuthIDDecoderItem(key, ticket) } func (a *AuthIDDecoderHolder) RemoveUser(key [16]byte) { delete(a.aidhi, string(key[:])) } func (a *AuthIDDecoderHolder) Match(AuthID [16]byte) (interface{}, error) { for _, v := range a.aidhi { t, z, r, d := v.dec.Decode(AuthID) if z != crc32.ChecksumIEEE(d[:12]) { continue } if t < 0 { continue } if math.Abs(math.Abs(float64(t))-float64(time.Now().Unix())) > 120 { continue } if !a.apw.Check(AuthID[:]) { return nil, ErrReplay } _ = r return v.ticket, nil } return nil, ErrNotFound } var ErrNotFound = errors.New("user do not exist") var ErrReplay = errors.New("replayed request") ================================================ FILE: proxy/vmess/aead/authid_test.go ================================================ package aead import ( "fmt" "github.com/stretchr/testify/assert" "strconv" "testing" "time" ) func TestCreateAuthID(t *testing.T) { key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") authid := CreateAuthID(key, time.Now().Unix()) fmt.Println(key) fmt.Println(authid) } func TestCreateAuthIDAndDecode(t *testing.T) { key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") authid := CreateAuthID(key, time.Now().Unix()) fmt.Println(key) fmt.Println(authid) AuthDecoder := NewAuthIDDecoderHolder() var keyw [16]byte copy(keyw[:], key) AuthDecoder.AddUser(keyw, "Demo User") res, err := AuthDecoder.Match(authid) fmt.Println(res) fmt.Println(err) assert.Equal(t, "Demo User", res) assert.Nil(t, err) } func TestCreateAuthIDAndDecode2(t *testing.T) { key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") authid := CreateAuthID(key, time.Now().Unix()) fmt.Println(key) fmt.Println(authid) AuthDecoder := NewAuthIDDecoderHolder() var keyw [16]byte copy(keyw[:], key) AuthDecoder.AddUser(keyw, "Demo User") res, err := AuthDecoder.Match(authid) fmt.Println(res) fmt.Println(err) assert.Equal(t, "Demo User", res) assert.Nil(t, err) key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test") authid2 := CreateAuthID(key2, time.Now().Unix()) res2, err2 := AuthDecoder.Match(authid2) assert.EqualError(t, err2, "user do not exist") assert.Nil(t, res2) } func TestCreateAuthIDAndDecodeMassive(t *testing.T) { key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") authid := CreateAuthID(key, time.Now().Unix()) fmt.Println(key) fmt.Println(authid) AuthDecoder := NewAuthIDDecoderHolder() var keyw [16]byte copy(keyw[:], key) AuthDecoder.AddUser(keyw, "Demo User") res, err := AuthDecoder.Match(authid) fmt.Println(res) fmt.Println(err) assert.Equal(t, "Demo User", res) assert.Nil(t, err) for i := 0; i <= 10000; i++ { key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test", strconv.Itoa(i)) var keyw2 [16]byte copy(keyw2[:], key2) AuthDecoder.AddUser(keyw2, "Demo User"+strconv.Itoa(i)) } authid3 := CreateAuthID(key, time.Now().Unix()) res2, err2 := AuthDecoder.Match(authid3) assert.Equal(t, "Demo User", res2) assert.Nil(t, err2) } func TestCreateAuthIDAndDecodeSuperMassive(t *testing.T) { key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") authid := CreateAuthID(key, time.Now().Unix()) fmt.Println(key) fmt.Println(authid) AuthDecoder := NewAuthIDDecoderHolder() var keyw [16]byte copy(keyw[:], key) AuthDecoder.AddUser(keyw, "Demo User") res, err := AuthDecoder.Match(authid) fmt.Println(res) fmt.Println(err) assert.Equal(t, "Demo User", res) assert.Nil(t, err) for i := 0; i <= 1000000; i++ { key2 := KDF16([]byte("Demo Key for Auth ID Test2"), "Demo Path for Auth ID Test", strconv.Itoa(i)) var keyw2 [16]byte copy(keyw2[:], key2) AuthDecoder.AddUser(keyw2, "Demo User"+strconv.Itoa(i)) } authid3 := CreateAuthID(key, time.Now().Unix()) before := time.Now() res2, err2 := AuthDecoder.Match(authid3) after := time.Now() assert.Equal(t, "Demo User", res2) assert.Nil(t, err2) fmt.Println(after.Sub(before).Seconds()) } ================================================ FILE: proxy/vmess/aead/consts.go ================================================ package aead const KDFSaltConst_AuthIDEncryptionKey = "AES Auth ID Encryption" const KDFSaltConst_AEADRespHeaderLenKey = "AEAD Resp Header Len Key" const KDFSaltConst_AEADRespHeaderLenIV = "AEAD Resp Header Len IV" const KDFSaltConst_AEADRespHeaderPayloadKey = "AEAD Resp Header Key" const KDFSaltConst_AEADRespHeaderPayloadIV = "AEAD Resp Header IV" const KDFSaltConst_VMessAEADKDF = "VMess AEAD KDF" const KDFSaltConst_VMessHeaderPayloadAEADKey = "VMess Header AEAD Key" const KDFSaltConst_VMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce" const KDFSaltConst_VMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length" const KDFSaltConst_VMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length" ================================================ FILE: proxy/vmess/aead/encrypt.go ================================================ package aead import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/binary" "io" "time" "v2ray.com/core/common" ) func SealVMessAEADHeader(key [16]byte, data []byte) []byte { generatedAuthID := CreateAuthID(key[:], time.Now().Unix()) connectionNonce := make([]byte, 8) if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil { panic(err.Error()) } aeadPayloadLengthSerializeBuffer := bytes.NewBuffer(nil) headerPayloadDataLen := uint16(len(data)) common.Must(binary.Write(aeadPayloadLengthSerializeBuffer, binary.BigEndian, headerPayloadDataLen)) aeadPayloadLengthSerializedByte := aeadPayloadLengthSerializeBuffer.Bytes() var payloadHeaderLengthAEADEncrypted []byte { payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce)) payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12] payloadHeaderLengthAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey) if err != nil { panic(err.Error()) } payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderLengthAEADAESBlock) if err != nil { panic(err.Error()) } payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:]) } var payloadHeaderAEADEncrypted []byte { payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce)) payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12] payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) if err != nil { panic(err.Error()) } payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) if err != nil { panic(err.Error()) } payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) } var outputBuffer = bytes.NewBuffer(nil) common.Must2(outputBuffer.Write(generatedAuthID[:])) //16 common.Must2(outputBuffer.Write(payloadHeaderLengthAEADEncrypted)) //2+16 common.Must2(outputBuffer.Write(connectionNonce)) //8 common.Must2(outputBuffer.Write(payloadHeaderAEADEncrypted)) return outputBuffer.Bytes() } func OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte, bool, error, int) { var payloadHeaderLengthAEADEncrypted [18]byte var nonce [8]byte var bytesRead int authidCheckValueReadBytesCounts, err := io.ReadFull(data, payloadHeaderLengthAEADEncrypted[:]) bytesRead += authidCheckValueReadBytesCounts if err != nil { return nil, false, err, bytesRead } nonceReadBytesCounts, err := io.ReadFull(data, nonce[:]) bytesRead += nonceReadBytesCounts if err != nil { return nil, false, err, bytesRead } //Decrypt Length var decryptedAEADHeaderLengthPayloadResult []byte { payloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADKey, string(authid[:]), string(nonce[:])) payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadLengthAEADIV, string(authid[:]), string(nonce[:]))[:12] payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey) if err != nil { panic(err.Error()) } payloadHeaderLengthAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) if err != nil { panic(err.Error()) } decryptedAEADHeaderLengthPayload, erropenAEAD := payloadHeaderLengthAEAD.Open(nil, payloadHeaderLengthAEADNonce, payloadHeaderLengthAEADEncrypted[:], authid[:]) if erropenAEAD != nil { return nil, true, erropenAEAD, bytesRead } decryptedAEADHeaderLengthPayloadResult = decryptedAEADHeaderLengthPayload } var length uint16 common.Must(binary.Read(bytes.NewReader(decryptedAEADHeaderLengthPayloadResult[:]), binary.BigEndian, &length)) var decryptedAEADHeaderPayloadR []byte var payloadHeaderAEADEncryptedReadedBytesCounts int { payloadHeaderAEADKey := KDF16(key[:], KDFSaltConst_VMessHeaderPayloadAEADKey, string(authid[:]), string(nonce[:])) payloadHeaderAEADNonce := KDF(key[:], KDFSaltConst_VMessHeaderPayloadAEADIV, string(authid[:]), string(nonce[:]))[:12] //16 == AEAD Tag size payloadHeaderAEADEncrypted := make([]byte, length+16) payloadHeaderAEADEncryptedReadedBytesCounts, err = io.ReadFull(data, payloadHeaderAEADEncrypted) bytesRead += payloadHeaderAEADEncryptedReadedBytesCounts if err != nil { return nil, false, err, bytesRead } payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey) if err != nil { panic(err.Error()) } payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock) if err != nil { panic(err.Error()) } decryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:]) if erropenAEAD != nil { return nil, true, erropenAEAD, bytesRead } decryptedAEADHeaderPayloadR = decryptedAEADHeaderPayload } return decryptedAEADHeaderPayloadR, false, nil, bytesRead } ================================================ FILE: proxy/vmess/aead/encrypt_test.go ================================================ package aead import ( "bytes" "fmt" "io" "testing" "github.com/stretchr/testify/assert" ) func TestOpenVMessAEADHeader(t *testing.T) { TestHeader := []byte("Test Header") key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") var keyw [16]byte copy(keyw[:], key) sealed := SealVMessAEADHeader(keyw, TestHeader) var AEADR = bytes.NewReader(sealed) var authid [16]byte io.ReadFull(AEADR, authid[:]) out, _, err, _ := OpenVMessAEADHeader(keyw, authid, AEADR) fmt.Println(string(out)) fmt.Println(err) } func TestOpenVMessAEADHeader2(t *testing.T) { TestHeader := []byte("Test Header") key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") var keyw [16]byte copy(keyw[:], key) sealed := SealVMessAEADHeader(keyw, TestHeader) var AEADR = bytes.NewReader(sealed) var authid [16]byte io.ReadFull(AEADR, authid[:]) out, _, err, readen := OpenVMessAEADHeader(keyw, authid, AEADR) assert.Equal(t, len(sealed)-16-AEADR.Len(), readen) assert.Equal(t, string(TestHeader), string(out)) assert.Nil(t, err) } func TestOpenVMessAEADHeader4(t *testing.T) { for i := 0; i <= 60; i++ { TestHeader := []byte("Test Header") key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") var keyw [16]byte copy(keyw[:], key) sealed := SealVMessAEADHeader(keyw, TestHeader) var sealedm [16]byte copy(sealedm[:], sealed) sealed[i] ^= 0xff var AEADR = bytes.NewReader(sealed) var authid [16]byte io.ReadFull(AEADR, authid[:]) out, drain, err, readen := OpenVMessAEADHeader(keyw, authid, AEADR) assert.Equal(t, len(sealed)-16-AEADR.Len(), readen) assert.Equal(t, true, drain) assert.NotNil(t, err) if err == nil { fmt.Println(">") } assert.Nil(t, out) } } func TestOpenVMessAEADHeader4Massive(t *testing.T) { for j := 0; j < 1000; j++ { for i := 0; i <= 60; i++ { TestHeader := []byte("Test Header") key := KDF16([]byte("Demo Key for Auth ID Test"), "Demo Path for Auth ID Test") var keyw [16]byte copy(keyw[:], key) sealed := SealVMessAEADHeader(keyw, TestHeader) var sealedm [16]byte copy(sealedm[:], sealed) sealed[i] ^= 0xff var AEADR = bytes.NewReader(sealed) var authid [16]byte io.ReadFull(AEADR, authid[:]) out, drain, err, readen := OpenVMessAEADHeader(keyw, authid, AEADR) assert.Equal(t, len(sealed)-16-AEADR.Len(), readen) assert.Equal(t, true, drain) assert.NotNil(t, err) if err == nil { fmt.Println(">") } assert.Nil(t, out) } } } ================================================ FILE: proxy/vmess/aead/kdf.go ================================================ package aead import ( "crypto/hmac" "crypto/sha256" "hash" ) func KDF(key []byte, path ...string) []byte { hmacf := hmac.New(func() hash.Hash { return sha256.New() }, []byte(KDFSaltConst_VMessAEADKDF)) for _, v := range path { hmacf = hmac.New(func() hash.Hash { return hmacf }, []byte(v)) } hmacf.Write(key) return hmacf.Sum(nil) } func KDF16(key []byte, path ...string) []byte { r := KDF(key, path...) return r[:16] } ================================================ FILE: proxy/vmess/encoding/auth.go ================================================ package encoding import ( "crypto/md5" "encoding/binary" "hash/fnv" "v2ray.com/core/common" "golang.org/x/crypto/sha3" ) // Authenticate authenticates a byte array using Fnv hash. func Authenticate(b []byte) uint32 { fnv1hash := fnv.New32a() common.Must2(fnv1hash.Write(b)) return fnv1hash.Sum32() } type NoOpAuthenticator struct{} func (NoOpAuthenticator) NonceSize() int { return 0 } func (NoOpAuthenticator) Overhead() int { return 0 } // Seal implements AEAD.Seal(). func (NoOpAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte { return append(dst[:0], plaintext...) } // Open implements AEAD.Open(). func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { return append(dst[:0], ciphertext...), nil } // FnvAuthenticator is an AEAD based on Fnv hash. type FnvAuthenticator struct { } // NonceSize implements AEAD.NonceSize(). func (*FnvAuthenticator) NonceSize() int { return 0 } // Overhead impelements AEAD.Overhead(). func (*FnvAuthenticator) Overhead() int { return 4 } // Seal implements AEAD.Seal(). func (*FnvAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte { dst = append(dst, 0, 0, 0, 0) binary.BigEndian.PutUint32(dst, Authenticate(plaintext)) return append(dst, plaintext...) } // Open implements AEAD.Open(). func (*FnvAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if binary.BigEndian.Uint32(ciphertext[:4]) != Authenticate(ciphertext[4:]) { return dst, newError("invalid authentication") } return append(dst, ciphertext[4:]...), nil } // GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array. func GenerateChacha20Poly1305Key(b []byte) []byte { key := make([]byte, 32) t := md5.Sum(b) copy(key, t[:]) t = md5.Sum(key[:16]) copy(key[16:], t[:]) return key } type ShakeSizeParser struct { shake sha3.ShakeHash buffer [2]byte } func NewShakeSizeParser(nonce []byte) *ShakeSizeParser { shake := sha3.NewShake128() common.Must2(shake.Write(nonce)) return &ShakeSizeParser{ shake: shake, } } func (*ShakeSizeParser) SizeBytes() int32 { return 2 } func (s *ShakeSizeParser) next() uint16 { common.Must2(s.shake.Read(s.buffer[:])) return binary.BigEndian.Uint16(s.buffer[:]) } func (s *ShakeSizeParser) Decode(b []byte) (uint16, error) { mask := s.next() size := binary.BigEndian.Uint16(b) return mask ^ size, nil } func (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte { mask := s.next() binary.BigEndian.PutUint16(b, mask^size) return b[:2] } func (s *ShakeSizeParser) NextPaddingLen() uint16 { return s.next() % 64 } func (s *ShakeSizeParser) MaxPaddingLen() uint16 { return 64 } ================================================ FILE: proxy/vmess/encoding/auth_test.go ================================================ package encoding_test import ( "crypto/rand" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/proxy/vmess/encoding" ) func TestFnvAuth(t *testing.T) { fnvAuth := new(FnvAuthenticator) expectedText := make([]byte, 256) _, err := rand.Read(expectedText) common.Must(err) buffer := make([]byte, 512) b := fnvAuth.Seal(buffer[:0], nil, expectedText, nil) b, err = fnvAuth.Open(buffer[:0], nil, b, nil) common.Must(err) if r := cmp.Diff(b, expectedText); r != "" { t.Error(r) } } ================================================ FILE: proxy/vmess/encoding/client.go ================================================ package encoding import ( "bytes" "context" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/rand" "crypto/sha256" "encoding/binary" "hash" "hash/fnv" "io" "golang.org/x/crypto/chacha20poly1305" "v2ray.com/core/common" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/dice" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/vmess" vmessaead "v2ray.com/core/proxy/vmess/aead" ) func hashTimestamp(h hash.Hash, t protocol.Timestamp) []byte { common.Must2(serial.WriteUint64(h, uint64(t))) common.Must2(serial.WriteUint64(h, uint64(t))) common.Must2(serial.WriteUint64(h, uint64(t))) common.Must2(serial.WriteUint64(h, uint64(t))) return h.Sum(nil) } // ClientSession stores connection session info for VMess client. type ClientSession struct { isAEAD bool idHash protocol.IDHash requestBodyKey [16]byte requestBodyIV [16]byte responseBodyKey [16]byte responseBodyIV [16]byte responseReader io.Reader responseHeader byte } // NewClientSession creates a new ClientSession. func NewClientSession(isAEAD bool, idHash protocol.IDHash, ctx context.Context) *ClientSession { session := &ClientSession{ isAEAD: isAEAD, idHash: idHash, } randomBytes := make([]byte, 33) // 16 + 16 + 1 common.Must2(rand.Read(randomBytes)) copy(session.requestBodyKey[:], randomBytes[:16]) copy(session.requestBodyIV[:], randomBytes[16:32]) session.responseHeader = randomBytes[32] if !session.isAEAD { session.responseBodyKey = md5.Sum(session.requestBodyKey[:]) session.responseBodyIV = md5.Sum(session.requestBodyIV[:]) } else { BodyKey := sha256.Sum256(session.requestBodyKey[:]) copy(session.responseBodyKey[:], BodyKey[:16]) BodyIV := sha256.Sum256(session.requestBodyIV[:]) copy(session.responseBodyIV[:], BodyIV[:16]) } return session } func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error { timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)() account := header.User.Account.(*vmess.MemoryAccount) if !c.isAEAD { idHash := c.idHash(account.AnyValidID().Bytes()) common.Must2(serial.WriteUint64(idHash, uint64(timestamp))) common.Must2(writer.Write(idHash.Sum(nil))) } buffer := buf.New() defer buffer.Release() common.Must(buffer.WriteByte(Version)) common.Must2(buffer.Write(c.requestBodyIV[:])) common.Must2(buffer.Write(c.requestBodyKey[:])) common.Must(buffer.WriteByte(c.responseHeader)) common.Must(buffer.WriteByte(byte(header.Option))) padingLen := dice.Roll(16) security := byte(padingLen<<4) | byte(header.Security) common.Must2(buffer.Write([]byte{security, byte(0), byte(header.Command)})) if header.Command != protocol.RequestCommandMux { if err := addrParser.WriteAddressPort(buffer, header.Address, header.Port); err != nil { return newError("failed to writer address and port").Base(err) } } if padingLen > 0 { common.Must2(buffer.ReadFullFrom(rand.Reader, int32(padingLen))) } { fnv1a := fnv.New32a() common.Must2(fnv1a.Write(buffer.Bytes())) hashBytes := buffer.Extend(int32(fnv1a.Size())) fnv1a.Sum(hashBytes[:0]) } if !c.isAEAD { iv := hashTimestamp(md5.New(), timestamp) aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv[:]) aesStream.XORKeyStream(buffer.Bytes(), buffer.Bytes()) common.Must2(writer.Write(buffer.Bytes())) } else { var fixedLengthCmdKey [16]byte copy(fixedLengthCmdKey[:], account.ID.CmdKey()) vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes()) common.Must2(io.Copy(writer, bytes.NewReader(vmessout))) } return nil } func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.requestBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { padding = sizeParser.(crypto.PaddingLengthGenerator) } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamWriter(sizeParser, writer) } auth := &crypto.AEADAuthenticator{ AEAD: new(NoOpAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) } return buf.NewWriter(writer) case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:]) cryptionWriter := crypto.NewCryptionWriter(aesStream, writer) if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding) } return &buf.SequentialWriter{Writer: cryptionWriter} case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.requestBodyKey[:]) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:])) common.Must(err) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) default: panic("Unknown security type.") } } func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) { if !c.isAEAD { aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) c.responseReader = crypto.NewCryptionReader(aesStream, reader) } else { aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderLenKey) aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderLenIV)[:12] aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) var aeadEncryptedResponseHeaderLength [18]byte var decryptedResponseHeaderLength int var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16 if _, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil { return nil, newError("Unable to Read Header Len").Base(err) } if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil { return nil, newError("Failed To Decrypt Length").Base(err) } else { common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer)) decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer) } aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadKey) aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadIV)[:12] aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) if _, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil { return nil, newError("Unable to Read Header Data").Base(err) } if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil { return nil, newError("Failed To Decrypt Payload").Base(err) } else { c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer) } } buffer := buf.StackNew() defer buffer.Release() if _, err := buffer.ReadFullFrom(c.responseReader, 4); err != nil { return nil, newError("failed to read response header").Base(err).AtWarning() } if buffer.Byte(0) != c.responseHeader { return nil, newError("unexpected response header. Expecting ", int(c.responseHeader), " but actually ", int(buffer.Byte(0))) } header := &protocol.ResponseHeader{ Option: bitmask.Byte(buffer.Byte(1)), } if buffer.Byte(2) != 0 { cmdID := buffer.Byte(2) dataLen := int32(buffer.Byte(3)) buffer.Clear() if _, err := buffer.ReadFullFrom(c.responseReader, dataLen); err != nil { return nil, newError("failed to read response command").Base(err) } command, err := UnmarshalCommand(cmdID, buffer.Bytes()) if err == nil { header.Command = command } } if c.isAEAD { aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) c.responseReader = crypto.NewCryptionReader(aesStream, reader) } return header, nil } func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.responseBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { padding = sizeParser.(crypto.PaddingLengthGenerator) } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamReader(sizeParser, reader) } auth := &crypto.AEADAuthenticator{ AEAD: new(NoOpAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) } return buf.NewReader(reader) case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding) } return buf.NewReader(c.responseReader) case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.responseBodyKey[:]) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:])) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) default: panic("Unknown security type.") } } func GenerateChunkNonce(nonce []byte, size uint32) crypto.BytesGenerator { c := append([]byte(nil), nonce...) count := uint16(0) return func() []byte { binary.BigEndian.PutUint16(c, count) count++ return c[:size] } } ================================================ FILE: proxy/vmess/encoding/commands.go ================================================ package encoding import ( "encoding/binary" "io" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" ) var ( ErrCommandTypeMismatch = newError("Command type mismatch.") ErrUnknownCommand = newError("Unknown command.") ErrCommandTooLarge = newError("Command too large.") ) func MarshalCommand(command interface{}, writer io.Writer) error { if command == nil { return ErrUnknownCommand } var cmdID byte var factory CommandFactory switch command.(type) { case *protocol.CommandSwitchAccount: factory = new(CommandSwitchAccountFactory) cmdID = 1 default: return ErrUnknownCommand } buffer := buf.New() defer buffer.Release() err := factory.Marshal(command, buffer) if err != nil { return err } auth := Authenticate(buffer.Bytes()) length := buffer.Len() + 4 if length > 255 { return ErrCommandTooLarge } common.Must2(writer.Write([]byte{cmdID, byte(length), byte(auth >> 24), byte(auth >> 16), byte(auth >> 8), byte(auth)})) common.Must2(writer.Write(buffer.Bytes())) return nil } func UnmarshalCommand(cmdID byte, data []byte) (protocol.ResponseCommand, error) { if len(data) <= 4 { return nil, newError("insufficient length") } expectedAuth := Authenticate(data[4:]) actualAuth := binary.BigEndian.Uint32(data[:4]) if expectedAuth != actualAuth { return nil, newError("invalid auth") } var factory CommandFactory switch cmdID { case 1: factory = new(CommandSwitchAccountFactory) default: return nil, ErrUnknownCommand } return factory.Unmarshal(data[4:]) } type CommandFactory interface { Marshal(command interface{}, writer io.Writer) error Unmarshal(data []byte) (interface{}, error) } type CommandSwitchAccountFactory struct { } func (f *CommandSwitchAccountFactory) Marshal(command interface{}, writer io.Writer) error { cmd, ok := command.(*protocol.CommandSwitchAccount) if !ok { return ErrCommandTypeMismatch } hostStr := "" if cmd.Host != nil { hostStr = cmd.Host.String() } common.Must2(writer.Write([]byte{byte(len(hostStr))})) if len(hostStr) > 0 { common.Must2(writer.Write([]byte(hostStr))) } common.Must2(serial.WriteUint16(writer, cmd.Port.Value())) idBytes := cmd.ID.Bytes() common.Must2(writer.Write(idBytes)) common.Must2(serial.WriteUint16(writer, cmd.AlterIds)) common.Must2(writer.Write([]byte{byte(cmd.Level)})) common.Must2(writer.Write([]byte{cmd.ValidMin})) return nil } func (f *CommandSwitchAccountFactory) Unmarshal(data []byte) (interface{}, error) { cmd := new(protocol.CommandSwitchAccount) if len(data) == 0 { return nil, newError("insufficient length.") } lenHost := int(data[0]) if len(data) < lenHost+1 { return nil, newError("insufficient length.") } if lenHost > 0 { cmd.Host = net.ParseAddress(string(data[1 : 1+lenHost])) } portStart := 1 + lenHost if len(data) < portStart+2 { return nil, newError("insufficient length.") } cmd.Port = net.PortFromBytes(data[portStart : portStart+2]) idStart := portStart + 2 if len(data) < idStart+16 { return nil, newError("insufficient length.") } cmd.ID, _ = uuid.ParseBytes(data[idStart : idStart+16]) alterIDStart := idStart + 16 if len(data) < alterIDStart+2 { return nil, newError("insufficient length.") } cmd.AlterIds = binary.BigEndian.Uint16(data[alterIDStart : alterIDStart+2]) levelStart := alterIDStart + 2 if len(data) < levelStart+1 { return nil, newError("insufficient length.") } cmd.Level = uint32(data[levelStart]) timeStart := levelStart + 1 if len(data) < timeStart { return nil, newError("insufficient length.") } cmd.ValidMin = data[timeStart] return cmd, nil } ================================================ FILE: proxy/vmess/encoding/commands_test.go ================================================ package encoding_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" . "v2ray.com/core/proxy/vmess/encoding" ) func TestSwitchAccount(t *testing.T) { sa := &protocol.CommandSwitchAccount{ Port: 1234, ID: uuid.New(), AlterIds: 1024, Level: 128, ValidMin: 16, } buffer := buf.New() common.Must(MarshalCommand(sa, buffer)) cmd, err := UnmarshalCommand(1, buffer.BytesFrom(2)) common.Must(err) sa2, ok := cmd.(*protocol.CommandSwitchAccount) if !ok { t.Fatal("failed to convert command to CommandSwitchAccount") } if r := cmp.Diff(sa2, sa); r != "" { t.Error(r) } } ================================================ FILE: proxy/vmess/encoding/encoding.go ================================================ package encoding import ( "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" ) //go:generate go run v2ray.com/core/common/errors/errorgen const ( Version = byte(1) ) var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6), protocol.PortThenAddress(), ) ================================================ FILE: proxy/vmess/encoding/encoding_test.go ================================================ package encoding_test import ( "context" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/vmess" . "v2ray.com/core/proxy/vmess/encoding" ) func toAccount(a *vmess.Account) protocol.Account { account, err := a.AsAccount() common.Must(err) return account } func TestRequestSerialization(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2ray.com", } id := uuid.New() account := &vmess.Account{ Id: id.String(), AlterId: 0, } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: 1, User: user, Command: protocol.RequestCommandTCP, Address: net.DomainAddress("www.v2ray.com"), Port: net.Port(443), Security: protocol.SecurityType_AES128_GCM, } buffer := buf.New() client := NewClientSession(true, protocol.DefaultIDHash, context.TODO()) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() buffer2.Write(buffer.Bytes()) sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) userValidator.Add(user) defer common.Close(userValidator) server := NewServerSession(userValidator, sessionHistory) actualRequest, err := server.DecodeRequestHeader(buffer) common.Must(err) if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { t.Error(r) } _, err = server.DecodeRequestHeader(buffer2) // anti replay attack if err == nil { t.Error("nil error") } } func TestInvalidRequest(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2ray.com", } id := uuid.New() account := &vmess.Account{ Id: id.String(), AlterId: 0, } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: 1, User: user, Command: protocol.RequestCommand(100), Address: net.DomainAddress("www.v2ray.com"), Port: net.Port(443), Security: protocol.SecurityType_AES128_GCM, } buffer := buf.New() client := NewClientSession(true, protocol.DefaultIDHash, context.TODO()) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() buffer2.Write(buffer.Bytes()) sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) userValidator.Add(user) defer common.Close(userValidator) server := NewServerSession(userValidator, sessionHistory) _, err := server.DecodeRequestHeader(buffer) if err == nil { t.Error("nil error") } } func TestMuxRequest(t *testing.T) { user := &protocol.MemoryUser{ Level: 0, Email: "test@v2ray.com", } id := uuid.New() account := &vmess.Account{ Id: id.String(), AlterId: 0, } user.Account = toAccount(account) expectedRequest := &protocol.RequestHeader{ Version: 1, User: user, Command: protocol.RequestCommandMux, Security: protocol.SecurityType_AES128_GCM, Address: net.DomainAddress("v1.mux.cool"), } buffer := buf.New() client := NewClientSession(true, protocol.DefaultIDHash, context.TODO()) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() buffer2.Write(buffer.Bytes()) sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) userValidator.Add(user) defer common.Close(userValidator) server := NewServerSession(userValidator, sessionHistory) actualRequest, err := server.DecodeRequestHeader(buffer) common.Must(err) if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { t.Error(r) } } ================================================ FILE: proxy/vmess/encoding/errors.generated.go ================================================ package encoding import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vmess/encoding/server.go ================================================ package encoding import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/sha256" "encoding/binary" "hash/fnv" "io" "io/ioutil" "sync" "time" "golang.org/x/crypto/chacha20poly1305" "v2ray.com/core/common" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/dice" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/task" "v2ray.com/core/proxy/vmess" vmessaead "v2ray.com/core/proxy/vmess/aead" ) type sessionId struct { user [16]byte key [16]byte nonce [16]byte } // SessionHistory keeps track of historical session ids, to prevent replay attacks. type SessionHistory struct { sync.RWMutex cache map[sessionId]time.Time task *task.Periodic } // NewSessionHistory creates a new SessionHistory object. func NewSessionHistory() *SessionHistory { h := &SessionHistory{ cache: make(map[sessionId]time.Time, 128), } h.task = &task.Periodic{ Interval: time.Second * 30, Execute: h.removeExpiredEntries, } return h } // Close implements common.Closable. func (h *SessionHistory) Close() error { return h.task.Close() } func (h *SessionHistory) addIfNotExits(session sessionId) bool { h.Lock() if expire, found := h.cache[session]; found && expire.After(time.Now()) { h.Unlock() return false } h.cache[session] = time.Now().Add(time.Minute * 3) h.Unlock() common.Must(h.task.Start()) return true } func (h *SessionHistory) removeExpiredEntries() error { now := time.Now() h.Lock() defer h.Unlock() if len(h.cache) == 0 { return newError("nothing to do") } for session, expire := range h.cache { if expire.Before(now) { delete(h.cache, session) } } if len(h.cache) == 0 { h.cache = make(map[sessionId]time.Time, 128) } return nil } // ServerSession keeps information for a session in VMess server. type ServerSession struct { userValidator *vmess.TimedUserValidator sessionHistory *SessionHistory requestBodyKey [16]byte requestBodyIV [16]byte responseBodyKey [16]byte responseBodyIV [16]byte responseWriter io.Writer responseHeader byte isAEADRequest bool isAEADForced bool } // NewServerSession creates a new ServerSession, using the given UserValidator. // The ServerSession instance doesn't take ownership of the validator. func NewServerSession(validator *vmess.TimedUserValidator, sessionHistory *SessionHistory) *ServerSession { return &ServerSession{ userValidator: validator, sessionHistory: sessionHistory, } } func parseSecurityType(b byte) protocol.SecurityType { if _, f := protocol.SecurityType_name[int32(b)]; f { st := protocol.SecurityType(b) // For backward compatibility. if st == protocol.SecurityType_UNKNOWN { st = protocol.SecurityType_LEGACY } return st } return protocol.SecurityType_UNKNOWN } // DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream. func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { buffer := buf.New() behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed())) BaseDrainSize := behaviorRand.Roll(3266) RandDrainMax := behaviorRand.Roll(64) + 1 RandDrainRolled := dice.Roll(RandDrainMax) DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled readSizeRemain := DrainSize drainConnection := func(e error) error { //We read a deterministic generated length of data before closing the connection to offset padding read pattern readSizeRemain -= int(buffer.Len()) if readSizeRemain > 0 { err := s.DrainConnN(reader, readSizeRemain) if err != nil { return newError("failed to drain connection DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(err).Base(e) } return newError("connection drained DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(e) } return e } defer func() { buffer.Release() }() if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil { return nil, newError("failed to read request header").Base(err) } var decryptor io.Reader var vmessAccount *vmess.MemoryAccount user, foundAEAD, errorAEAD := s.userValidator.GetAEAD(buffer.Bytes()) var fixedSizeAuthID [16]byte copy(fixedSizeAuthID[:], buffer.Bytes()) if foundAEAD { vmessAccount = user.Account.(*vmess.MemoryAccount) var fixedSizeCmdKey [16]byte copy(fixedSizeCmdKey[:], vmessAccount.ID.CmdKey()) aeadData, shouldDrain, errorReason, bytesRead := vmessaead.OpenVMessAEADHeader(fixedSizeCmdKey, fixedSizeAuthID, reader) if errorReason != nil { if shouldDrain { readSizeRemain -= bytesRead return nil, drainConnection(newError("AEAD read failed").Base(errorReason)) } else { return nil, drainConnection(newError("AEAD read failed, drain skiped").Base(errorReason)) } } decryptor = bytes.NewReader(aeadData) s.isAEADRequest = true } else if !s.isAEADForced && errorAEAD == vmessaead.ErrNotFound { userLegacy, timestamp, valid, userValidationError := s.userValidator.Get(buffer.Bytes()) if !valid || userValidationError != nil { return nil, drainConnection(newError("invalid user").Base(userValidationError)) } user = userLegacy iv := hashTimestamp(md5.New(), timestamp) vmessAccount = userLegacy.Account.(*vmess.MemoryAccount) aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:]) decryptor = crypto.NewCryptionReader(aesStream, reader) } else { return nil, drainConnection(newError("invalid user").Base(errorAEAD)) } readSizeRemain -= int(buffer.Len()) buffer.Clear() if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil { return nil, newError("failed to read request header").Base(err) } request := &protocol.RequestHeader{ User: user, Version: buffer.Byte(0), } copy(s.requestBodyIV[:], buffer.BytesRange(1, 17)) // 16 bytes copy(s.requestBodyKey[:], buffer.BytesRange(17, 33)) // 16 bytes var sid sessionId copy(sid.user[:], vmessAccount.ID.Bytes()) sid.key = s.requestBodyKey sid.nonce = s.requestBodyIV if !s.sessionHistory.addIfNotExits(sid) { if !s.isAEADRequest { drainErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) if drainErr != nil { return nil, drainConnection(newError("duplicated session id, possibly under replay attack, and failed to taint userHash").Base(drainErr)) } return nil, drainConnection(newError("duplicated session id, possibly under replay attack, userHash tainted")) } else { return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request") } } s.responseHeader = buffer.Byte(33) // 1 byte request.Option = bitmask.Byte(buffer.Byte(34)) // 1 byte padingLen := int(buffer.Byte(35) >> 4) request.Security = parseSecurityType(buffer.Byte(35) & 0x0F) // 1 bytes reserved request.Command = protocol.RequestCommand(buffer.Byte(37)) switch request.Command { case protocol.RequestCommandMux: request.Address = net.DomainAddress("v1.mux.cool") request.Port = 0 case protocol.RequestCommandTCP, protocol.RequestCommandUDP: if addr, port, err := addrParser.ReadAddressPort(buffer, decryptor); err == nil { request.Address = addr request.Port = port } } if padingLen > 0 { if _, err := buffer.ReadFullFrom(decryptor, int32(padingLen)); err != nil { if !s.isAEADRequest { burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) if burnErr != nil { return nil, newError("failed to read padding, failed to taint userHash").Base(burnErr).Base(err) } return nil, newError("failed to read padding, userHash tainted").Base(err) } return nil, newError("failed to read padding").Base(err) } } if _, err := buffer.ReadFullFrom(decryptor, 4); err != nil { if !s.isAEADRequest { burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) if burnErr != nil { return nil, newError("failed to read checksum, failed to taint userHash").Base(burnErr).Base(err) } return nil, newError("failed to read checksum, userHash tainted").Base(err) } return nil, newError("failed to read checksum").Base(err) } fnv1a := fnv.New32a() common.Must2(fnv1a.Write(buffer.BytesTo(-4))) actualHash := fnv1a.Sum32() expectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4)) if actualHash != expectedHash { if !s.isAEADRequest { Autherr := newError("invalid auth, legacy userHash tainted") burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) if burnErr != nil { Autherr = newError("invalid auth, can't taint legacy userHash").Base(burnErr) } //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523 return nil, drainConnection(Autherr) } else { return nil, newError("invalid auth, but this is a AEAD request") } } if request.Address == nil { return nil, newError("invalid remote address") } if request.Security == protocol.SecurityType_UNKNOWN || request.Security == protocol.SecurityType_AUTO { return nil, newError("unknown security type: ", request.Security) } return request, nil } // DecodeRequestBody returns Reader from which caller can fetch decrypted body. func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.requestBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { padding = sizeParser.(crypto.PaddingLengthGenerator) } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamReader(sizeParser, reader) } auth := &crypto.AEADAuthenticator{ AEAD: new(NoOpAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) } return buf.NewReader(reader) case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:]) cryptionReader := crypto.NewCryptionReader(aesStream, reader) if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding) } return buf.NewReader(cryptionReader) case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.requestBodyKey[:]) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:])) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) default: panic("Unknown security type.") } } // EncodeResponseHeader writes encoded response header into the given writer. func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) { var encryptionWriter io.Writer if !s.isAEADRequest { s.responseBodyKey = md5.Sum(s.requestBodyKey[:]) s.responseBodyIV = md5.Sum(s.requestBodyIV[:]) } else { BodyKey := sha256.Sum256(s.requestBodyKey[:]) copy(s.responseBodyKey[:], BodyKey[:16]) BodyIV := sha256.Sum256(s.requestBodyIV[:]) copy(s.responseBodyIV[:], BodyIV[:16]) } aesStream := crypto.NewAesEncryptionStream(s.responseBodyKey[:], s.responseBodyIV[:]) encryptionWriter = crypto.NewCryptionWriter(aesStream, writer) s.responseWriter = encryptionWriter aeadEncryptedHeaderBuffer := bytes.NewBuffer(nil) if s.isAEADRequest { encryptionWriter = aeadEncryptedHeaderBuffer } common.Must2(encryptionWriter.Write([]byte{s.responseHeader, byte(header.Option)})) err := MarshalCommand(header.Command, encryptionWriter) if err != nil { common.Must2(encryptionWriter.Write([]byte{0x00, 0x00})) } if s.isAEADRequest { aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderLenKey) aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderLenIV)[:12] aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil) decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len()) common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer)) AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil) common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength))) aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadKey) aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConst_AEADRespHeaderPayloadIV)[:12] aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil) common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload))) } } // EncodeResponseBody returns a Writer that auto-encrypt content written by caller. func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.responseBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { padding = sizeParser.(crypto.PaddingLengthGenerator) } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamWriter(sizeParser, writer) } auth := &crypto.AEADAuthenticator{ AEAD: new(NoOpAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) } return buf.NewWriter(writer) case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding) } return &buf.SequentialWriter{Writer: s.responseWriter} case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.responseBodyKey[:]) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:])) auth := &crypto.AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) default: panic("Unknown security type.") } } func (s *ServerSession) DrainConnN(reader io.Reader, n int) error { _, err := io.CopyN(ioutil.Discard, reader, int64(n)) return err } ================================================ FILE: proxy/vmess/errors.generated.go ================================================ package vmess import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vmess/inbound/config.go ================================================ // +build !confonly package inbound // GetDefaultValue returns default settings of DefaultConfig. func (c *Config) GetDefaultValue() *DefaultConfig { if c.GetDefault() == nil { return &DefaultConfig{ AlterId: 32, Level: 0, } } return c.Default } ================================================ FILE: proxy/vmess/inbound/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vmess/inbound/config.proto package inbound import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type DetourConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` } func (x *DetourConfig) Reset() { *x = DetourConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DetourConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*DetourConfig) ProtoMessage() {} func (x *DetourConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DetourConfig.ProtoReflect.Descriptor instead. func (*DetourConfig) Descriptor() ([]byte, []int) { return file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{0} } func (x *DetourConfig) GetTo() string { if x != nil { return x.To } return "" } type DefaultConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AlterId uint32 `protobuf:"varint,1,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"` Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` } func (x *DefaultConfig) Reset() { *x = DefaultConfig{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultConfig) ProtoMessage() {} func (x *DefaultConfig) ProtoReflect() protoreflect.Message { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultConfig.ProtoReflect.Descriptor instead. func (*DefaultConfig) Descriptor() ([]byte, []int) { return file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{1} } func (x *DefaultConfig) GetAlterId() uint32 { if x != nil { return x.AlterId } return 0 } func (x *DefaultConfig) GetLevel() uint32 { if x != nil { return x.Level } return 0 } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` Default *DefaultConfig `protobuf:"bytes,2,opt,name=default,proto3" json:"default,omitempty"` Detour *DetourConfig `protobuf:"bytes,3,opt,name=detour,proto3" json:"detour,omitempty"` SecureEncryptionOnly bool `protobuf:"varint,4,opt,name=secure_encryption_only,json=secureEncryptionOnly,proto3" json:"secure_encryption_only,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_vmess_inbound_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{2} } func (x *Config) GetUser() []*protocol.User { if x != nil { return x.User } return nil } func (x *Config) GetDefault() *DefaultConfig { if x != nil { return x.Default } return nil } func (x *Config) GetDetour() *DetourConfig { if x != nil { return x.Detour } return nil } func (x *Config) GetSecureEncryptionOnly() bool { if x != nil { return x.SecureEncryptionOnly } return false } var File_proxy_vmess_inbound_config_proto protoreflect.FileDescriptor var file_proxy_vmess_inbound_config_proto_rawDesc = []byte{ 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1e, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x83, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x44, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x6b, 0x0a, 0x22, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1e, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vmess_inbound_config_proto_rawDescOnce sync.Once file_proxy_vmess_inbound_config_proto_rawDescData = file_proxy_vmess_inbound_config_proto_rawDesc ) func file_proxy_vmess_inbound_config_proto_rawDescGZIP() []byte { file_proxy_vmess_inbound_config_proto_rawDescOnce.Do(func() { file_proxy_vmess_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vmess_inbound_config_proto_rawDescData) }) return file_proxy_vmess_inbound_config_proto_rawDescData } var file_proxy_vmess_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_vmess_inbound_config_proto_goTypes = []interface{}{ (*DetourConfig)(nil), // 0: v2ray.core.proxy.vmess.inbound.DetourConfig (*DefaultConfig)(nil), // 1: v2ray.core.proxy.vmess.inbound.DefaultConfig (*Config)(nil), // 2: v2ray.core.proxy.vmess.inbound.Config (*protocol.User)(nil), // 3: v2ray.core.common.protocol.User } var file_proxy_vmess_inbound_config_proto_depIdxs = []int32{ 3, // 0: v2ray.core.proxy.vmess.inbound.Config.user:type_name -> v2ray.core.common.protocol.User 1, // 1: v2ray.core.proxy.vmess.inbound.Config.default:type_name -> v2ray.core.proxy.vmess.inbound.DefaultConfig 0, // 2: v2ray.core.proxy.vmess.inbound.Config.detour:type_name -> v2ray.core.proxy.vmess.inbound.DetourConfig 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_proxy_vmess_inbound_config_proto_init() } func file_proxy_vmess_inbound_config_proto_init() { if File_proxy_vmess_inbound_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vmess_inbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DetourConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_vmess_inbound_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_proxy_vmess_inbound_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vmess_inbound_config_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vmess_inbound_config_proto_goTypes, DependencyIndexes: file_proxy_vmess_inbound_config_proto_depIdxs, MessageInfos: file_proxy_vmess_inbound_config_proto_msgTypes, }.Build() File_proxy_vmess_inbound_config_proto = out.File file_proxy_vmess_inbound_config_proto_rawDesc = nil file_proxy_vmess_inbound_config_proto_goTypes = nil file_proxy_vmess_inbound_config_proto_depIdxs = nil } ================================================ FILE: proxy/vmess/inbound/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vmess.inbound; option csharp_namespace = "V2Ray.Core.Proxy.Vmess.Inbound"; option go_package = "v2ray.com/core/proxy/vmess/inbound"; option java_package = "com.v2ray.core.proxy.vmess.inbound"; option java_multiple_files = true; import "common/protocol/user.proto"; message DetourConfig { string to = 1; } message DefaultConfig { uint32 alter_id = 1; uint32 level = 2; } message Config { repeated v2ray.core.common.protocol.User user = 1; DefaultConfig default = 2; DetourConfig detour = 3; bool secure_encryption_only = 4; } ================================================ FILE: proxy/vmess/inbound/errors.generated.go ================================================ package inbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vmess/inbound/inbound.go ================================================ // +build !confonly package inbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "io" "strings" "sync" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/common/uuid" feature_inbound "v2ray.com/core/features/inbound" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/encoding" "v2ray.com/core/transport/internet" ) type userByEmail struct { sync.Mutex cache map[string]*protocol.MemoryUser defaultLevel uint32 defaultAlterIDs uint16 } func newUserByEmail(config *DefaultConfig) *userByEmail { return &userByEmail{ cache: make(map[string]*protocol.MemoryUser), defaultLevel: config.Level, defaultAlterIDs: uint16(config.AlterId), } } func (v *userByEmail) addNoLock(u *protocol.MemoryUser) bool { email := strings.ToLower(u.Email) _, found := v.cache[email] if found { return false } v.cache[email] = u return true } func (v *userByEmail) Add(u *protocol.MemoryUser) bool { v.Lock() defer v.Unlock() return v.addNoLock(u) } func (v *userByEmail) Get(email string) (*protocol.MemoryUser, bool) { email = strings.ToLower(email) v.Lock() defer v.Unlock() user, found := v.cache[email] if !found { id := uuid.New() rawAccount := &vmess.Account{ Id: id.String(), AlterId: uint32(v.defaultAlterIDs), } account, err := rawAccount.AsAccount() common.Must(err) user = &protocol.MemoryUser{ Level: v.defaultLevel, Email: email, Account: account, } v.cache[email] = user } return user, found } func (v *userByEmail) Remove(email string) bool { email = strings.ToLower(email) v.Lock() defer v.Unlock() if _, found := v.cache[email]; !found { return false } delete(v.cache, email) return true } // Handler is an inbound connection handler that handles messages in VMess protocol. type Handler struct { policyManager policy.Manager inboundHandlerManager feature_inbound.Manager clients *vmess.TimedUserValidator usersByEmail *userByEmail detours *DetourConfig sessionHistory *encoding.SessionHistory secure bool } // New creates a new VMess inbound handler. func New(ctx context.Context, config *Config) (*Handler, error) { v := core.MustFromContext(ctx) handler := &Handler{ policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), clients: vmess.NewTimedUserValidator(protocol.DefaultIDHash), detours: config.Detour, usersByEmail: newUserByEmail(config.GetDefaultValue()), sessionHistory: encoding.NewSessionHistory(), secure: config.SecureEncryptionOnly, } for _, user := range config.User { mUser, err := user.ToMemoryUser() if err != nil { return nil, newError("failed to get VMess user").Base(err) } if err := handler.AddUser(ctx, mUser); err != nil { return nil, newError("failed to initiate user").Base(err) } } return handler, nil } // Close implements common.Closable. func (h *Handler) Close() error { return errors.Combine( h.clients.Close(), h.sessionHistory.Close(), common.Close(h.usersByEmail)) } // Network implements proxy.Inbound.Network(). func (*Handler) Network() []net.Network { return []net.Network{net.Network_TCP} } func (h *Handler) GetUser(email string) *protocol.MemoryUser { user, existing := h.usersByEmail.Get(email) if !existing { h.clients.Add(user) } return user } func (h *Handler) AddUser(ctx context.Context, user *protocol.MemoryUser) error { if len(user.Email) > 0 && !h.usersByEmail.Add(user) { return newError("User ", user.Email, " already exists.") } return h.clients.Add(user) } func (h *Handler) RemoveUser(ctx context.Context, email string) error { if email == "" { return newError("Email must not be empty.") } if !h.usersByEmail.Remove(email) { return newError("User ", email, " not found.") } h.clients.Remove(email) return nil } func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, response *protocol.ResponseHeader, input buf.Reader, output *buf.BufferedWriter) error { session.EncodeResponseHeader(response, output) bodyWriter := session.EncodeResponseBody(request, output) { // Optimize for small response packet data, err := input.ReadMultiBuffer() if err != nil { return err } if err := bodyWriter.WriteMultiBuffer(data); err != nil { return err } } if err := output.SetBuffered(false); err != nil { return err } if err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil { return err } if request.Option.Has(protocol.RequestOptionChunkStream) { if err := bodyWriter.WriteMultiBuffer(buf.MultiBuffer{}); err != nil { return err } } return nil } func isInsecureEncryption(s protocol.SecurityType) bool { return s == protocol.SecurityType_NONE || s == protocol.SecurityType_LEGACY || s == protocol.SecurityType_UNKNOWN } // Process implements proxy.Inbound.Process(). func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher routing.Dispatcher) error { sessionPolicy := h.policyManager.ForLevel(0) if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { return newError("unable to set read deadline").Base(err).AtWarning() } reader := &buf.BufferedReader{Reader: buf.NewReader(connection)} svrSession := encoding.NewServerSession(h.clients, h.sessionHistory) request, err := svrSession.DecodeRequestHeader(reader) if err != nil { if errors.Cause(err) != io.EOF { log.Record(&log.AccessMessage{ From: connection.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: err, }) err = newError("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo() } return err } if h.secure && isInsecureEncryption(request.Security) { log.Record(&log.AccessMessage{ From: connection.RemoteAddr(), To: "", Status: log.AccessRejected, Reason: "Insecure encryption", Email: request.User.Email, }) return newError("client is using insecure encryption: ", request.Security) } if request.Command != protocol.RequestCommandMux { ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: connection.RemoteAddr(), To: request.Destination(), Status: log.AccessAccepted, Reason: "", Email: request.User.Email, }) } newError("received request for ", request.Destination()).WriteToLog(session.ExportIDToError(ctx)) if err := connection.SetReadDeadline(time.Time{}); err != nil { newError("unable to set back read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) } inbound := session.InboundFromContext(ctx) if inbound == nil { panic("no inbound metadata") } inbound.User = request.User sessionPolicy = h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) link, err := dispatcher.Dispatch(ctx, request.Destination()) if err != nil { return newError("failed to dispatch request to ", request.Destination()).Base(err) } requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) bodyReader := svrSession.DecodeRequestBody(request, reader) if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request").Base(err) } return nil } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) writer := buf.NewBufferedWriter(buf.NewWriter(connection)) defer writer.Flush() response := &protocol.ResponseHeader{ Command: h.generateCommand(ctx, request), } return transferResponse(timer, svrSession, request, response, link.Reader, writer) } var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer)) if err := task.Run(ctx, requestDonePost, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) return newError("connection ends").Base(err) } return nil } func (h *Handler) generateCommand(ctx context.Context, request *protocol.RequestHeader) protocol.ResponseCommand { if h.detours != nil { tag := h.detours.To if h.inboundHandlerManager != nil { handler, err := h.inboundHandlerManager.GetHandler(ctx, tag) if err != nil { newError("failed to get detour handler: ", tag).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) return nil } proxyHandler, port, availableMin := handler.GetRandomInboundProxy() inboundHandler, ok := proxyHandler.(*Handler) if ok && inboundHandler != nil { if availableMin > 255 { availableMin = 255 } newError("pick detour handler for port ", port, " for ", availableMin, " minutes.").AtDebug().WriteToLog(session.ExportIDToError(ctx)) user := inboundHandler.GetUser(request.User.Email) if user == nil { return nil } account := user.Account.(*vmess.MemoryAccount) return &protocol.CommandSwitchAccount{ Port: port, ID: account.ID.UUID(), AlterIds: uint16(len(account.AlterIDs)), Level: user.Level, ValidMin: byte(availableMin), } } } } return nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) } ================================================ FILE: proxy/vmess/outbound/command.go ================================================ // +build !confonly package outbound import ( "time" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/proxy/vmess" ) func (h *Handler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) { rawAccount := &vmess.Account{ Id: cmd.ID.String(), AlterId: uint32(cmd.AlterIds), SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_LEGACY, }, } account, err := rawAccount.AsAccount() common.Must(err) user := &protocol.MemoryUser{ Email: "", Level: cmd.Level, Account: account, } dest := net.TCPDestination(cmd.Host, cmd.Port) until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute) h.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user)) } func (h *Handler) handleCommand(dest net.Destination, cmd protocol.ResponseCommand) { switch typedCommand := cmd.(type) { case *protocol.CommandSwitchAccount: if typedCommand.Host == nil { typedCommand.Host = dest.Address } h.handleSwitchAccount(typedCommand) default: } } ================================================ FILE: proxy/vmess/outbound/config.go ================================================ package outbound ================================================ FILE: proxy/vmess/outbound/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: proxy/vmess/outbound/config.proto package outbound import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Receiver []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=Receiver,proto3" json:"Receiver,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_proxy_vmess_outbound_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_proxy_vmess_outbound_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_proxy_vmess_outbound_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetReceiver() []*protocol.ServerEndpoint { if x != nil { return x.Receiver } return nil } var File_proxy_vmess_outbound_config_proto protoreflect.FileDescriptor var file_proxy_vmess_outbound_config_proto_rawDesc = []byte{ 0x0a, 0x21, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x50, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x42, 0x6e, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x23, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1f, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proxy_vmess_outbound_config_proto_rawDescOnce sync.Once file_proxy_vmess_outbound_config_proto_rawDescData = file_proxy_vmess_outbound_config_proto_rawDesc ) func file_proxy_vmess_outbound_config_proto_rawDescGZIP() []byte { file_proxy_vmess_outbound_config_proto_rawDescOnce.Do(func() { file_proxy_vmess_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_vmess_outbound_config_proto_rawDescData) }) return file_proxy_vmess_outbound_config_proto_rawDescData } var file_proxy_vmess_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_vmess_outbound_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.proxy.vmess.outbound.Config (*protocol.ServerEndpoint)(nil), // 1: v2ray.core.common.protocol.ServerEndpoint } var file_proxy_vmess_outbound_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.proxy.vmess.outbound.Config.Receiver:type_name -> v2ray.core.common.protocol.ServerEndpoint 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_proxy_vmess_outbound_config_proto_init() } func file_proxy_vmess_outbound_config_proto_init() { if File_proxy_vmess_outbound_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proxy_vmess_outbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vmess_outbound_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proxy_vmess_outbound_config_proto_goTypes, DependencyIndexes: file_proxy_vmess_outbound_config_proto_depIdxs, MessageInfos: file_proxy_vmess_outbound_config_proto_msgTypes, }.Build() File_proxy_vmess_outbound_config_proto = out.File file_proxy_vmess_outbound_config_proto_rawDesc = nil file_proxy_vmess_outbound_config_proto_goTypes = nil file_proxy_vmess_outbound_config_proto_depIdxs = nil } ================================================ FILE: proxy/vmess/outbound/config.proto ================================================ syntax = "proto3"; package v2ray.core.proxy.vmess.outbound; option csharp_namespace = "V2Ray.Core.Proxy.Vmess.Outbound"; option go_package = "v2ray.com/core/proxy/vmess/outbound"; option java_package = "com.v2ray.core.proxy.vmess.outbound"; option java_multiple_files = true; import "common/protocol/server_spec.proto"; message Config { repeated v2ray.core.common.protocol.ServerEndpoint Receiver = 1; } ================================================ FILE: proxy/vmess/outbound/errors.generated.go ================================================ package outbound import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: proxy/vmess/outbound/outbound.go ================================================ // +build !confonly package outbound //go:generate go run v2ray.com/core/common/errors/errorgen import ( "context" "time" "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/platform" "v2ray.com/core/common/protocol" "v2ray.com/core/common/retry" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/common/task" "v2ray.com/core/features/policy" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/encoding" "v2ray.com/core/transport" "v2ray.com/core/transport/internet" ) // Handler is an outbound connection handler for VMess protocol. type Handler struct { serverList *protocol.ServerList serverPicker protocol.ServerPicker policyManager policy.Manager } // New creates a new VMess outbound handler. func New(ctx context.Context, config *Config) (*Handler, error) { serverList := protocol.NewServerList() for _, rec := range config.Receiver { s, err := protocol.NewServerSpecFromPB(rec) if err != nil { return nil, newError("failed to parse server spec").Base(err) } serverList.AddServer(s) } v := core.MustFromContext(ctx) handler := &Handler{ serverList: serverList, serverPicker: protocol.NewRoundRobinServerPicker(serverList), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), } return handler, nil } // Process implements proxy.Outbound.Process(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { var rec *protocol.ServerSpec var conn internet.Connection err := retry.ExponentialBackoff(5, 200).On(func() error { rec = h.serverPicker.PickServer() rawConn, err := dialer.Dial(ctx, rec.Destination()) if err != nil { return err } conn = rawConn return nil }) if err != nil { return newError("failed to find an available destination").Base(err).AtWarning() } defer conn.Close() //nolint: errcheck outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified").AtError() } target := outbound.Target newError("tunneling request to ", target, " via ", rec.Destination()).WriteToLog(session.ExportIDToError(ctx)) command := protocol.RequestCommandTCP if target.Network == net.Network_UDP { command = protocol.RequestCommandUDP } if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" { command = protocol.RequestCommandMux } user := rec.PickUser() request := &protocol.RequestHeader{ Version: encoding.Version, User: user, Command: command, Address: target.Address, Port: target.Port, Option: protocol.RequestOptionChunkStream, } account := request.User.Account.(*vmess.MemoryAccount) request.Security = account.Security if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 { request.Option.Set(protocol.RequestOptionChunkMasking) } if shouldEnablePadding(request.Security) && request.Option.Has(protocol.RequestOptionChunkMasking) { request.Option.Set(protocol.RequestOptionGlobalPadding) } input := link.Reader output := link.Writer isAEAD := false if !aead_disabled && len(account.AlterIDs) == 0 { isAEAD = true } session := encoding.NewClientSession(isAEAD, protocol.DefaultIDHash, ctx) sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) writer := buf.NewBufferedWriter(buf.NewWriter(conn)) if err := session.EncodeRequestHeader(request, writer); err != nil { return newError("failed to encode request").Base(err).AtWarning() } bodyWriter := session.EncodeRequestBody(request, writer) if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { return newError("failed to write first payload").Base(err) } if err := writer.SetBuffered(false); err != nil { return err } if err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil { return err } if request.Option.Has(protocol.RequestOptionChunkStream) { if err := bodyWriter.WriteMultiBuffer(buf.MultiBuffer{}); err != nil { return err } } return nil } responseDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) reader := &buf.BufferedReader{Reader: buf.NewReader(conn)} header, err := session.DecodeResponseHeader(reader) if err != nil { return newError("failed to read header").Base(err) } h.handleCommand(rec.Destination(), header.Command) bodyReader := session.DecodeResponseBody(request, reader) return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) } var responseDonePost = task.OnSuccess(responseDone, task.Close(output)) if err := task.Run(ctx, requestDone, responseDonePost); err != nil { return newError("connection ends").Base(err) } return nil } var ( enablePadding = false aead_disabled = false ) func shouldEnablePadding(s protocol.SecurityType) bool { return enablePadding || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) const defaultFlagValue = "NOT_DEFINED_AT_ALL" paddingValue := platform.NewEnvFlag("v2ray.vmess.padding").GetValue(func() string { return defaultFlagValue }) if paddingValue != defaultFlagValue { enablePadding = true } aeadDisabled := platform.NewEnvFlag("v2ray.vmess.aead.disabled").GetValue(func() string { return defaultFlagValue }) if aeadDisabled == "true" { aead_disabled = true } } ================================================ FILE: proxy/vmess/validator.go ================================================ // +build !confonly package vmess import ( "crypto/hmac" "crypto/sha256" "hash" "hash/crc64" "strings" "sync" "sync/atomic" "time" "v2ray.com/core/common" "v2ray.com/core/common/dice" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/task" "v2ray.com/core/proxy/vmess/aead" ) const ( updateInterval = 10 * time.Second cacheDurationSec = 120 ) type user struct { user protocol.MemoryUser lastSec protocol.Timestamp } // TimedUserValidator is a user Validator based on time. type TimedUserValidator struct { sync.RWMutex users []*user userHash map[[16]byte]indexTimePair hasher protocol.IDHash baseTime protocol.Timestamp task *task.Periodic behaviorSeed uint64 behaviorFused bool aeadDecoderHolder *aead.AuthIDDecoderHolder } type indexTimePair struct { user *user timeInc uint32 taintedFuse *uint32 } // NewTimedUserValidator creates a new TimedUserValidator. func NewTimedUserValidator(hasher protocol.IDHash) *TimedUserValidator { tuv := &TimedUserValidator{ users: make([]*user, 0, 16), userHash: make(map[[16]byte]indexTimePair, 1024), hasher: hasher, baseTime: protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2), aeadDecoderHolder: aead.NewAuthIDDecoderHolder(), } tuv.task = &task.Periodic{ Interval: updateInterval, Execute: func() error { tuv.updateUserHash() return nil }, } common.Must(tuv.task.Start()) return tuv } func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, user *user) { var hashValue [16]byte genEndSec := nowSec + cacheDurationSec genHashForID := func(id *protocol.ID) { idHash := v.hasher(id.Bytes()) genBeginSec := user.lastSec if genBeginSec < nowSec-cacheDurationSec { genBeginSec = nowSec - cacheDurationSec } for ts := genBeginSec; ts <= genEndSec; ts++ { common.Must2(serial.WriteUint64(idHash, uint64(ts))) idHash.Sum(hashValue[:0]) idHash.Reset() v.userHash[hashValue] = indexTimePair{ user: user, timeInc: uint32(ts - v.baseTime), taintedFuse: new(uint32), } } } account := user.user.Account.(*MemoryAccount) genHashForID(account.ID) for _, id := range account.AlterIDs { genHashForID(id) } user.lastSec = genEndSec } func (v *TimedUserValidator) removeExpiredHashes(expire uint32) { for key, pair := range v.userHash { if pair.timeInc < expire { delete(v.userHash, key) } } } func (v *TimedUserValidator) updateUserHash() { now := time.Now() nowSec := protocol.Timestamp(now.Unix()) v.Lock() defer v.Unlock() for _, user := range v.users { v.generateNewHashes(nowSec, user) } expire := protocol.Timestamp(now.Unix() - cacheDurationSec) if expire > v.baseTime { v.removeExpiredHashes(uint32(expire - v.baseTime)) } } func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error { v.Lock() defer v.Unlock() nowSec := time.Now().Unix() uu := &user{ user: *u, lastSec: protocol.Timestamp(nowSec - cacheDurationSec), } v.users = append(v.users, uu) v.generateNewHashes(protocol.Timestamp(nowSec), uu) account := uu.user.Account.(*MemoryAccount) if !v.behaviorFused { hashkdf := hmac.New(func() hash.Hash { return sha256.New() }, []byte("VMESSBSKDF")) hashkdf.Write(account.ID.Bytes()) v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil)) } var cmdkeyfl [16]byte copy(cmdkeyfl[:], account.ID.CmdKey()) v.aeadDecoderHolder.AddUser(cmdkeyfl, u) return nil } func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protocol.Timestamp, bool, error) { defer v.RUnlock() v.RLock() v.behaviorFused = true var fixedSizeHash [16]byte copy(fixedSizeHash[:], userHash) pair, found := v.userHash[fixedSizeHash] if found { user := pair.user.user if atomic.LoadUint32(pair.taintedFuse) == 0 { return &user, protocol.Timestamp(pair.timeInc) + v.baseTime, true, nil } return nil, 0, false, ErrTainted } return nil, 0, false, ErrNotFound } func (v *TimedUserValidator) GetAEAD(userHash []byte) (*protocol.MemoryUser, bool, error) { defer v.RUnlock() v.RLock() var userHashFL [16]byte copy(userHashFL[:], userHash) userd, err := v.aeadDecoderHolder.Match(userHashFL) if err != nil { return nil, false, err } return userd.(*protocol.MemoryUser), true, err } func (v *TimedUserValidator) Remove(email string) bool { v.Lock() defer v.Unlock() idx := -1 for i := range v.users { if strings.EqualFold(v.users[i].user.Email, email) { idx = i var cmdkeyfl [16]byte copy(cmdkeyfl[:], v.users[i].user.Account.(*MemoryAccount).ID.CmdKey()) v.aeadDecoderHolder.RemoveUser(cmdkeyfl) break } } if idx == -1 { return false } ulen := len(v.users) v.users[idx] = v.users[ulen-1] v.users[ulen-1] = nil v.users = v.users[:ulen-1] return true } // Close implements common.Closable. func (v *TimedUserValidator) Close() error { return v.task.Close() } func (v *TimedUserValidator) GetBehaviorSeed() uint64 { v.Lock() defer v.Unlock() v.behaviorFused = true if v.behaviorSeed == 0 { v.behaviorSeed = dice.RollUint64() } return v.behaviorSeed } func (v *TimedUserValidator) BurnTaintFuse(userHash []byte) error { v.RLock() defer v.RUnlock() var userHashFL [16]byte copy(userHashFL[:], userHash) pair, found := v.userHash[userHashFL] if found { if atomic.CompareAndSwapUint32(pair.taintedFuse, 0, 1) { return nil } return ErrTainted } return ErrNotFound } var ErrNotFound = newError("Not Found") var ErrTainted = newError("ErrTainted") ================================================ FILE: proxy/vmess/validator_test.go ================================================ package vmess_test import ( "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" . "v2ray.com/core/proxy/vmess" ) func toAccount(a *Account) protocol.Account { account, err := a.AsAccount() common.Must(err) return account } func TestUserValidator(t *testing.T) { hasher := protocol.DefaultIDHash v := NewTimedUserValidator(hasher) defer common.Close(v) id := uuid.New() user := &protocol.MemoryUser{ Email: "test", Account: toAccount(&Account{ Id: id.String(), AlterId: 8, }), } common.Must(v.Add(user)) { testSmallLag := func(lag time.Duration) { ts := protocol.Timestamp(time.Now().Add(time.Second * lag).Unix()) idHash := hasher(id.Bytes()) common.Must2(serial.WriteUint64(idHash, uint64(ts))) userHash := idHash.Sum(nil) euser, ets, found, _ := v.Get(userHash) if !found { t.Fatal("user not found") } if euser.Email != user.Email { t.Error("unexpected user email: ", euser.Email, " want ", user.Email) } if ets != ts { t.Error("unexpected timestamp: ", ets, " want ", ts) } } testSmallLag(0) testSmallLag(40) testSmallLag(-40) testSmallLag(80) testSmallLag(-80) testSmallLag(120) testSmallLag(-120) } { testBigLag := func(lag time.Duration) { ts := protocol.Timestamp(time.Now().Add(time.Second * lag).Unix()) idHash := hasher(id.Bytes()) common.Must2(serial.WriteUint64(idHash, uint64(ts))) userHash := idHash.Sum(nil) euser, _, found, _ := v.Get(userHash) if found || euser != nil { t.Error("unexpected user") } } testBigLag(121) testBigLag(-121) testBigLag(310) testBigLag(-310) testBigLag(500) testBigLag(-500) } if v := v.Remove(user.Email); !v { t.Error("unable to remove user") } if v := v.Remove(user.Email); v { t.Error("remove user twice") } } func BenchmarkUserValidator(b *testing.B) { for i := 0; i < b.N; i++ { hasher := protocol.DefaultIDHash v := NewTimedUserValidator(hasher) for j := 0; j < 1500; j++ { id := uuid.New() v.Add(&protocol.MemoryUser{ Email: "test", Account: toAccount(&Account{ Id: id.String(), AlterId: 16, }), }) } common.Close(v) } } ================================================ FILE: proxy/vmess/vmess.go ================================================ // Package vmess contains the implementation of VMess protocol and transportation. // // VMess contains both inbound and outbound connections. VMess inbound is usually used on servers // together with 'freedom' to talk to final destination, while VMess outbound is usually used on // clients with 'socks' for proxying. package vmess //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: proxy/vmess/vmessCtxInterface.go ================================================ package vmess // example const AlterID = "VMessCtxInterface_AlterID" ================================================ FILE: release/BUILD ================================================ package(default_visibility = ["//visibility:public"]) load("//infra/bazel:zip.bzl", "pkg_zip") load("//release:mapping.bzl", "gen_mappings") filegroup( name = "config_json", srcs = [ "config/config.json", "config/vpoint_socks_vmess.json", "config/vpoint_vmess_freedom.json", ], ) filegroup( name = "systemd", srcs = [ "config/systemd/system/v2ray.service", "config/systemd/system/v2ray@.service", ], ) filegroup( name = "geodata", srcs = [ "config/geoip.dat", "config/geosite.dat", ], ) pkg_zip( name = "v2ray_darwin_amd64_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_darwin_amd64", "//main:v2ray_darwin_amd64", ], out = "v2ray-macos-64.zip", mappings = gen_mappings("darwin", "amd64", "0"), ) pkg_zip( name = "v2ray_windows_amd64_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_windows_amd64", "//main:v2ray_windows_amd64", "//main:v2ray_windows_amd64_nowindow", ], out = "v2ray-windows-64.zip", mappings = gen_mappings("windows", "amd64", "0"), ) pkg_zip( name = "v2ray_windows_x86_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_windows_386", "//main:v2ray_windows_386", "//main:v2ray_windows_386_nowindow", ], out = "v2ray-windows-32.zip", mappings = gen_mappings("windows", "386", "0"), ) pkg_zip( name = "v2ray_windows_armv7_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_windows_arm_7", "//main:v2ray_windows_arm_7", "//main:v2ray_windows_arm_7_nowindow", ], out = "v2ray-windows-arm32-v7a.zip", mappings = gen_mappings("windows", "arm", "7"), ) pkg_zip( name = "v2ray_freebsd_amd64_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_freebsd_amd64", "//main:v2ray_freebsd_amd64", ], out = "v2ray-freebsd-64.zip", mappings = gen_mappings("freebsd", "amd64", "0"), ) pkg_zip( name = "v2ray_freebsd_x86_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_freebsd_386", "//main:v2ray_freebsd_386", ], out = "v2ray-freebsd-32.zip", mappings = gen_mappings("freebsd", "386", "0"), ) pkg_zip( name = "v2ray_openbsd_amd64_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_openbsd_amd64", "//main:v2ray_openbsd_amd64", ], out = "v2ray-openbsd-64.zip", mappings = gen_mappings("openbsd", "amd64", "0"), ) pkg_zip( name = "v2ray_openbsd_x86_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_openbsd_386", "//main:v2ray_openbsd_386", ], out = "v2ray-openbsd-32.zip", mappings = gen_mappings("openbsd", "386", "0"), ) pkg_zip( name = "v2ray_dragonfly_amd64_package", srcs = [ ":config_json", ":geodata", "//infra/control/main:v2ctl_dragonfly_amd64", "//main:v2ray_dragonfly_amd64", ], out = "v2ray-dragonfly-64.zip", mappings = gen_mappings("dragonfly", "amd64", "0"), ) pkg_zip( name = "v2ray_linux_amd64_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_amd64", "//main:v2ray_linux_amd64", ], out = "v2ray-linux-64.zip", mappings = gen_mappings("linux", "amd64", "0"), ) pkg_zip( name = "v2ray_linux_x86_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_386", "//main:v2ray_linux_386", ], out = "v2ray-linux-32.zip", mappings = gen_mappings("linux", "386", "0"), ) pkg_zip( name = "v2ray_linux_arm64_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_arm64", "//main:v2ray_linux_arm64", ], out = "v2ray-linux-arm64-v8a.zip", mappings = gen_mappings("linux", "arm64", "0"), ) pkg_zip( name = "v2ray_linux_armv7_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_arm_7", "//main:v2ray_linux_arm_7", ], out = "v2ray-linux-arm32-v7a.zip", mappings = gen_mappings("linux", "arm", "7"), ) pkg_zip( name = "v2ray_linux_armv6_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_arm_6", "//main:v2ray_linux_arm_6", ], out = "v2ray-linux-arm32-v6.zip", mappings = gen_mappings("linux", "arm", "6"), ) pkg_zip( name = "v2ray_linux_armv5_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_arm_5", "//main:v2ray_linux_arm_5", ], out = "v2ray-linux-arm32-v5.zip", mappings = gen_mappings("linux", "arm", "5"), ) pkg_zip( name = "v2ray_linux_mips32_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_mips", "//infra/control/main:v2ctl_linux_mips_softfloat", "//main:v2ray_linux_mips", "//main:v2ray_linux_mips_softfloat", ], out = "v2ray-linux-mips32.zip", mappings = gen_mappings("linux", "mips", "0"), ) pkg_zip( name = "v2ray_linux_mips32le_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_mipsle", "//infra/control/main:v2ctl_linux_mipsle_softfloat", "//main:v2ray_linux_mipsle", "//main:v2ray_linux_mipsle_softfloat", ], out = "v2ray-linux-mips32le.zip", mappings = gen_mappings("linux", "mipsle", "0"), ) pkg_zip( name = "v2ray_linux_mips64_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_mips64", "//main:v2ray_linux_mips64", ], out = "v2ray-linux-mips64.zip", mappings = gen_mappings("linux", "mips64", "0"), ) pkg_zip( name = "v2ray_linux_mips64le_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_mips64le", "//main:v2ray_linux_mips64le", ], out = "v2ray-linux-mips64le.zip", mappings = gen_mappings("linux", "mips64le", "0"), ) pkg_zip( name = "v2ray_linux_riscv64_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_riscv64", "//main:v2ray_linux_riscv64", ], out = "v2ray-linux-riscv64.zip", mappings = gen_mappings("linux", "riscv64", "0"), ) pkg_zip( name = "v2ray_linux_s390x_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_s390x", "//main:v2ray_linux_s390x", ], out = "v2ray-linux-s390x.zip", mappings = gen_mappings("linux", "s390x", "0"), ) pkg_zip( name = "v2ray_linux_ppc64_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_ppc64", "//main:v2ray_linux_ppc64", ], out = "v2ray-linux-ppc64.zip", mappings = gen_mappings("linux", "ppc64", "0"), ) pkg_zip( name = "v2ray_linux_ppc64le_package", srcs = [ ":config_json", ":geodata", ":systemd", "//infra/control/main:v2ctl_linux_ppc64le", "//main:v2ray_linux_ppc64le", ], out = "v2ray-linux-ppc64le.zip", mappings = gen_mappings("linux", "ppc64le", "0"), ) ================================================ FILE: release/bleedingrelease.sh ================================================ #!/usr/bin/env bash RELBODY="https://github.com/v2fly/v2ray-core/commit/${RELEASE_SHA}" JSON_DATA=$(echo "{}" | jq -c ".tag_name=\"${RELEASE_TAG}\"") JSON_DATA=$(echo ${JSON_DATA} | jq -c ".prerelease=${PRERELEASE}") JSON_DATA=$(echo ${JSON_DATA} | jq -c ".body=\"${RELBODY}\"") RELEASE_DATA=$(curl --data "${JSON_DATA}" -H "Authorization: token ${GITHUB_TOKEN}" -X POST https://api.github.com/repos/v2fly/V2FlyBleedingEdgeBinary/releases) echo $RELEASE_DATA RELEASE_ID=$(echo $RELEASE_DATA | jq ".id") function uploadfile() { FILE=$1 CTYPE=$(file -b --mime-type $FILE) sleep 1 curl -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: ${CTYPE}" --data-binary @$FILE "https://uploads.github.com/repos/v2fly/V2FlyBleedingEdgeBinary/releases/${RELEASE_ID}/assets?name=$(basename $FILE)" sleep 1 } function upload() { FILE=$1 DGST=$1.dgst openssl dgst -md5 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha1 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha256 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >>$DGST uploadfile $FILE uploadfile $DGST } ART_ROOT=${WORKDIR}/bazel-bin/release pushd ${ART_ROOT} { go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen version ${RELEASE_TAG} go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen project "v2flyunstable" go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-macos-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-arm32-v7a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm64-v8a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v7a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v6.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v5.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips64le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips32le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-ppc64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-ppc64le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-riscv64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-s390x.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-freebsd-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-freebsd-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-openbsd-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-openbsd-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-dragonfly-64.zip } >Release.unsigned.unsorted go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen sort < Release.unsigned.unsorted > Release.unsigned { echo "Build Finished" echo "https://github.com/v2fly/V2FlyBleedingEdgeBinary/releases/tag/${RELEASE_TAG}" } > buildcomment go run github.com/xiaokangwang/V2BuildAssist/v2buildutil post commit "${RELEASE_SHA}" < buildcomment popd upload ${ART_ROOT}/v2ray-macos-64.zip upload ${ART_ROOT}/v2ray-windows-64.zip upload ${ART_ROOT}/v2ray-windows-32.zip upload ${ART_ROOT}/v2ray-windows-arm32-v7a.zip upload ${ART_ROOT}/v2ray-linux-64.zip upload ${ART_ROOT}/v2ray-linux-32.zip upload ${ART_ROOT}/v2ray-linux-arm64-v8a.zip upload ${ART_ROOT}/v2ray-linux-arm32-v7a.zip upload ${ART_ROOT}/v2ray-linux-arm32-v6.zip upload ${ART_ROOT}/v2ray-linux-arm32-v5.zip upload ${ART_ROOT}/v2ray-linux-mips64.zip upload ${ART_ROOT}/v2ray-linux-mips64le.zip upload ${ART_ROOT}/v2ray-linux-mips32.zip upload ${ART_ROOT}/v2ray-linux-mips32le.zip upload ${ART_ROOT}/v2ray-linux-ppc64.zip upload ${ART_ROOT}/v2ray-linux-ppc64le.zip upload ${ART_ROOT}/v2ray-linux-riscv64.zip upload ${ART_ROOT}/v2ray-linux-s390x.zip upload ${ART_ROOT}/v2ray-freebsd-64.zip upload ${ART_ROOT}/v2ray-freebsd-32.zip upload ${ART_ROOT}/v2ray-openbsd-64.zip upload ${ART_ROOT}/v2ray-openbsd-32.zip upload ${ART_ROOT}/v2ray-dragonfly-64.zip upload ${ART_ROOT}/Release.unsigned ================================================ FILE: release/config/config.json ================================================ // Config file of V2Ray. This file follows standard JSON format, with comments support. // Uncomment entries below to satisfy your needs. Also read our manual for more detail at // https://www.v2ray.com/ { "log": { // By default, V2Ray writes access log to stdout. // "access": "/path/to/access/log/file", // By default, V2Ray write error log to stdout. // "error": "/path/to/error/log/file", // Log level, one of "debug", "info", "warning", "error", "none" "loglevel": "warning" }, // List of inbound proxy configurations. "inbounds": [{ // Port to listen on. You may need root access if the value is less than 1024. "port": 1080, // IP address to listen on. Change to "0.0.0.0" to listen on all network interfaces. "listen": "127.0.0.1", // Tag of the inbound proxy. May be used for routing. "tag": "socks-inbound", // Protocol name of inbound proxy. "protocol": "socks", // Settings of the protocol. Varies based on protocol. "settings": { "auth": "noauth", "udp": false, "ip": "127.0.0.1" }, // Enable sniffing on TCP connection. "sniffing": { "enabled": true, // Target domain will be overriden to the one carried by the connection, if the connection is HTTP or HTTPS. "destOverride": ["http", "tls"] } }], // List of outbound proxy configurations. "outbounds": [{ // Protocol name of the outbound proxy. "protocol": "freedom", // Settings of the protocol. Varies based on protocol. "settings": {}, // Tag of the outbound. May be used for routing. "tag": "direct" },{ "protocol": "blackhole", "settings": {}, "tag": "blocked" }], // Transport is for global transport settings. If you have multiple transports with same settings // (say mKCP), you may put it here, instead of in each individual inbound/outbounds. //"transport": {}, // Routing controls how traffic from inbounds are sent to outbounds. "routing": { "domainStrategy": "IPOnDemand", "rules":[ { // Blocks access to private IPs. Remove this if you want to access your router. "type": "field", "ip": ["geoip:private"], "outboundTag": "blocked" }, { // Blocks major ads. "type": "field", "domain": ["geosite:category-ads"], "outboundTag": "blocked" } ] }, // Dns settings for domain resolution. "dns": { // Static hosts, similar to hosts file. "hosts": { // Match v2ray.com to another domain on CloudFlare. This domain will be used when querying IPs for v2ray.com. "domain:v2ray.com": "www.vicemc.net", // The following settings help to eliminate DNS poisoning in mainland China. // It is safe to comment these out if this is not the case for you. "domain:github.io": "pages.github.com", "domain:wikipedia.org": "www.wikimedia.org", "domain:shadowsocks.org": "electronicsrealm.com" }, "servers": [ "1.1.1.1", { "address": "114.114.114.114", "port": 53, // List of domains that use this DNS first. "domains": [ "geosite:cn" ] }, "8.8.8.8", "localhost" ] }, // Policy controls some internal behavior of how V2Ray handles connections. // It may be on connection level by user levels in 'levels', or global settings in 'system.' "policy": { // Connection policys by user levels "levels": { "0": { "uplinkOnly": 0, "downlinkOnly": 0 } }, "system": { "statsInboundUplink": false, "statsInboundDownlink": false, "statsOutboundUplink": false, "statsOutboundDownlink": false } }, // Stats enables internal stats counter. // This setting can be used together with Policy and Api. //"stats":{}, // Api enables gRPC APIs for external programs to communicate with V2Ray instance. //"api": { //"tag": "api", //"services": [ // "HandlerService", // "LoggerService", // "StatsService" //] //}, // You may add other entries to the configuration, but they will not be recognized by V2Ray. "other": {} } ================================================ FILE: release/config/systemd/system/v2ray.service ================================================ [Unit] Description=V2Ray Service Documentation=https://www.v2fly.org/ After=network.target nss-lookup.target [Service] User=nobody CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE NoNewPrivileges=true ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/config.json Restart=on-failure RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target ================================================ FILE: release/config/systemd/system/v2ray@.service ================================================ [Unit] Description=V2Ray Service Documentation=https://www.v2fly.org/ After=network.target nss-lookup.target [Service] User=nobody CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE NoNewPrivileges=true ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/%i.json Restart=on-failure RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target ================================================ FILE: release/config/vpoint_socks_vmess.json ================================================ { "log": { "loglevel": "warning" }, "inbounds": [{ "port": 1080, "listen": "127.0.0.1", "protocol": "socks", "settings": { "auth": "noauth", "udp": false, "ip": "127.0.0.1" } }], "outbounds": [{ "protocol": "freedom", "settings": {}, "tag": "direct" }], "policy": { "levels": { "0": {"uplinkOnly": 0} } } } ================================================ FILE: release/config/vpoint_vmess_freedom.json ================================================ { "inbounds": [{ "port": 10086, "protocol": "vmess", "settings": { "clients": [ { "id": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297", "level": 1, "alterId": 64 } ] } }], "outbounds": [{ "protocol": "freedom", "settings": {} },{ "protocol": "blackhole", "settings": {}, "tag": "blocked" }], "routing": { "rules": [ { "type": "field", "ip": ["geoip:private"], "outboundTag": "blocked" } ] } } ================================================ FILE: release/install-release.sh ================================================ #!/usr/bin/env bash # This file is accessible as https://install.direct/go.sh # Original source is located at github.com/v2fly/v2ray-core/release/install-release.sh # If not specify, default meaning of return value: # 0: Success # 1: System error # 2: Application error # 3: Network error #######color code######## RED="31m" # Error message YELLOW="33m" # Warning message colorEcho(){ echo -e "\033[${1}${@:2}\033[0m" 1>& 2 } colorEcho ${RED} "ERROR: This script has been DISCARDED, please switch to fhs-install-v2ray project." colorEcho ${YELLOW} "HOW TO USE: https://github.com/v2fly/fhs-install-v2ray" colorEcho ${YELLOW} "TO MIGRATE: https://github.com/v2fly/fhs-install-v2ray/wiki/Migrate-from-the-old-script-to-this" exit 255 ================================================ FILE: release/mapping.bzl ================================================ def gen_mappings(os, arch, ver): return { "v2ray_core/release/config": "", "v2ray_core/main/" + os + "/" + arch + "/" + ver: "", "v2ray_core/infra/control/main/" + os + "/" + arch + "/" + ver : "", } ================================================ FILE: release/mutilate/removeVSign.sh ================================================ #!/usr/bin/env bash export VROOT=$(dirname "${BASH_SOURCE[0]}")/../../ rm $VROOT/infra/control/verify.go sed -i '/VSign/d' $VROOT/go.mod ================================================ FILE: release/requestsign.sh ================================================ #!/usr/bin/env bash RELEASE_DATA=$(curl --data "version=${SIGN_VERSION}" --data "password=${SIGN_SERVICE_PASSWORD}" -X POST "${SIGN_SERIVCE_URL}" ) echo $RELEASE_DATA RELEASE_ID=$(echo $RELEASE_DATA| jq -r ".id") function uploadfile() { FILE=$1 CTYPE=$(file -b --mime-type $FILE) sleep 1 curl -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: ${CTYPE}" --data-binary @$FILE "https://uploads.github.com/repos/v2fly/v2ray-core/releases/${RELEASE_ID}/assets?name=$(basename $FILE)" sleep 1 } function upload() { FILE=$1 DGST=$1.dgst openssl dgst -md5 $FILE | sed 's/([^)]*)//g' >> $DGST openssl dgst -sha1 $FILE | sed 's/([^)]*)//g' >> $DGST openssl dgst -sha256 $FILE | sed 's/([^)]*)//g' >> $DGST openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >> $DGST uploadfile $FILE uploadfile $DGST } curl "https://raw.githubusercontent.com/v2fly/Release/master/v2fly/${SIGN_VERSION}.Release" > Release upload Release ================================================ FILE: release/requestsign_github.sh ================================================ #!/usr/bin/env bash export SIGN_VERSION=$(cat $GITHUB_EVENT_PATH| jq -r ".release.tag_name") echo $SIGN_VERSION $GITHUB_WORKSPACE/release/requestsign.sh ================================================ FILE: release/tagrelease.sh ================================================ #!/usr/bin/env bash CONST_refs="refs" TRIGGER_REASON_A=${TRIGGER_REASON:0:${#CONST_refs}} if [ $TRIGGER_REASON_A != $CONST_refs ]; then echo "not a tag: $TRIGGER_REASON_A" exit fi CONST_refsB="refs/tags/" TRIGGER_REASON_B=${TRIGGER_REASON:0:${#CONST_refsB}} if [ $TRIGGER_REASON_B != $CONST_refsB ]; then echo "not a tag (B)" exit fi GITHUB_RELEASE_TAG=${TRIGGER_REASON:${#CONST_refsB}:25} echo ${GITHUB_RELEASE_TAG} RELEASE_DATA=$(curl -H "Authorization: token ${GITHUB_TOKEN}" -X GET https://api.github.com/repos/v2fly/v2ray-core/releases/tags/${GITHUB_RELEASE_TAG}) echo $RELEASE_DATA RELEASE_ID=$(echo $RELEASE_DATA | jq ".id") echo $RELEASE_ID function uploadfile() { FILE=$1 CTYPE=$(file -b --mime-type $FILE) sleep 1 curl -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: ${CTYPE}" --data-binary @$FILE "https://uploads.github.com/repos/v2fly/v2ray-core/releases/${RELEASE_ID}/assets?name=$(basename $FILE)" sleep 1 } function upload() { FILE=$1 DGST=$1.dgst openssl dgst -md5 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha1 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha256 $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >>$DGST uploadfile $FILE uploadfile $DGST } ART_ROOT=${WORKDIR}/bazel-bin/release pushd ${ART_ROOT} { go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen version "${GITHUB_RELEASE_TAG}" go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen project "v2fly" go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-macos-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-windows-arm32-v7a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm64-v8a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v7a.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v6.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-arm32-v5.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips64le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-mips32le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-ppc64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-ppc64le.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-riscv64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-linux-s390x.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-freebsd-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-freebsd-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-openbsd-64.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-openbsd-32.zip go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen file v2ray-dragonfly-64.zip } >Release.unsigned.unsorted go run github.com/xiaokangwang/V2BuildAssist/v2buildutil gen sort Release.unsigned popd upload ${ART_ROOT}/v2ray-macos-64.zip upload ${ART_ROOT}/v2ray-windows-64.zip upload ${ART_ROOT}/v2ray-windows-32.zip upload ${ART_ROOT}/v2ray-windows-arm32-v7a.zip upload ${ART_ROOT}/v2ray-linux-64.zip upload ${ART_ROOT}/v2ray-linux-32.zip upload ${ART_ROOT}/v2ray-linux-arm64-v8a.zip upload ${ART_ROOT}/v2ray-linux-arm32-v7a.zip upload ${ART_ROOT}/v2ray-linux-arm32-v6.zip upload ${ART_ROOT}/v2ray-linux-arm32-v5.zip upload ${ART_ROOT}/v2ray-linux-mips64.zip upload ${ART_ROOT}/v2ray-linux-mips64le.zip upload ${ART_ROOT}/v2ray-linux-mips32.zip upload ${ART_ROOT}/v2ray-linux-mips32le.zip upload ${ART_ROOT}/v2ray-linux-ppc64.zip upload ${ART_ROOT}/v2ray-linux-ppc64le.zip upload ${ART_ROOT}/v2ray-linux-riscv64.zip upload ${ART_ROOT}/v2ray-linux-s390x.zip upload ${ART_ROOT}/v2ray-freebsd-64.zip upload ${ART_ROOT}/v2ray-freebsd-32.zip upload ${ART_ROOT}/v2ray-openbsd-64.zip upload ${ART_ROOT}/v2ray-openbsd-32.zip upload ${ART_ROOT}/v2ray-dragonfly-64.zip upload ${ART_ROOT}/Release.unsigned ================================================ FILE: release/user-package.sh ================================================ #!/usr/bin/env bash set -o errexit set -o pipefail set -o nounset # set -o xtrace trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; exit 1' ERR NOW=$(date '+%Y%m%d-%H%M%S') TMP=$(mktemp -d) SRCDIR=$(pwd) CODENAME="user" BUILDNAME=$NOW cleanup() { rm -rf "$TMP"; } trap cleanup INT TERM ERR get_source() { echo ">>> Clone v2fly/v2ray-core repo..." git clone https://github.com/v2fly/v2ray-core.git cd v2ray-core go mod download } build_v2() { if [[ $nosource != 1 ]]; then cd ${SRCDIR}/v2ray-core local VERSIONTAG=$(git describe --abbrev=0 --tags) else echo ">>> Use current directory as WORKDIR" local VERSIONTAG=$(git describe --abbrev=0 --tags) fi LDFLAGS="-s -w -buildid= -X v2ray.codename=${CODENAME} -X v2ray.build=${BUILDNAME} -X v2ray.version=${VERSIONTAG}" echo ">>> Compile v2ray ..." env CGO_ENABLED=0 go build -o "$TMP"/v2ray"${EXESUFFIX}" -ldflags "$LDFLAGS" ./main if [[ $GOOS == "windows" ]]; then env CGO_ENABLED=0 go build -o "$TMP"/wv2ray"${EXESUFFIX}" -ldflags "-H windowsgui $LDFLAGS" ./main fi echo ">>> Compile v2ctl ..." env CGO_ENABLED=0 go build -o "$TMP"/v2ctl"${EXESUFFIX}" -tags confonly -ldflags "$LDFLAGS" ./infra/control/main } build_dat() { echo ">>> Download latest geoip..." curl -s -L -o "$TMP"/geoip.dat "https://github.com/v2fly/geoip/raw/release/geoip.dat" echo ">>> Download latest geosite..." curl -s -L -o "$TMP"/geosite.dat "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat" } copyconf() { echo ">>> Copying config..." cd ./release/config if [[ $GOOS == "linux" ]]; then tar c --exclude "*.dat" . | tar x -C "$TMP" else tar c --exclude "*.dat" --exclude "systemd/**" . | tar x -C "$TMP" fi } packzip() { echo ">>> Generating zip package" cd "$TMP" local PKG=${SRCDIR}/v2ray-custom-${GOARCH}-${GOOS}-${PKGSUFFIX}${NOW}.zip zip -r "$PKG" . echo ">>> Generated: $(basename "$PKG") at $(dirname "$PKG")" } packtgz() { echo ">>> Generating tgz package" cd "$TMP" local PKG=${SRCDIR}/v2ray-custom-${GOARCH}-${GOOS}-${PKGSUFFIX}${NOW}.tar.gz tar cvfz "$PKG" . echo ">>> Generated: $(basename "$PKG") at $(dirname "$PKG")" } packtgzAbPath() { local ABPATH="$1" echo ">>> Generating tgz package at $ABPATH" cd "$TMP" tar cvfz "$ABPATH" . echo ">>> Generated: $ABPATH" } pkg=zip nosource=0 nodat=0 noconf=0 GOOS=linux GOARCH=amd64 EXESUFFIX= PKGSUFFIX= for arg in "$@"; do case $arg in 386 | arm* | mips* | ppc64* | riscv64 | s390x) GOARCH=$arg ;; windows) GOOS=$arg EXESUFFIX=.exe ;; darwin | dragonfly | freebsd | openbsd) GOOS=$arg ;; nodat) nodat=1 PKGSUFFIX=${PKGSUFFIX}nodat- ;; noconf) noconf=1 ;; nosource) nosource=1 ;; tgz) pkg=tgz ;; abpathtgz=*) pkg=${arg##abpathtgz=} ;; codename=*) CODENAME=${arg##codename=} ;; buildname=*) BUILDNAME=${arg##buildname=} ;; esac done if [[ $nosource != 1 ]]; then get_source fi export GOOS GOARCH echo "Build ARGS: GOOS=${GOOS} GOARCH=${GOARCH} CODENAME=${CODENAME} BUILDNAME=${BUILDNAME}" echo "PKG ARGS: pkg=${pkg}" build_v2 if [[ $nodat != 1 ]]; then build_dat fi if [[ $noconf != 1 ]]; then copyconf fi if [[ $pkg == "zip" ]]; then packzip elif [[ $pkg == "tgz" ]]; then packtgz else packtgzAbPath "$pkg" fi cleanup ================================================ FILE: testing/coverage/coverall ================================================ #!/bin/bash FAIL=0 V2RAY_OUT=${PWD}/out/v2ray export V2RAY_COV=${V2RAY_OUT}/cov COVERAGE_FILE=${V2RAY_COV}/coverage.txt function test_package { DIR=".$1" DEP=$(go list -f '{{ join .Deps "\n" }}' $DIR | grep v2ray | tr '\n' ',') DEP=${DEP}$DIR RND_NAME=$(openssl rand -hex 16) COV_PROFILE=${V2RAY_COV}/${RND_NAME}.out go test -tags "json coverage" -coverprofile=${COV_PROFILE} -coverpkg=$DEP $DIR || FAIL=1 } rm -rf ${V2RAY_OUT} mkdir -p ${V2RAY_COV} touch ${COVERAGE_FILE} TEST_FILES=(./*_test.go) if [ -f ${TEST_FILES[0]} ]; then test_package "" fi for DIR in $(find * -type d ! -path "*.git*" ! -path "*vendor*" ! -path "*external*"); do TEST_FILES=($DIR/*_test.go) if [ -f ${TEST_FILES[0]} ]; then test_package "/$DIR" fi done for OUT_FILE in $(find ${V2RAY_COV} -name "*.out"); do echo "Merging file ${OUT_FILE}" cat ${OUT_FILE} | grep -v "mode: set" >> ${COVERAGE_FILE} done COV_SORTED=${V2RAY_COV}/coverallsorted.out cat ${COVERAGE_FILE} | sort -t: -k1 | grep -vw "testing" | grep -v ".pb.go" | grep -vw "vendor" | grep -vw "external" > ${COV_SORTED} echo "mode: set" | cat - ${COV_SORTED} > ${COVERAGE_FILE} if [ "$FAIL" -eq 0 ]; then echo "Uploading coverage datea to codecov." #bash <(curl -s https://codecov.io/bash) -f ${COVERAGE_FILE} -v || echo "Codecov did not collect coverage reports." fi exit $FAIL ================================================ FILE: testing/coverage/coverall2 ================================================ #!/bin/bash COVERAGE_FILE=${PWD}/coverage.txt COV_SORTED=${PWD}/coverallsorted.out touch "$COVERAGE_FILE" function test_package { DIR=".$1" DEP=$(go list -f '{{ join .Deps "\n" }}' "$DIR" | grep v2ray | tr '\n' ',') DEP=${DEP}$DIR RND_NAME=$(openssl rand -hex 16) COV_PROFILE=${RND_NAME}.out go test -coverprofile="$COV_PROFILE" -coverpkg="$DEP" "$DIR" || return } TEST_FILES=(./*_test.go) if [ -f "${TEST_FILES[0]}" ]; then test_package "" fi # shellcheck disable=SC2044 for DIR in $(find ./* -type d ! -path "*.git*" ! -path "*vendor*" ! -path "*external*"); do TEST_FILES=("$DIR"/*_test.go) if [ -f "${TEST_FILES[0]}" ]; then test_package "/$DIR" fi done # merge out while IFS= read -r -d '' OUT_FILE do echo "Merging file ${OUT_FILE}" < "${OUT_FILE}" grep -v "mode: set" >> "$COVERAGE_FILE" done < <(find ./* -name "*.out" -print0) < "$COVERAGE_FILE" sort -t: -k1 | grep -vw "testing" | grep -v ".pb.go" | grep -vw "vendor" | grep -vw "external" > "$COV_SORTED" echo "mode: set" | cat - "${COV_SORTED}" > "${COVERAGE_FILE}" bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' ================================================ FILE: testing/mocks/dns.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: v2ray.com/core/features/dns (interfaces: Client) // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" net "net" reflect "reflect" ) // DNSClient is a mock of Client interface type DNSClient struct { ctrl *gomock.Controller recorder *DNSClientMockRecorder } // DNSClientMockRecorder is the mock recorder for DNSClient type DNSClientMockRecorder struct { mock *DNSClient } // NewDNSClient creates a new mock instance func NewDNSClient(ctrl *gomock.Controller) *DNSClient { mock := &DNSClient{ctrl: ctrl} mock.recorder = &DNSClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *DNSClient) EXPECT() *DNSClientMockRecorder { return m.recorder } // Close mocks base method func (m *DNSClient) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close func (mr *DNSClientMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*DNSClient)(nil).Close)) } // LookupIP mocks base method func (m *DNSClient) LookupIP(arg0 string) ([]net.IP, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LookupIP", arg0) ret0, _ := ret[0].([]net.IP) ret1, _ := ret[1].(error) return ret0, ret1 } // LookupIP indicates an expected call of LookupIP func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0) } // Start mocks base method func (m *DNSClient) Start() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Start") ret0, _ := ret[0].(error) return ret0 } // Start indicates an expected call of Start func (mr *DNSClientMockRecorder) Start() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*DNSClient)(nil).Start)) } // Type mocks base method func (m *DNSClient) Type() interface{} { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Type") ret0, _ := ret[0].(interface{}) return ret0 } // Type indicates an expected call of Type func (mr *DNSClientMockRecorder) Type() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*DNSClient)(nil).Type)) } ================================================ FILE: testing/mocks/io.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: io (interfaces: Reader,Writer) // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" reflect "reflect" ) // Reader is a mock of Reader interface type Reader struct { ctrl *gomock.Controller recorder *ReaderMockRecorder } // ReaderMockRecorder is the mock recorder for Reader type ReaderMockRecorder struct { mock *Reader } // NewReader creates a new mock instance func NewReader(ctrl *gomock.Controller) *Reader { mock := &Reader{ctrl: ctrl} mock.recorder = &ReaderMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *Reader) EXPECT() *ReaderMockRecorder { return m.recorder } // Read mocks base method func (m *Reader) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read func (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Reader)(nil).Read), arg0) } // Writer is a mock of Writer interface type Writer struct { ctrl *gomock.Controller recorder *WriterMockRecorder } // WriterMockRecorder is the mock recorder for Writer type WriterMockRecorder struct { mock *Writer } // NewWriter creates a new mock instance func NewWriter(ctrl *gomock.Controller) *Writer { mock := &Writer{ctrl: ctrl} mock.recorder = &WriterMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *Writer) EXPECT() *WriterMockRecorder { return m.recorder } // Write mocks base method func (m *Writer) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Write indicates an expected call of Write func (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0) } ================================================ FILE: testing/mocks/log.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: v2ray.com/core/common/log (interfaces: Handler) // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" reflect "reflect" log "v2ray.com/core/common/log" ) // LogHandler is a mock of Handler interface type LogHandler struct { ctrl *gomock.Controller recorder *LogHandlerMockRecorder } // LogHandlerMockRecorder is the mock recorder for LogHandler type LogHandlerMockRecorder struct { mock *LogHandler } // NewLogHandler creates a new mock instance func NewLogHandler(ctrl *gomock.Controller) *LogHandler { mock := &LogHandler{ctrl: ctrl} mock.recorder = &LogHandlerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *LogHandler) EXPECT() *LogHandlerMockRecorder { return m.recorder } // Handle mocks base method func (m *LogHandler) Handle(arg0 log.Message) { m.ctrl.T.Helper() m.ctrl.Call(m, "Handle", arg0) } // Handle indicates an expected call of Handle func (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0) } ================================================ FILE: testing/mocks/mux.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: v2ray.com/core/common/mux (interfaces: ClientWorkerFactory) // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" reflect "reflect" mux "v2ray.com/core/common/mux" ) // MuxClientWorkerFactory is a mock of ClientWorkerFactory interface type MuxClientWorkerFactory struct { ctrl *gomock.Controller recorder *MuxClientWorkerFactoryMockRecorder } // MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory type MuxClientWorkerFactoryMockRecorder struct { mock *MuxClientWorkerFactory } // NewMuxClientWorkerFactory creates a new mock instance func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory { mock := &MuxClientWorkerFactory{ctrl: ctrl} mock.recorder = &MuxClientWorkerFactoryMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder { return m.recorder } // Create mocks base method func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Create") ret0, _ := ret[0].(*mux.ClientWorker) ret1, _ := ret[1].(error) return ret0, ret1 } // Create indicates an expected call of Create func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create)) } ================================================ FILE: testing/mocks/outbound.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: v2ray.com/core/features/outbound (interfaces: Manager,HandlerSelector) // Package mocks is a generated GoMock package. package mocks import ( context "context" gomock "github.com/golang/mock/gomock" reflect "reflect" outbound "v2ray.com/core/features/outbound" ) // OutboundManager is a mock of Manager interface type OutboundManager struct { ctrl *gomock.Controller recorder *OutboundManagerMockRecorder } // OutboundManagerMockRecorder is the mock recorder for OutboundManager type OutboundManagerMockRecorder struct { mock *OutboundManager } // NewOutboundManager creates a new mock instance func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager { mock := &OutboundManager{ctrl: ctrl} mock.recorder = &OutboundManagerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder { return m.recorder } // AddHandler mocks base method func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddHandler", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddHandler indicates an expected call of AddHandler func (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHandler", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1) } // Close mocks base method func (m *OutboundManager) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close func (mr *OutboundManagerMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*OutboundManager)(nil).Close)) } // GetDefaultHandler mocks base method func (m *OutboundManager) GetDefaultHandler() outbound.Handler { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDefaultHandler") ret0, _ := ret[0].(outbound.Handler) return ret0 } // GetDefaultHandler indicates an expected call of GetDefaultHandler func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultHandler", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler)) } // GetHandler mocks base method func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHandler", arg0) ret0, _ := ret[0].(outbound.Handler) return ret0 } // GetHandler indicates an expected call of GetHandler func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandler", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0) } // RemoveHandler mocks base method func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // RemoveHandler indicates an expected call of RemoveHandler func (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHandler", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1) } // Start mocks base method func (m *OutboundManager) Start() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Start") ret0, _ := ret[0].(error) return ret0 } // Start indicates an expected call of Start func (mr *OutboundManagerMockRecorder) Start() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*OutboundManager)(nil).Start)) } // Type mocks base method func (m *OutboundManager) Type() interface{} { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Type") ret0, _ := ret[0].(interface{}) return ret0 } // Type indicates an expected call of Type func (mr *OutboundManagerMockRecorder) Type() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*OutboundManager)(nil).Type)) } // OutboundHandlerSelector is a mock of HandlerSelector interface type OutboundHandlerSelector struct { ctrl *gomock.Controller recorder *OutboundHandlerSelectorMockRecorder } // OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector type OutboundHandlerSelectorMockRecorder struct { mock *OutboundHandlerSelector } // NewOutboundHandlerSelector creates a new mock instance func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector { mock := &OutboundHandlerSelector{ctrl: ctrl} mock.recorder = &OutboundHandlerSelectorMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder { return m.recorder } // Select mocks base method func (m *OutboundHandlerSelector) Select(arg0 []string) []string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Select", arg0) ret0, _ := ret[0].([]string) return ret0 } // Select indicates an expected call of Select func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0) } ================================================ FILE: testing/mocks/proxy.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: v2ray.com/core/proxy (interfaces: Inbound,Outbound) // Package mocks is a generated GoMock package. package mocks import ( context "context" gomock "github.com/golang/mock/gomock" reflect "reflect" net "v2ray.com/core/common/net" routing "v2ray.com/core/features/routing" transport "v2ray.com/core/transport" internet "v2ray.com/core/transport/internet" ) // ProxyInbound is a mock of Inbound interface type ProxyInbound struct { ctrl *gomock.Controller recorder *ProxyInboundMockRecorder } // ProxyInboundMockRecorder is the mock recorder for ProxyInbound type ProxyInboundMockRecorder struct { mock *ProxyInbound } // NewProxyInbound creates a new mock instance func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound { mock := &ProxyInbound{ctrl: ctrl} mock.recorder = &ProxyInboundMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder { return m.recorder } // Network mocks base method func (m *ProxyInbound) Network() []net.Network { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") ret0, _ := ret[0].([]net.Network) return ret0 } // Network indicates an expected call of Network func (mr *ProxyInboundMockRecorder) Network() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*ProxyInbound)(nil).Network)) } // Process mocks base method func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 internet.Connection, arg3 routing.Dispatcher) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // Process indicates an expected call of Process func (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3) } // ProxyOutbound is a mock of Outbound interface type ProxyOutbound struct { ctrl *gomock.Controller recorder *ProxyOutboundMockRecorder } // ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound type ProxyOutboundMockRecorder struct { mock *ProxyOutbound } // NewProxyOutbound creates a new mock instance func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound { mock := &ProxyOutbound{ctrl: ctrl} mock.recorder = &ProxyOutboundMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder { return m.recorder } // Process mocks base method func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // Process indicates an expected call of Process func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2) } ================================================ FILE: testing/scenarios/command_test.go ================================================ package scenarios import ( "context" "fmt" "github.com/google/go-cmp/cmp/cmpopts" "io" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "google.golang.org/grpc" "v2ray.com/core" "v2ray.com/core/app/commander" "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/proxyman/command" "v2ray.com/core/app/router" "v2ray.com/core/app/stats" statscmd "v2ray.com/core/app/stats/command" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" ) func TestCommanderRemoveHandler(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() clientPort := tcp.PickPort() cmdPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&commander.Config{ Tag: "api", Service: []*serial.TypedMessage{ serial.ToTypedMessage(&command.Config{}), }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { InboundTag: []string{"api"}, TargetTag: &router.RoutingRule_Tag{ Tag: "api", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "d", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), Networks: []net.Network{net.Network_TCP}, }), }, { Tag: "api", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(cmdPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), Networks: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { Tag: "default-outbound", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Fatal(err) } cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) common.Must(err) defer cmdConn.Close() hsClient := command.NewHandlerServiceClient(cmdConn) resp, err := hsClient.RemoveInbound(context.Background(), &command.RemoveInboundRequest{ Tag: "d", }) common.Must(err) if resp == nil { t.Error("unexpected nil response") } { _, err := net.DialTCP("tcp", nil, &net.TCPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(clientPort), }) if err == nil { t.Error("unexpected nil error") } } } func TestCommanderAddRemoveUser(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() u1 := protocol.NewID(uuid.New()) u2 := protocol.NewID(uuid.New()) cmdPort := tcp.PickPort() serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&commander.Config{ Tag: "api", Service: []*serial.TypedMessage{ serial.ToTypedMessage(&command.Config{}), }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { InboundTag: []string{"api"}, TargetTag: &router.RoutingRule_Tag{ Tag: "api", }, }, }, }), serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "v", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: u1.String(), AlterId: 64, }), }, }, }), }, { Tag: "api", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(cmdPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), Networks: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "d", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: u2.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF && /*We might wish to drain the connection*/ (err != nil && !strings.HasSuffix(err.Error(), "i/o timeout")) { t.Fatal("expected error: ", err) } cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) common.Must(err) defer cmdConn.Close() hsClient := command.NewHandlerServiceClient(cmdConn) resp, err := hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{ Tag: "v", Operation: serial.ToTypedMessage( &command.AddUserOperation{ User: &protocol.User{ Email: "test@v2ray.com", Account: serial.ToTypedMessage(&vmess.Account{ Id: u2.String(), AlterId: 64, }), }, }), }) common.Must(err) if resp == nil { t.Fatal("nil response") } if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Fatal(err) } resp, err = hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{ Tag: "v", Operation: serial.ToTypedMessage(&command.RemoveUserOperation{Email: "test@v2ray.com"}), }) common.Must(err) if resp == nil { t.Fatal("nil response") } } func TestCommanderStats(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() cmdPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&stats.Config{}), serial.ToTypedMessage(&commander.Config{ Tag: "api", Service: []*serial.TypedMessage{ serial.ToTypedMessage(&statscmd.Config{}), }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { InboundTag: []string{"api"}, TargetTag: &router.RoutingRule_Tag{ Tag: "api", }, }, }, }), serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, 1: { Stats: &policy.Policy_Stats{ UserUplink: true, UserDownlink: true, }, }, }, System: &policy.SystemPolicy{ Stats: &policy.SystemPolicy_Stats{ InboundUplink: true, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "vmess", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Level: 1, Email: "test", Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, { Tag: "api", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(cmdPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) if err != nil { t.Fatal("Failed to create all servers", err) } defer CloseAllServers(servers) if err := testTCPConn(clientPort, 10240*1024, time.Second*20)(); err != nil { t.Fatal(err) } cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) common.Must(err) defer cmdConn.Close() const name = "user>>>test>>>traffic>>>uplink" sClient := statscmd.NewStatsServiceClient(cmdConn) sresp, err := sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{ Name: name, Reset_: true, }) common.Must(err) if r := cmp.Diff(sresp.Stat, &statscmd.Stat{ Name: name, Value: 10240 * 1024, }, cmpopts.IgnoreUnexported(statscmd.Stat{})); r != "" { t.Error(r) } sresp, err = sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{ Name: name, }) common.Must(err) if r := cmp.Diff(sresp.Stat, &statscmd.Stat{ Name: name, Value: 0, }, cmpopts.IgnoreUnexported(statscmd.Stat{})); r != "" { t.Error(r) } sresp, err = sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{ Name: "inbound>>>vmess>>>traffic>>>uplink", Reset_: true, }) common.Must(err) if sresp.Stat.Value <= 10240*1024 { t.Error("value < 10240*1024: ", sresp.Stat.Value) } } ================================================ FILE: testing/scenarios/common.go ================================================ package scenarios import ( "bytes" "crypto/rand" "fmt" "io" "io/ioutil" "os/exec" "path/filepath" "runtime" "sync" "syscall" "time" "github.com/golang/protobuf/proto" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/retry" "v2ray.com/core/common/serial" ) func xor(b []byte) []byte { r := make([]byte, len(b)) for i, v := range b { r[i] = v ^ 'c' } return r } func readFrom(conn net.Conn, timeout time.Duration, length int) []byte { b := make([]byte, length) deadline := time.Now().Add(timeout) conn.SetReadDeadline(deadline) n, err := io.ReadFull(conn, b[:length]) if err != nil { fmt.Println("Unexpected error from readFrom:", err) } return b[:n] } func readFrom2(conn net.Conn, timeout time.Duration, length int) ([]byte, error) { b := make([]byte, length) deadline := time.Now().Add(timeout) conn.SetReadDeadline(deadline) n, err := io.ReadFull(conn, b[:length]) if err != nil { return nil, err } return b[:n], nil } func InitializeServerConfigs(configs ...*core.Config) ([]*exec.Cmd, error) { servers := make([]*exec.Cmd, 0, 10) for _, config := range configs { server, err := InitializeServerConfig(config) if err != nil { CloseAllServers(servers) return nil, err } servers = append(servers, server) } time.Sleep(time.Second * 2) return servers, nil } func InitializeServerConfig(config *core.Config) (*exec.Cmd, error) { err := BuildV2Ray() if err != nil { return nil, err } config = withDefaultApps(config) configBytes, err := proto.Marshal(config) if err != nil { return nil, err } proc := RunV2RayProtobuf(configBytes) if err := proc.Start(); err != nil { return nil, err } return proc, nil } var ( testBinaryPath string testBinaryPathGen sync.Once ) func genTestBinaryPath() { testBinaryPathGen.Do(func() { var tempDir string common.Must(retry.Timed(5, 100).On(func() error { dir, err := ioutil.TempDir("", "v2ray") if err != nil { return err } tempDir = dir return nil })) file := filepath.Join(tempDir, "v2ray.test") if runtime.GOOS == "windows" { file += ".exe" } testBinaryPath = file fmt.Printf("Generated binary path: %s\n", file) }) } func GetSourcePath() string { return filepath.Join("v2ray.com", "core", "main") } func CloseAllServers(servers []*exec.Cmd) { log.Record(&log.GeneralMessage{ Severity: log.Severity_Info, Content: "Closing all servers.", }) for _, server := range servers { if runtime.GOOS == "windows" { server.Process.Kill() } else { server.Process.Signal(syscall.SIGTERM) } } for _, server := range servers { server.Process.Wait() } log.Record(&log.GeneralMessage{ Severity: log.Severity_Info, Content: "All server closed.", }) } func withDefaultApps(config *core.Config) *core.Config { config.App = append(config.App, serial.ToTypedMessage(&dispatcher.Config{})) config.App = append(config.App, serial.ToTypedMessage(&proxyman.InboundConfig{})) config.App = append(config.App, serial.ToTypedMessage(&proxyman.OutboundConfig{})) return config } func testTCPConn(port net.Port, payloadSize int, timeout time.Duration) func() error { return func() error { conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(port), }) if err != nil { return err } defer conn.Close() return testTCPConn2(conn, payloadSize, timeout)() } } func testUDPConn(port net.Port, payloadSize int, timeout time.Duration) func() error { return func() error { conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(port), }) if err != nil { return err } defer conn.Close() return testTCPConn2(conn, payloadSize, timeout)() } } func testTCPConn2(conn net.Conn, payloadSize int, timeout time.Duration) func() error { return func() error { payload := make([]byte, payloadSize) common.Must2(rand.Read(payload)) nBytes, err := conn.Write(payload) if err != nil { return err } if nBytes != len(payload) { return errors.New("expect ", len(payload), " written, but actually ", nBytes) } response, err := readFrom2(conn, timeout, payloadSize) if err != nil { return err } _ = response if r := bytes.Compare(response, xor(payload)); r != 0 { return errors.New(r) } return nil } } ================================================ FILE: testing/scenarios/common_coverage.go ================================================ // +build coverage package scenarios import ( "bytes" "os" "os/exec" "v2ray.com/core/common/uuid" ) func BuildV2Ray() error { genTestBinaryPath() if _, err := os.Stat(testBinaryPath); err == nil { return nil } cmd := exec.Command("go", "test", "-tags", "coverage coveragemain", "-coverpkg", "v2ray.com/core/...", "-c", "-o", testBinaryPath, GetSourcePath()) return cmd.Run() } func RunV2RayProtobuf(config []byte) *exec.Cmd { genTestBinaryPath() covDir := os.Getenv("V2RAY_COV") os.MkdirAll(covDir, os.ModeDir) randomID := uuid.New() profile := randomID.String() + ".out" proc := exec.Command(testBinaryPath, "-config=stdin:", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir) proc.Stdin = bytes.NewBuffer(config) proc.Stderr = os.Stderr proc.Stdout = os.Stdout return proc } ================================================ FILE: testing/scenarios/common_regular.go ================================================ // +build !coverage package scenarios import ( "bytes" "fmt" "os" "os/exec" ) func BuildV2Ray() error { genTestBinaryPath() if _, err := os.Stat(testBinaryPath); err == nil { return nil } fmt.Printf("Building V2Ray into path (%s)\n", testBinaryPath) cmd := exec.Command("go", "build", "-o="+testBinaryPath, GetSourcePath()) return cmd.Run() } func RunV2RayProtobuf(config []byte) *exec.Cmd { genTestBinaryPath() proc := exec.Command(testBinaryPath, "-config=stdin:", "-format=pb") proc.Stdin = bytes.NewBuffer(config) proc.Stderr = os.Stderr proc.Stdout = os.Stdout return proc } ================================================ FILE: testing/scenarios/dns_test.go ================================================ package scenarios import ( "fmt" "testing" "time" xproxy "golang.org/x/net/proxy" "v2ray.com/core" "v2ray.com/core/app/dns" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/blackhole" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/socks" "v2ray.com/core/testing/servers/tcp" ) func TestResolveIP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dns.Config{ Hosts: map[string]*net.IPOrDomain{ "google.com": net.NewIPOrDomain(dest.Address), }, }), serial.ToTypedMessage(&router.Config{ DomainStrategy: router.Config_IpIfNonMatch, Rule: []*router.RoutingRule{ { Cidr: []*router.CIDR{ { Ip: []byte{127, 0, 0, 0}, Prefix: 8, }, }, TargetTag: &router.RoutingRule_Tag{ Tag: "direct", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_NO_AUTH, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: false, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, { Tag: "direct", ProxySettings: serial.ToTypedMessage(&freedom.Config{ DomainStrategy: freedom.Config_USE_IP, }), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { noAuthDialer, err := xproxy.SOCKS5("tcp", net.TCPDestination(net.LocalHostIP, serverPort).NetAddr(), nil, xproxy.Direct) common.Must(err) conn, err := noAuthDialer.Dial("tcp", fmt.Sprintf("google.com:%d", dest.Port)) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } } ================================================ FILE: testing/scenarios/dokodemo_test.go ================================================ package scenarios import ( "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" ) func TestDokodemoTCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := uint32(tcp.PickPort()) clientPortRange := uint32(5) clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange}, Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) for port := clientPort; port <= clientPort+clientPortRange; port++ { if err := testTCPConn(net.Port(port), 1024, time.Second*2)(); err != nil { t.Error(err) } } } func TestDokodemoUDP(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := uint32(tcp.PickPort()) clientPortRange := uint32(5) clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange}, Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for port := clientPort; port <= clientPort+clientPortRange; port++ { errg.Go(testUDPConn(net.Port(port), 1024, time.Second*5)) } if err := errg.Wait(); err != nil { t.Error(err) } } ================================================ FILE: testing/scenarios/feature_test.go ================================================ package scenarios import ( "context" "io/ioutil" "net/http" "net/url" "testing" "time" xproxy "golang.org/x/net/proxy" "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" _ "v2ray.com/core/app/proxyman/inbound" _ "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/app/router" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/blackhole" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" v2http "v2ray.com/core/proxy/http" "v2ray.com/core/proxy/socks" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" "v2ray.com/core/transport/internet" ) func TestPassiveConnection(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, SendFirst: []byte("send first"), } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(serverPort), }) common.Must(err) { response := make([]byte, 1024) nBytes, err := conn.Read(response) common.Must(err) if string(response[:nBytes]) != "send first" { t.Error("unexpected first response: ", string(response[:nBytes])) } } if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestProxy(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverUserID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: serverUserID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } proxyUserID := protocol.NewID(uuid.New()) proxyPort := tcp.PickPort() proxyConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(proxyPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: proxyUserID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: serverUserID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ ProxySettings: &internet.ProxyConfig{ Tag: "proxy", }, }), }, { Tag: "proxy", ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(proxyPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: proxyUserID.String(), }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, proxyConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestProxyOverKCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverUserID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: serverUserID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } proxyUserID := protocol.NewID(uuid.New()) proxyPort := tcp.PickPort() proxyConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(proxyPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: proxyUserID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, }, }), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: serverUserID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ ProxySettings: &internet.ProxyConfig{ Tag: "proxy", }, StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, }, }), }, { Tag: "proxy", ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(proxyPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: proxyUserID.String(), }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, proxyConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestBlackhole(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() tcpServer2 := tcp.Server{ MsgProcessor: xor, } dest2, err := tcpServer2.Start() common.Must(err) defer tcpServer2.Close() serverPort := tcp.PickPort() serverPort2 := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort2), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest2.Address), Port: uint32(dest2.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { Tag: "direct", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, { Tag: "blocked", ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, }, App: []*serial.TypedMessage{ serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { TargetTag: &router.RoutingRule_Tag{ Tag: "blocked", }, PortRange: net.SinglePortRange(dest2.Port), }, }, }), }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(serverPort2, 1024, time.Second*5)(); err == nil { t.Error("nil error") } } func TestForward(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_NO_AUTH, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: false, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{ DestinationOverride: &freedom.DestinationOverride{ Server: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(dest.Port), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { noAuthDialer, err := xproxy.SOCKS5("tcp", net.TCPDestination(net.LocalHostIP, serverPort).NetAddr(), nil, xproxy.Direct) common.Must(err) conn, err := noAuthDialer.Dial("tcp", "google.com:80") common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } } func TestUDPConnection(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } time.Sleep(20 * time.Second) if err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestDomainSniffing(t *testing.T) { sniffingPort := tcp.PickPort() httpPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { Tag: "snif", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(sniffingPort), Listen: net.NewIPOrDomain(net.LocalHostIP), DomainOverride: []proxyman.KnownProtocols{ proxyman.KnownProtocols_TLS, }, }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: 443, NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, { Tag: "http", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(httpPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}), }, }, Outbound: []*core.OutboundHandlerConfig{ { Tag: "redir", ProxySettings: serial.ToTypedMessage(&freedom.Config{ DestinationOverride: &freedom.DestinationOverride{ Server: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(sniffingPort), }, }, }), }, { Tag: "direct", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, App: []*serial.TypedMessage{ serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { TargetTag: &router.RoutingRule_Tag{ Tag: "direct", }, InboundTag: []string{"snif"}, }, { TargetTag: &router.RoutingRule_Tag{ Tag: "redir", }, InboundTag: []string{"http"}, }, }, }), serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + httpPort.String()) }, } client := &http.Client{ Transport: transport, } resp, err := client.Get("https://www.github.com/") common.Must(err) if resp.StatusCode != 200 { t.Error("unexpected status code: ", resp.StatusCode) } common.Must(resp.Write(ioutil.Discard)) } } func TestDialV2Ray(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, Inbound: []*core.InboundHandlerConfig{}, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) client, err := core.New(clientConfig) common.Must(err) conn, err := core.Dial(context.Background(), client, dest) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } ================================================ FILE: testing/scenarios/http_test.go ================================================ package scenarios import ( "bytes" "crypto/rand" "io" "io/ioutil" "net/http" "net/url" "testing" "time" "github.com/google/go-cmp/cmp" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/freedom" v2http "v2ray.com/core/proxy/http" v2httptest "v2ray.com/core/testing/servers/http" "v2ray.com/core/testing/servers/tcp" ) func TestHttpConformance(t *testing.T) { httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: make(map[string]http.HandlerFunc), } _, err := httpServer.Start() common.Must(err) defer httpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + serverPort.String()) }, } client := &http.Client{ Transport: transport, } resp, err := client.Get("http://127.0.0.1:" + httpServerPort.String()) common.Must(err) if resp.StatusCode != 200 { t.Fatal("status: ", resp.StatusCode) } content, err := ioutil.ReadAll(resp.Body) common.Must(err) if string(content) != "Home" { t.Fatal("body: ", string(content)) } } } func TestHttpError(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: func(msg []byte) []byte { return []byte{} }, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() time.AfterFunc(time.Second*2, func() { tcpServer.ShouldClose = true }) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + serverPort.String()) }, } client := &http.Client{ Transport: transport, } resp, err := client.Get("http://127.0.0.1:" + dest.Port.String()) common.Must(err) if resp.StatusCode != 503 { t.Error("status: ", resp.StatusCode) } } } func TestHttpConnectMethod(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + serverPort.String()) }, } client := &http.Client{ Transport: transport, } payload := make([]byte, 1024*64) common.Must2(rand.Read(payload)) req, err := http.NewRequest("Connect", "http://"+dest.NetAddr()+"/", bytes.NewReader(payload)) req.Header.Set("X-a", "b") req.Header.Set("X-b", "d") common.Must(err) resp, err := client.Do(req) common.Must(err) if resp.StatusCode != 200 { t.Fatal("status: ", resp.StatusCode) } content := make([]byte, len(payload)) common.Must2(io.ReadFull(resp.Body, content)) if r := cmp.Diff(content, xor(payload)); r != "" { t.Fatal(r) } } } func TestHttpPost(t *testing.T) { httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: map[string]http.HandlerFunc{ "/testpost": func(w http.ResponseWriter, r *http.Request) { payload, err := buf.ReadAllToBytes(r.Body) r.Body.Close() if err != nil { w.WriteHeader(500) w.Write([]byte("Unable to read all payload")) return } payload = xor(payload) w.Write(payload) }, }, } _, err := httpServer.Start() common.Must(err) defer httpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + serverPort.String()) }, } client := &http.Client{ Transport: transport, } payload := make([]byte, 1024*64) common.Must2(rand.Read(payload)) resp, err := client.Post("http://127.0.0.1:"+httpServerPort.String()+"/testpost", "application/x-www-form-urlencoded", bytes.NewReader(payload)) common.Must(err) if resp.StatusCode != 200 { t.Fatal("status: ", resp.StatusCode) } content, err := ioutil.ReadAll(resp.Body) common.Must(err) if r := cmp.Diff(content, xor(payload)); r != "" { t.Fatal(r) } } } func setProxyBasicAuth(req *http.Request, user, pass string) { req.SetBasicAuth(user, pass) req.Header.Set("Proxy-Authorization", req.Header.Get("Authorization")) req.Header.Del("Authorization") } func TestHttpBasicAuth(t *testing.T) { httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: make(map[string]http.HandlerFunc), } _, err := httpServer.Start() common.Must(err) defer httpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{ Accounts: map[string]string{ "a": "b", }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { transport := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("http://127.0.0.1:" + serverPort.String()) }, } client := &http.Client{ Transport: transport, } { resp, err := client.Get("http://127.0.0.1:" + httpServerPort.String()) common.Must(err) if resp.StatusCode != 407 { t.Fatal("status: ", resp.StatusCode) } } { req, err := http.NewRequest("GET", "http://127.0.0.1:"+httpServerPort.String(), nil) common.Must(err) setProxyBasicAuth(req, "a", "c") resp, err := client.Do(req) common.Must(err) if resp.StatusCode != 407 { t.Fatal("status: ", resp.StatusCode) } } { req, err := http.NewRequest("GET", "http://127.0.0.1:"+httpServerPort.String(), nil) common.Must(err) setProxyBasicAuth(req, "a", "b") resp, err := client.Do(req) common.Must(err) if resp.StatusCode != 200 { t.Fatal("status: ", resp.StatusCode) } content, err := ioutil.ReadAll(resp.Body) common.Must(err) if string(content) != "Home" { t.Fatal("body: ", string(content)) } } } } ================================================ FILE: testing/scenarios/policy_test.go ================================================ package scenarios import ( "io" "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" ) func startQuickClosingTCPServer() (net.Listener, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } go func() { for { conn, err := listener.Accept() if err != nil { break } b := make([]byte, 1024) conn.Read(b) conn.Close() } }() return listener, nil } func TestVMessClosing(t *testing.T) { tcpServer, err := startQuickClosingTCPServer() common.Must(err) defer tcpServer.Close() dest := net.DestinationFromAddr(tcpServer.Addr()) userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != io.EOF { t.Error(err) } } func TestZeroBuffer(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, Buffer: &policy.Policy_Buffer{ Connection: 0, }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Error(err) } } ================================================ FILE: testing/scenarios/reverse_test.go ================================================ package scenarios import ( "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/reverse" "v2ray.com/core/app/router" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/blackhole" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" ) func TestReverseProxy(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) externalPort := tcp.PickPort() reversePort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&reverse.Config{ PortalConfig: []*reverse.PortalConfig{ { Tag: "portal", Domain: "test.v2ray.com", }, }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ {Type: router.Domain_Full, Value: "test.v2ray.com"}, }, TargetTag: &router.RoutingRule_Tag{ Tag: "portal", }, }, { InboundTag: []string{"external"}, TargetTag: &router.RoutingRule_Tag{ Tag: "portal", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "external", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(externalPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(reversePort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&reverse.Config{ BridgeConfig: []*reverse.BridgeConfig{ { Tag: "bridge", Domain: "test.v2ray.com", }, }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ {Type: router.Domain_Full, Value: "test.v2ray.com"}, }, TargetTag: &router.RoutingRule_Tag{ Tag: "reverse", }, }, { InboundTag: []string{"bridge"}, TargetTag: &router.RoutingRule_Tag{ Tag: "freedom", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { Tag: "freedom", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, { Tag: "reverse", ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(reversePort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 32; i++ { errg.Go(testTCPConn(externalPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Fatal(err) } } func TestReverseProxyLongRunning(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) externalPort := tcp.PickPort() reversePort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Warning, ErrorLogType: log.LogType_Console, }), serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), serial.ToTypedMessage(&reverse.Config{ PortalConfig: []*reverse.PortalConfig{ { Tag: "portal", Domain: "test.v2ray.com", }, }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ {Type: router.Domain_Full, Value: "test.v2ray.com"}, }, TargetTag: &router.RoutingRule_Tag{ Tag: "portal", }, }, { InboundTag: []string{"external"}, TargetTag: &router.RoutingRule_Tag{ Tag: "portal", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "external", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(externalPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(reversePort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Warning, ErrorLogType: log.LogType_Console, }), serial.ToTypedMessage(&policy.Config{ Level: map[uint32]*policy.Policy{ 0: { Timeout: &policy.Policy_Timeout{ UplinkOnly: &policy.Second{Value: 0}, DownlinkOnly: &policy.Second{Value: 0}, }, }, }, }), serial.ToTypedMessage(&reverse.Config{ BridgeConfig: []*reverse.BridgeConfig{ { Tag: "bridge", Domain: "test.v2ray.com", }, }, }), serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { Domain: []*router.Domain{ {Type: router.Domain_Full, Value: "test.v2ray.com"}, }, TargetTag: &router.RoutingRule_Tag{ Tag: "reverse", }, }, { InboundTag: []string{"bridge"}, TargetTag: &router.RoutingRule_Tag{ Tag: "freedom", }, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { Tag: "freedom", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, { Tag: "reverse", ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(reversePort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) for i := 0; i < 4096; i++ { if err := testTCPConn(externalPort, 1024, time.Second*20)(); err != nil { t.Error(err) } } } ================================================ FILE: testing/scenarios/shadowsocks_test.go ================================================ package scenarios import ( "crypto/rand" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/errors" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/shadowsocks" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" ) func TestShadowsocksAES256TCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_AES_256_CFB, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Fatal(err) } } func TestShadowsocksAES128UDP(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_AES_128_CFB, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_UDP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(func() error { conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(clientPort), }) if err != nil { return err } defer conn.Close() payload := make([]byte, 1024) common.Must2(rand.Read(payload)) nBytes, err := conn.Write([]byte(payload)) if err != nil { return err } if nBytes != len(payload) { return errors.New("expect ", len(payload), " written, but actually ", nBytes) } response := readFrom(conn, time.Second*5, 1024) if r := cmp.Diff(response, xor(payload)); r != "" { return errors.New(r) } return nil }) } if err := errg.Wait(); err != nil { t.Fatal(err) } } func TestShadowsocksChacha20TCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_CHACHA20_IETF, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestShadowsocksChacha20Poly1305TCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_CHACHA20_POLY1305, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestShadowsocksAES256GCMTCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_AES_256_GCM, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestShadowsocksAES128GCMUDP(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_AES_128_GCM, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_UDP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testUDPConn(clientPort, 1024, time.Second*5)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestShadowsocksAES128GCMUDPMux(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_AES_128_GCM, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ MultiplexSettings: &proxyman.MultiplexingConfig{ Enabled: true, Concurrency: 8, }, }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testUDPConn(clientPort, 1024, time.Second*5)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestShadowsocksNone(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ Password: "shadowsocks-password", CipherType: shadowsocks.CipherType_NONE, }) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ User: &protocol.User{ Account: account, Level: 1, }, Network: []net.Network{net.Network_TCP}, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: account, }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Fatal(err) } } ================================================ FILE: testing/scenarios/socks_test.go ================================================ package scenarios import ( "testing" "time" xproxy "golang.org/x/net/proxy" socks4 "h12.io/socks" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/router" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/blackhole" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/socks" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" ) func TestSocksBridgeTCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_PASSWORD, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: false, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&socks.Account{ Username: "Test Account", Password: "Test Password", }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } func TestSocksBridageUDP(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_PASSWORD, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: true, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP, net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&socks.Account{ Username: "Test Account", Password: "Test Password", }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestSocksBridageUDPWithRouting(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&router.Config{ Rule: []*router.RoutingRule{ { TargetTag: &router.RoutingRule_Tag{ Tag: "out", }, InboundTag: []string{"socks"}, }, }, }), }, Inbound: []*core.InboundHandlerConfig{ { Tag: "socks", ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_NO_AUTH, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: true, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, { Tag: "out", ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP, net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{ Server: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil { t.Error(err) } } func TestSocksConformanceMod(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() authPort := tcp.PickPort() noAuthPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(authPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_PASSWORD, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: false, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(noAuthPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{ AuthType: socks.AuthType_NO_AUTH, Accounts: map[string]string{ "Test Account": "Test Password", }, Address: net.NewIPOrDomain(net.LocalHostIP), UdpEnabled: false, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } servers, err := InitializeServerConfigs(serverConfig) common.Must(err) defer CloseAllServers(servers) { noAuthDialer, err := xproxy.SOCKS5("tcp", net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr(), nil, xproxy.Direct) common.Must(err) conn, err := noAuthDialer.Dial("tcp", dest.NetAddr()) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } { authDialer, err := xproxy.SOCKS5("tcp", net.TCPDestination(net.LocalHostIP, authPort).NetAddr(), &xproxy.Auth{User: "Test Account", Password: "Test Password"}, xproxy.Direct) common.Must(err) conn, err := authDialer.Dial("tcp", dest.NetAddr()) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } { dialer := socks4.Dial("socks4://" + net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr()) conn, err := dialer("tcp", dest.NetAddr()) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } { dialer := socks4.Dial("socks4://" + net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr()) conn, err := dialer("tcp", net.TCPDestination(net.LocalHostIP, tcpServer.Port).NetAddr()) common.Must(err) defer conn.Close() if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil { t.Error(err) } } } ================================================ FILE: testing/scenarios/tls_test.go ================================================ package scenarios import ( "crypto/x509" "runtime" "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/http" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/websocket" ) func TestSimpleTLSConnection(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ AllowInsecure: true, }), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Fatal(err) } } func TestAutoIssuingCertificate(t *testing.T) { if runtime.GOOS == "windows" { // Not supported on Windows yet. return } if runtime.GOARCH == "arm64" { return } tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() caCert, err := cert.Generate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment|x509.KeyUsageCertSign)) common.Must(err) certPEM, keyPEM := caCert.ToPEM() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ Certificate: []*tls.Certificate{{ Certificate: certPEM, Key: keyPEM, Usage: tls.Certificate_AUTHORITY_ISSUE, }}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ ServerName: "v2ray.com", Certificate: []*tls.Certificate{{ Certificate: certPEM, Usage: tls.Certificate_AUTHORITY_VERIFY, }}, }), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) for i := 0; i < 10; i++ { if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } } func TestTLSOverKCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := udp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ AllowInsecure: true, }), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } func TestTLSOverWebSocket(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_WebSocket, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_WebSocket, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_WebSocket, Settings: serial.ToTypedMessage(&websocket.Config{}), }, }, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ AllowInsecure: true, }), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestHTTP2(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_HTTP, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_HTTP, Settings: serial.ToTypedMessage(&http.Config{ Host: []string{"v2ray.com"}, Path: "/testpath", }), }, }, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, }), }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_HTTP, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_HTTP, Settings: serial.ToTypedMessage(&http.Config{ Host: []string{"v2ray.com"}, Path: "/testpath", }), }, }, SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ AllowInsecure: true, }), }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Error(err) } } ================================================ FILE: testing/scenarios/transport_test.go ================================================ package scenarios import ( "os" "runtime" "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/domainsocket" "v2ray.com/core/transport/internet/headers/http" "v2ray.com/core/transport/internet/headers/wechat" "v2ray.com/core/transport/internet/quic" tcptransport "v2ray.com/core/transport/internet/tcp" ) func TestHttpConnectionHeader(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_TCP, Settings: serial.ToTypedMessage(&tcptransport.Config{ HeaderSettings: serial.ToTypedMessage(&http.Config{}), }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_TCP, Settings: serial.ToTypedMessage(&tcptransport.Config{ HeaderSettings: serial.ToTypedMessage(&http.Config{}), }), }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } func TestDomainSocket(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Not supported on windows") return } tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() const dsPath = "/tmp/ds_scenario" os.Remove(dsPath) userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_DomainSocket, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_DomainSocket, Settings: serial.ToTypedMessage(&domainsocket.Config{ Path: dsPath, }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_DomainSocket, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_DomainSocket, Settings: serial.ToTypedMessage(&domainsocket.Config{ Path: dsPath, }), }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } func TestVMessQuic(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ ProtocolName: "quic", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "quic", Settings: serial.ToTypedMessage(&quic.Config{ Header: serial.ToTypedMessage(&wechat.VideoConfig{}), Security: &protocol.SecurityConfig{ Type: protocol.SecurityType_NONE, }, }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ ProtocolName: "quic", TransportSettings: []*internet.TransportConfig{ { ProtocolName: "quic", Settings: serial.ToTypedMessage(&quic.Config{ Header: serial.ToTypedMessage(&wechat.VideoConfig{}), Security: &protocol.SecurityConfig{ Type: protocol.SecurityType_NONE, }, }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) if err != nil { t.Fatal("Failed to initialize all servers: ", err.Error()) } defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Error(err) } } ================================================ FILE: testing/scenarios/vmess_test.go ================================================ package scenarios import ( "os" "testing" "time" "golang.org/x/sync/errgroup" "v2ray.com/core" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/inbound" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/testing/servers/udp" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/kcp" ) func TestVMessDynamicPort(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, Detour: &inbound.DetourConfig{ To: "detour", }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: &net.PortRange{ From: uint32(serverPort + 1), To: uint32(serverPort + 100), }, Listen: net.NewIPOrDomain(net.LocalHostIP), AllocationStrategy: &proxyman.AllocationStrategy{ Type: proxyman.AllocationStrategy_Random, Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ Value: 2, }, Refresh: &proxyman.AllocationStrategy_AllocationStrategyRefresh{ Value: 5, }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{}), Tag: "detour", }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) for i := 0; i < 10; i++ { if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil { t.Error(err) } } } func TestVMessGCM(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) if err != nil { t.Fatal("Failed to initialize all servers: ", err.Error()) } defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessGCMReadv(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } const envName = "V2RAY_BUF_READV" common.Must(os.Setenv(envName, "enable")) defer os.Unsetenv(envName) servers, err := InitializeServerConfigs(serverConfig, clientConfig) if err != nil { t.Fatal("Failed to initialize all servers: ", err.Error()) } defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessGCMUDP(t *testing.T) { udpServer := udp.Server{ MsgProcessor: xor, } dest, err := udpServer.Start() common.Must(err) defer udpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testUDPConn(clientPort, 1024, time.Second*5)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessChacha20(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_CHACHA20_POLY1305, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessNone(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_NONE, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessKCP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := udp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Minute*2)) } if err := errg.Wait(); err != nil { t.Error(err) } } func TestVMessKCPLarge(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := udp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_MKCP, Settings: serial.ToTypedMessage(&kcp.Config{ ReadBuffer: &kcp.ReadBuffer{ Size: 512 * 1024, }, WriteBuffer: &kcp.WriteBuffer{ Size: 512 * 1024, }, UplinkCapacity: &kcp.UplinkCapacity{ Value: 20, }, DownlinkCapacity: &kcp.DownlinkCapacity{ Value: 20, }, }), }, }, }, }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ StreamSettings: &internet.StreamConfig{ Protocol: internet.TransportProtocol_MKCP, TransportSettings: []*internet.TransportConfig{ { Protocol: internet.TransportProtocol_MKCP, Settings: serial.ToTypedMessage(&kcp.Config{ ReadBuffer: &kcp.ReadBuffer{ Size: 512 * 1024, }, WriteBuffer: &kcp.WriteBuffer{ Size: 512 * 1024, }, UplinkCapacity: &kcp.UplinkCapacity{ Value: 20, }, DownlinkCapacity: &kcp.DownlinkCapacity{ Value: 20, }, }), }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) var errg errgroup.Group for i := 0; i < 2; i++ { errg.Go(testTCPConn(clientPort, 10240*1024, time.Minute*5)) } if err := errg.Wait(); err != nil { t.Error(err) } defer func() { <-time.After(5 * time.Second) CloseAllServers(servers) }() } func TestVMessGCMMux(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ MultiplexSettings: &proxyman.MultiplexingConfig{ Enabled: true, Concurrency: 4, }, }), ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) defer CloseAllServers(servers) for range "abcd" { var errg errgroup.Group for i := 0; i < 16; i++ { errg.Go(testTCPConn(clientPort, 10240, time.Second*20)) } if err := errg.Wait(); err != nil { t.Fatal(err) } time.Sleep(time.Second) } } func TestVMessGCMMuxUDP(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() udpServer := udp.Server{ MsgProcessor: xor, } udpDest, err := udpServer.Start() common.Must(err) defer udpServer.Close() userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(serverPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&inbound.Config{ User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, }), }, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&freedom.Config{}), }, }, } clientPort := tcp.PickPort() clientUDPPort := udp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ ErrorLogLevel: clog.Severity_Debug, ErrorLogType: log.LogType_Console, }), }, Inbound: []*core.InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(dest.Address), Port: uint32(dest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP}, }, }), }, { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(clientUDPPort), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(udpDest.Address), Port: uint32(udpDest.Port), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_UDP}, }, }), }, }, Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ MultiplexSettings: &proxyman.MultiplexingConfig{ Enabled: true, Concurrency: 4, }, }), ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), AlterId: 64, SecuritySettings: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }), }, }, }, }, }), }, }, } servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) for range "abcd" { var errg errgroup.Group for i := 0; i < 16; i++ { errg.Go(testTCPConn(clientPort, 10240, time.Second*20)) errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10)) } if err := errg.Wait(); err != nil { t.Error(err) } time.Sleep(time.Second) } defer func() { <-time.After(5 * time.Second) CloseAllServers(servers) }() } ================================================ FILE: testing/servers/http/http.go ================================================ package tcp import ( "net/http" "v2ray.com/core/common/net" ) type Server struct { Port net.Port PathHandler map[string]http.HandlerFunc server *http.Server } func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) { if req.URL.Path == "/" { resp.Header().Set("Content-Type", "text/plain; charset=utf-8") resp.WriteHeader(http.StatusOK) resp.Write([]byte("Home")) return } handler, found := s.PathHandler[req.URL.Path] if found { handler(resp, req) } } func (s *Server) Start() (net.Destination, error) { s.server = &http.Server{ Addr: "127.0.0.1:" + s.Port.String(), Handler: s, } go s.server.ListenAndServe() return net.TCPDestination(net.LocalHostIP, net.Port(s.Port)), nil } func (s *Server) Close() error { return s.server.Close() } ================================================ FILE: testing/servers/tcp/port.go ================================================ package tcp import ( "v2ray.com/core/common" "v2ray.com/core/common/net" ) // PickPort returns an unused TCP port in the system. The port returned is highly likely to be unused, but not guaranteed. func PickPort() net.Port { listener, err := net.Listen("tcp4", "127.0.0.1:0") common.Must(err) defer listener.Close() addr := listener.Addr().(*net.TCPAddr) return net.Port(addr.Port) } ================================================ FILE: testing/servers/tcp/tcp.go ================================================ package tcp import ( "context" "fmt" "io" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/task" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/pipe" ) type Server struct { Port net.Port MsgProcessor func(msg []byte) []byte ShouldClose bool SendFirst []byte Listen net.Address listener net.Listener } func (server *Server) Start() (net.Destination, error) { return server.StartContext(context.Background(), nil) } func (server *Server) StartContext(ctx context.Context, sockopt *internet.SocketConfig) (net.Destination, error) { listenerAddr := server.Listen if listenerAddr == nil { listenerAddr = net.LocalHostIP } listener, err := internet.ListenSystem(ctx, &net.TCPAddr{ IP: listenerAddr.IP(), Port: int(server.Port), }, sockopt) if err != nil { return net.Destination{}, err } localAddr := listener.Addr().(*net.TCPAddr) server.Port = net.Port(localAddr.Port) server.listener = listener go server.acceptConnections(listener.(*net.TCPListener)) return net.TCPDestination(net.IPAddress(localAddr.IP), net.Port(localAddr.Port)), nil } func (server *Server) acceptConnections(listener *net.TCPListener) { for { conn, err := listener.Accept() if err != nil { fmt.Printf("Failed accept TCP connection: %v\n", err) return } go server.handleConnection(conn) } } func (server *Server) handleConnection(conn net.Conn) { if len(server.SendFirst) > 0 { conn.Write(server.SendFirst) } pReader, pWriter := pipe.New(pipe.WithoutSizeLimit()) err := task.Run(context.Background(), func() error { defer pWriter.Close() // nolint: errcheck for { b := buf.New() if _, err := b.ReadFrom(conn); err != nil { if err == io.EOF { return nil } return err } copy(b.Bytes(), server.MsgProcessor(b.Bytes())) if err := pWriter.WriteMultiBuffer(buf.MultiBuffer{b}); err != nil { return err } } }, func() error { defer pReader.Interrupt() w := buf.NewWriter(conn) for { mb, err := pReader.ReadMultiBuffer() if err != nil { if err == io.EOF { return nil } return err } if err := w.WriteMultiBuffer(mb); err != nil { return err } } }) if err != nil { fmt.Println("failed to transfer data: ", err.Error()) } conn.Close() // nolint: errcheck } func (server *Server) Close() error { return server.listener.Close() } ================================================ FILE: testing/servers/udp/port.go ================================================ package udp import ( "v2ray.com/core/common" "v2ray.com/core/common/net" ) // PickPort returns an unused UDP port in the system. The port returned is highly likely to be unused, but not guaranteed. func PickPort() net.Port { conn, err := net.ListenUDP("udp4", &net.UDPAddr{ IP: net.LocalHostIP.IP(), Port: 0, }) common.Must(err) defer conn.Close() addr := conn.LocalAddr().(*net.UDPAddr) return net.Port(addr.Port) } ================================================ FILE: testing/servers/udp/udp.go ================================================ package udp import ( "fmt" "v2ray.com/core/common/net" ) type Server struct { Port net.Port MsgProcessor func(msg []byte) []byte accepting bool conn *net.UDPConn } func (server *Server) Start() (net.Destination, error) { conn, err := net.ListenUDP("udp", &net.UDPAddr{ IP: []byte{127, 0, 0, 1}, Port: int(server.Port), Zone: "", }) if err != nil { return net.Destination{}, err } server.Port = net.Port(conn.LocalAddr().(*net.UDPAddr).Port) fmt.Println("UDP server started on port ", server.Port) server.conn = conn go server.handleConnection(conn) localAddr := conn.LocalAddr().(*net.UDPAddr) return net.UDPDestination(net.IPAddress(localAddr.IP), net.Port(localAddr.Port)), nil } func (server *Server) handleConnection(conn *net.UDPConn) { server.accepting = true for server.accepting { buffer := make([]byte, 2*1024) nBytes, addr, err := conn.ReadFromUDP(buffer) if err != nil { fmt.Printf("Failed to read from UDP: %v\n", err) continue } response := server.MsgProcessor(buffer[:nBytes]) if _, err := conn.WriteToUDP(response, addr); err != nil { fmt.Println("Failed to write to UDP: ", err.Error()) } } } func (server *Server) Close() error { server.accepting = false return server.conn.Close() } ================================================ FILE: transport/config.go ================================================ package transport import ( "v2ray.com/core/transport/internet" ) // Apply applies this Config. func (c *Config) Apply() error { if c == nil { return nil } return internet.ApplyGlobalTransportSettings(c.TransportSettings) } ================================================ FILE: transport/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/config.proto package transport import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" internet "v2ray.com/core/transport/internet" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Global transport settings. This affects all type of connections that go // through V2Ray. Deprecated. Use each settings in StreamConfig. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields TransportSettings []*internet.TransportConfig `protobuf:"bytes,1,rep,name=transport_settings,json=transportSettings,proto3" json:"transport_settings,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetTransportSettings() []*internet.TransportConfig { if x != nil { return x.TransportSettings } return nil } var File_transport_config_proto protoreflect.FileDescriptor var file_transport_config_proto_rawDesc = []byte{ 0x0a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x67, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5d, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x4d, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x01, 0x5a, 0x18, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0xaa, 0x02, 0x14, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_config_proto_rawDescOnce sync.Once file_transport_config_proto_rawDescData = file_transport_config_proto_rawDesc ) func file_transport_config_proto_rawDescGZIP() []byte { file_transport_config_proto_rawDescOnce.Do(func() { file_transport_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_config_proto_rawDescData) }) return file_transport_config_proto_rawDescData } var file_transport_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.Config (*internet.TransportConfig)(nil), // 1: v2ray.core.transport.internet.TransportConfig } var file_transport_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.transport.Config.transport_settings:type_name -> v2ray.core.transport.internet.TransportConfig 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_transport_config_proto_init() } func file_transport_config_proto_init() { if File_transport_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_config_proto_goTypes, DependencyIndexes: file_transport_config_proto_depIdxs, MessageInfos: file_transport_config_proto_msgTypes, }.Build() File_transport_config_proto = out.File file_transport_config_proto_rawDesc = nil file_transport_config_proto_goTypes = nil file_transport_config_proto_depIdxs = nil } ================================================ FILE: transport/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport; option csharp_namespace = "V2Ray.Core.Transport"; option go_package = "v2ray.com/core/transport"; option java_package = "com.v2ray.core.transport"; option java_multiple_files = true; import "transport/internet/config.proto"; // Global transport settings. This affects all type of connections that go // through V2Ray. Deprecated. Use each settings in StreamConfig. message Config { repeated v2ray.core.transport.internet.TransportConfig transport_settings = 1; } ================================================ FILE: transport/internet/config.go ================================================ package internet import ( "v2ray.com/core/common/serial" "v2ray.com/core/features" ) type ConfigCreator func() interface{} var ( globalTransportConfigCreatorCache = make(map[string]ConfigCreator) globalTransportSettings []*TransportConfig ) const unknownProtocol = "unknown" func transportProtocolToString(protocol TransportProtocol) string { switch protocol { case TransportProtocol_TCP: return "tcp" case TransportProtocol_UDP: return "udp" case TransportProtocol_HTTP: return "http" case TransportProtocol_MKCP: return "mkcp" case TransportProtocol_WebSocket: return "websocket" case TransportProtocol_DomainSocket: return "domainsocket" default: return unknownProtocol } } func RegisterProtocolConfigCreator(name string, creator ConfigCreator) error { if _, found := globalTransportConfigCreatorCache[name]; found { return newError("protocol ", name, " is already registered").AtError() } globalTransportConfigCreatorCache[name] = creator return nil } func CreateTransportConfig(name string) (interface{}, error) { creator, ok := globalTransportConfigCreatorCache[name] if !ok { return nil, newError("unknown transport protocol: ", name) } return creator(), nil } func (c *TransportConfig) GetTypedSettings() (interface{}, error) { return c.Settings.GetInstance() } func (c *TransportConfig) GetUnifiedProtocolName() string { if len(c.ProtocolName) > 0 { return c.ProtocolName } return transportProtocolToString(c.Protocol) } func (c *StreamConfig) GetEffectiveProtocol() string { if c == nil { return "tcp" } if len(c.ProtocolName) > 0 { return c.ProtocolName } return transportProtocolToString(c.Protocol) } func (c *StreamConfig) GetEffectiveTransportSettings() (interface{}, error) { protocol := c.GetEffectiveProtocol() return c.GetTransportSettingsFor(protocol) } func (c *StreamConfig) GetTransportSettingsFor(protocol string) (interface{}, error) { if c != nil { for _, settings := range c.TransportSettings { if settings.GetUnifiedProtocolName() == protocol { return settings.GetTypedSettings() } } } for _, settings := range globalTransportSettings { if settings.GetUnifiedProtocolName() == protocol { return settings.GetTypedSettings() } } return CreateTransportConfig(protocol) } func (c *StreamConfig) GetEffectiveSecuritySettings() (interface{}, error) { for _, settings := range c.SecuritySettings { if settings.Type == c.SecurityType { return settings.GetInstance() } } return serial.GetInstance(c.SecurityType) } func (c *StreamConfig) HasSecuritySettings() bool { return len(c.SecurityType) > 0 } func ApplyGlobalTransportSettings(settings []*TransportConfig) error { features.PrintDeprecatedFeatureWarning("global transport settings") globalTransportSettings = settings return nil } func (c *ProxyConfig) HasTag() bool { return c != nil && len(c.Tag) > 0 } func (m SocketConfig_TProxyMode) IsEnabled() bool { return m != SocketConfig_Off } ================================================ FILE: transport/internet/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/config.proto package internet import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type TransportProtocol int32 const ( TransportProtocol_TCP TransportProtocol = 0 TransportProtocol_UDP TransportProtocol = 1 TransportProtocol_MKCP TransportProtocol = 2 TransportProtocol_WebSocket TransportProtocol = 3 TransportProtocol_HTTP TransportProtocol = 4 TransportProtocol_DomainSocket TransportProtocol = 5 ) // Enum value maps for TransportProtocol. var ( TransportProtocol_name = map[int32]string{ 0: "TCP", 1: "UDP", 2: "MKCP", 3: "WebSocket", 4: "HTTP", 5: "DomainSocket", } TransportProtocol_value = map[string]int32{ "TCP": 0, "UDP": 1, "MKCP": 2, "WebSocket": 3, "HTTP": 4, "DomainSocket": 5, } ) func (x TransportProtocol) Enum() *TransportProtocol { p := new(TransportProtocol) *p = x return p } func (x TransportProtocol) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (TransportProtocol) Descriptor() protoreflect.EnumDescriptor { return file_transport_internet_config_proto_enumTypes[0].Descriptor() } func (TransportProtocol) Type() protoreflect.EnumType { return &file_transport_internet_config_proto_enumTypes[0] } func (x TransportProtocol) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use TransportProtocol.Descriptor instead. func (TransportProtocol) EnumDescriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{0} } type SocketConfig_TCPFastOpenState int32 const ( // AsIs is to leave the current TFO state as is, unmodified. SocketConfig_AsIs SocketConfig_TCPFastOpenState = 0 // Enable is for enabling TFO explictly. SocketConfig_Enable SocketConfig_TCPFastOpenState = 1 // Disable is for disabling TFO explictly. SocketConfig_Disable SocketConfig_TCPFastOpenState = 2 ) // Enum value maps for SocketConfig_TCPFastOpenState. var ( SocketConfig_TCPFastOpenState_name = map[int32]string{ 0: "AsIs", 1: "Enable", 2: "Disable", } SocketConfig_TCPFastOpenState_value = map[string]int32{ "AsIs": 0, "Enable": 1, "Disable": 2, } ) func (x SocketConfig_TCPFastOpenState) Enum() *SocketConfig_TCPFastOpenState { p := new(SocketConfig_TCPFastOpenState) *p = x return p } func (x SocketConfig_TCPFastOpenState) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SocketConfig_TCPFastOpenState) Descriptor() protoreflect.EnumDescriptor { return file_transport_internet_config_proto_enumTypes[1].Descriptor() } func (SocketConfig_TCPFastOpenState) Type() protoreflect.EnumType { return &file_transport_internet_config_proto_enumTypes[1] } func (x SocketConfig_TCPFastOpenState) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SocketConfig_TCPFastOpenState.Descriptor instead. func (SocketConfig_TCPFastOpenState) EnumDescriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{3, 0} } type SocketConfig_TProxyMode int32 const ( // TProxy is off. SocketConfig_Off SocketConfig_TProxyMode = 0 // TProxy mode. SocketConfig_TProxy SocketConfig_TProxyMode = 1 // Redirect mode. SocketConfig_Redirect SocketConfig_TProxyMode = 2 ) // Enum value maps for SocketConfig_TProxyMode. var ( SocketConfig_TProxyMode_name = map[int32]string{ 0: "Off", 1: "TProxy", 2: "Redirect", } SocketConfig_TProxyMode_value = map[string]int32{ "Off": 0, "TProxy": 1, "Redirect": 2, } ) func (x SocketConfig_TProxyMode) Enum() *SocketConfig_TProxyMode { p := new(SocketConfig_TProxyMode) *p = x return p } func (x SocketConfig_TProxyMode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor { return file_transport_internet_config_proto_enumTypes[2].Descriptor() } func (SocketConfig_TProxyMode) Type() protoreflect.EnumType { return &file_transport_internet_config_proto_enumTypes[2] } func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead. func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{3, 1} } type TransportConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Type of network that this settings supports. // Deprecated. Use the string form below. Protocol TransportProtocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=v2ray.core.transport.internet.TransportProtocol" json:"protocol,omitempty"` // Type of network that this settings supports. ProtocolName string `protobuf:"bytes,3,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"` // Specific settings. Must be of the transports. Settings *serial.TypedMessage `protobuf:"bytes,2,opt,name=settings,proto3" json:"settings,omitempty"` } func (x *TransportConfig) Reset() { *x = TransportConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TransportConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransportConfig) ProtoMessage() {} func (x *TransportConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransportConfig.ProtoReflect.Descriptor instead. func (*TransportConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{0} } func (x *TransportConfig) GetProtocol() TransportProtocol { if x != nil { return x.Protocol } return TransportProtocol_TCP } func (x *TransportConfig) GetProtocolName() string { if x != nil { return x.ProtocolName } return "" } func (x *TransportConfig) GetSettings() *serial.TypedMessage { if x != nil { return x.Settings } return nil } type StreamConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Effective network. Deprecated. Use the string form below. // // Deprecated: Do not use. Protocol TransportProtocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=v2ray.core.transport.internet.TransportProtocol" json:"protocol,omitempty"` // Effective network. ProtocolName string `protobuf:"bytes,5,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"` TransportSettings []*TransportConfig `protobuf:"bytes,2,rep,name=transport_settings,json=transportSettings,proto3" json:"transport_settings,omitempty"` // Type of security. Must be a message name of the settings proto. SecurityType string `protobuf:"bytes,3,opt,name=security_type,json=securityType,proto3" json:"security_type,omitempty"` // Settings for transport security. For now the only choice is TLS. SecuritySettings []*serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"` SocketSettings *SocketConfig `protobuf:"bytes,6,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"` } func (x *StreamConfig) Reset() { *x = StreamConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StreamConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*StreamConfig) ProtoMessage() {} func (x *StreamConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StreamConfig.ProtoReflect.Descriptor instead. func (*StreamConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{1} } // Deprecated: Do not use. func (x *StreamConfig) GetProtocol() TransportProtocol { if x != nil { return x.Protocol } return TransportProtocol_TCP } func (x *StreamConfig) GetProtocolName() string { if x != nil { return x.ProtocolName } return "" } func (x *StreamConfig) GetTransportSettings() []*TransportConfig { if x != nil { return x.TransportSettings } return nil } func (x *StreamConfig) GetSecurityType() string { if x != nil { return x.SecurityType } return "" } func (x *StreamConfig) GetSecuritySettings() []*serial.TypedMessage { if x != nil { return x.SecuritySettings } return nil } func (x *StreamConfig) GetSocketSettings() *SocketConfig { if x != nil { return x.SocketSettings } return nil } type ProxyConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *ProxyConfig) Reset() { *x = ProxyConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ProxyConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ProxyConfig) ProtoMessage() {} func (x *ProxyConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead. func (*ProxyConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{2} } func (x *ProxyConfig) GetTag() string { if x != nil { return x.Tag } return "" } // SocketConfig is options to be applied on network sockets. type SocketConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Mark of the connection. If non-zero, the value will be set to SO_MARK. Mark int32 `protobuf:"varint,1,opt,name=mark,proto3" json:"mark,omitempty"` // TFO is the state of TFO settings. Tfo SocketConfig_TCPFastOpenState `protobuf:"varint,2,opt,name=tfo,proto3,enum=v2ray.core.transport.internet.SocketConfig_TCPFastOpenState" json:"tfo,omitempty"` // TProxy is for enabling TProxy socket option. Tproxy SocketConfig_TProxyMode `protobuf:"varint,3,opt,name=tproxy,proto3,enum=v2ray.core.transport.internet.SocketConfig_TProxyMode" json:"tproxy,omitempty"` // ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket // option. This option is for UDP only. ReceiveOriginalDestAddress bool `protobuf:"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3" json:"receive_original_dest_address,omitempty"` BindAddress []byte `protobuf:"bytes,5,opt,name=bind_address,json=bindAddress,proto3" json:"bind_address,omitempty"` BindPort uint32 `protobuf:"varint,6,opt,name=bind_port,json=bindPort,proto3" json:"bind_port,omitempty"` } func (x *SocketConfig) Reset() { *x = SocketConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SocketConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SocketConfig) ProtoMessage() {} func (x *SocketConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead. func (*SocketConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{3} } func (x *SocketConfig) GetMark() int32 { if x != nil { return x.Mark } return 0 } func (x *SocketConfig) GetTfo() SocketConfig_TCPFastOpenState { if x != nil { return x.Tfo } return SocketConfig_AsIs } func (x *SocketConfig) GetTproxy() SocketConfig_TProxyMode { if x != nil { return x.Tproxy } return SocketConfig_Off } func (x *SocketConfig) GetReceiveOriginalDestAddress() bool { if x != nil { return x.ReceiveOriginalDestAddress } return false } func (x *SocketConfig) GetBindAddress() []byte { if x != nil { return x.BindAddress } return nil } func (x *SocketConfig) GetBindPort() uint32 { if x != nil { return x.BindPort } return 0 } var File_transport_internet_config_proto protoreflect.FileDescriptor var file_transport_internet_config_proto_rawDesc = []byte{ 0x0a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc8, 0x01, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xb4, 0x03, 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5d, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x53, 0x0a, 0x11, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x1f, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xad, 0x03, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x4e, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x43, 0x50, 0x46, 0x61, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x4e, 0x0a, 0x06, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x35, 0x0a, 0x10, 0x54, 0x43, 0x50, 0x46, 0x61, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x10, 0x02, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x5a, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x05, 0x42, 0x68, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x1d, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_config_proto_rawDescOnce sync.Once file_transport_internet_config_proto_rawDescData = file_transport_internet_config_proto_rawDesc ) func file_transport_internet_config_proto_rawDescGZIP() []byte { file_transport_internet_config_proto_rawDescOnce.Do(func() { file_transport_internet_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_config_proto_rawDescData) }) return file_transport_internet_config_proto_rawDescData } var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_transport_internet_config_proto_goTypes = []interface{}{ (TransportProtocol)(0), // 0: v2ray.core.transport.internet.TransportProtocol (SocketConfig_TCPFastOpenState)(0), // 1: v2ray.core.transport.internet.SocketConfig.TCPFastOpenState (SocketConfig_TProxyMode)(0), // 2: v2ray.core.transport.internet.SocketConfig.TProxyMode (*TransportConfig)(nil), // 3: v2ray.core.transport.internet.TransportConfig (*StreamConfig)(nil), // 4: v2ray.core.transport.internet.StreamConfig (*ProxyConfig)(nil), // 5: v2ray.core.transport.internet.ProxyConfig (*SocketConfig)(nil), // 6: v2ray.core.transport.internet.SocketConfig (*serial.TypedMessage)(nil), // 7: v2ray.core.common.serial.TypedMessage } var file_transport_internet_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.transport.internet.TransportConfig.protocol:type_name -> v2ray.core.transport.internet.TransportProtocol 7, // 1: v2ray.core.transport.internet.TransportConfig.settings:type_name -> v2ray.core.common.serial.TypedMessage 0, // 2: v2ray.core.transport.internet.StreamConfig.protocol:type_name -> v2ray.core.transport.internet.TransportProtocol 3, // 3: v2ray.core.transport.internet.StreamConfig.transport_settings:type_name -> v2ray.core.transport.internet.TransportConfig 7, // 4: v2ray.core.transport.internet.StreamConfig.security_settings:type_name -> v2ray.core.common.serial.TypedMessage 6, // 5: v2ray.core.transport.internet.StreamConfig.socket_settings:type_name -> v2ray.core.transport.internet.SocketConfig 1, // 6: v2ray.core.transport.internet.SocketConfig.tfo:type_name -> v2ray.core.transport.internet.SocketConfig.TCPFastOpenState 2, // 7: v2ray.core.transport.internet.SocketConfig.tproxy:type_name -> v2ray.core.transport.internet.SocketConfig.TProxyMode 8, // [8:8] is the sub-list for method output_type 8, // [8:8] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name } func init() { file_transport_internet_config_proto_init() } func file_transport_internet_config_proto_init() { if File_transport_internet_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TransportConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ProxyConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SocketConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_config_proto_rawDesc, NumEnums: 3, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_config_proto_goTypes, DependencyIndexes: file_transport_internet_config_proto_depIdxs, EnumInfos: file_transport_internet_config_proto_enumTypes, MessageInfos: file_transport_internet_config_proto_msgTypes, }.Build() File_transport_internet_config_proto = out.File file_transport_internet_config_proto_rawDesc = nil file_transport_internet_config_proto_goTypes = nil file_transport_internet_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet; option csharp_namespace = "V2Ray.Core.Transport.Internet"; option go_package = "v2ray.com/core/transport/internet"; option java_package = "com.v2ray.core.transport.internet"; option java_multiple_files = true; import "common/serial/typed_message.proto"; enum TransportProtocol { TCP = 0; UDP = 1; MKCP = 2; WebSocket = 3; HTTP = 4; DomainSocket = 5; } message TransportConfig { // Type of network that this settings supports. // Deprecated. Use the string form below. TransportProtocol protocol = 1; // Type of network that this settings supports. string protocol_name = 3; // Specific settings. Must be of the transports. v2ray.core.common.serial.TypedMessage settings = 2; } message StreamConfig { // Effective network. Deprecated. Use the string form below. TransportProtocol protocol = 1 [deprecated = true]; // Effective network. string protocol_name = 5; repeated TransportConfig transport_settings = 2; // Type of security. Must be a message name of the settings proto. string security_type = 3; // Settings for transport security. For now the only choice is TLS. repeated v2ray.core.common.serial.TypedMessage security_settings = 4; SocketConfig socket_settings = 6; } message ProxyConfig { string tag = 1; } // SocketConfig is options to be applied on network sockets. message SocketConfig { // Mark of the connection. If non-zero, the value will be set to SO_MARK. int32 mark = 1; enum TCPFastOpenState { // AsIs is to leave the current TFO state as is, unmodified. AsIs = 0; // Enable is for enabling TFO explictly. Enable = 1; // Disable is for disabling TFO explictly. Disable = 2; } // TFO is the state of TFO settings. TCPFastOpenState tfo = 2; enum TProxyMode { // TProxy is off. Off = 0; // TProxy mode. TProxy = 1; // Redirect mode. Redirect = 2; } // TProxy is for enabling TProxy socket option. TProxyMode tproxy = 3; // ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket // option. This option is for UDP only. bool receive_original_dest_address = 4; bytes bind_address = 5; uint32 bind_port = 6; } ================================================ FILE: transport/internet/connection.go ================================================ package internet import ( "net" "v2ray.com/core/features/stats" ) type Connection interface { net.Conn } type StatCouterConnection struct { Connection ReadCounter stats.Counter WriteCounter stats.Counter } func (c *StatCouterConnection) Read(b []byte) (int, error) { nBytes, err := c.Connection.Read(b) if c.ReadCounter != nil { c.ReadCounter.Add(int64(nBytes)) } return nBytes, err } func (c *StatCouterConnection) Write(b []byte) (int, error) { nBytes, err := c.Connection.Write(b) if c.WriteCounter != nil { c.WriteCounter.Add(int64(nBytes)) } return nBytes, err } ================================================ FILE: transport/internet/dialer.go ================================================ package internet import ( "context" "v2ray.com/core/common/net" "v2ray.com/core/common/session" ) // Dialer is the interface for dialing outbound connections. type Dialer interface { // Dial dials a system connection to the given destination. Dial(ctx context.Context, destination net.Destination) (Connection, error) // Address returns the address used by this Dialer. Maybe nil if not known. Address() net.Address } // dialFunc is an interface to dial network connection to a specific destination. type dialFunc func(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (Connection, error) var ( transportDialerCache = make(map[string]dialFunc) ) // RegisterTransportDialer registers a Dialer with given name. func RegisterTransportDialer(protocol string, dialer dialFunc) error { if _, found := transportDialerCache[protocol]; found { return newError(protocol, " dialer already registered").AtError() } transportDialerCache[protocol] = dialer return nil } // Dial dials a internet connection towards the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (Connection, error) { if dest.Network == net.Network_TCP { if streamSettings == nil { s, err := ToMemoryStreamConfig(nil) if err != nil { return nil, newError("failed to create default stream settings").Base(err) } streamSettings = s } protocol := streamSettings.ProtocolName dialer := transportDialerCache[protocol] if dialer == nil { return nil, newError(protocol, " dialer not registered").AtError() } return dialer(ctx, dest, streamSettings) } if dest.Network == net.Network_UDP { udpDialer := transportDialerCache["udp"] if udpDialer == nil { return nil, newError("UDP dialer not registered").AtError() } return udpDialer(ctx, dest, streamSettings) } return nil, newError("unknown network ", dest.Network) } // DialSystem calls system dialer to create a network connection. func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) { var src net.Address if outbound := session.OutboundFromContext(ctx); outbound != nil { src = outbound.Gateway } return effectiveSystemDialer.Dial(ctx, src, dest, sockopt) } ================================================ FILE: transport/internet/dialer_test.go ================================================ package internet_test import ( "context" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/testing/servers/tcp" . "v2ray.com/core/transport/internet" ) func TestDialWithLocalAddr(t *testing.T) { server := &tcp.Server{} dest, err := server.Start() common.Must(err) defer server.Close() conn, err := DialSystem(context.Background(), net.TCPDestination(net.LocalHostIP, dest.Port), nil) common.Must(err) if r := cmp.Diff(conn.RemoteAddr().String(), "127.0.0.1:"+dest.Port.String()); r != "" { t.Error(r) } conn.Close() } ================================================ FILE: transport/internet/domainsocket/config.go ================================================ // +build !confonly package domainsocket import ( "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) const protocolName = "domainsocket" const sizeofSunPath = 108 func (c *Config) GetUnixAddr() (*net.UnixAddr, error) { path := c.Path if path == "" { return nil, newError("empty domain socket path") } if c.Abstract && path[0] != '@' { path = "@" + path } if c.Abstract && c.Padding { raw := []byte(path) addr := make([]byte, sizeofSunPath) for i, c := range raw { addr[i] = c } path = string(addr) } return &net.UnixAddr{ Name: path, Net: "unix", }, nil } func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/domainsocket/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/domainsocket/config.proto package domainsocket import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Path of the domain socket. This overrides the IP/Port parameter from // upstream caller. Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` // Abstract speicifies whether to use abstract namespace or not. // Traditionally Unix domain socket is file system based. Abstract domain // socket can be used without acquiring file lock. Abstract bool `protobuf:"varint,2,opt,name=abstract,proto3" json:"abstract,omitempty"` // Some apps, eg. haproxy, use the full length of sockaddr_un.sun_path to // connect(2) or bind(2) when using abstract UDS. Padding bool `protobuf:"varint,3,opt,name=padding,proto3" json:"padding,omitempty"` AcceptProxyProtocol bool `protobuf:"varint,4,opt,name=acceptProxyProtocol,proto3" json:"acceptProxyProtocol,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_domainsocket_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_domainsocket_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_domainsocket_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetPath() string { if x != nil { return x.Path } return "" } func (x *Config) GetAbstract() bool { if x != nil { return x.Abstract } return false } func (x *Config) GetPadding() bool { if x != nil { return x.Padding } return false } func (x *Config) GetAcceptProxyProtocol() bool { if x != nil { return x.AcceptProxyProtocol } return false } var File_transport_internet_domainsocket_config_proto protoreflect.FileDescriptor var file_transport_internet_domainsocket_config_proto_rawDesc = []byte{ 0x0a, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_domainsocket_config_proto_rawDescOnce sync.Once file_transport_internet_domainsocket_config_proto_rawDescData = file_transport_internet_domainsocket_config_proto_rawDesc ) func file_transport_internet_domainsocket_config_proto_rawDescGZIP() []byte { file_transport_internet_domainsocket_config_proto_rawDescOnce.Do(func() { file_transport_internet_domainsocket_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_domainsocket_config_proto_rawDescData) }) return file_transport_internet_domainsocket_config_proto_rawDescData } var file_transport_internet_domainsocket_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_domainsocket_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.domainsocket.Config } var file_transport_internet_domainsocket_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_domainsocket_config_proto_init() } func file_transport_internet_domainsocket_config_proto_init() { if File_transport_internet_domainsocket_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_domainsocket_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_domainsocket_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_domainsocket_config_proto_goTypes, DependencyIndexes: file_transport_internet_domainsocket_config_proto_depIdxs, MessageInfos: file_transport_internet_domainsocket_config_proto_msgTypes, }.Build() File_transport_internet_domainsocket_config_proto = out.File file_transport_internet_domainsocket_config_proto_rawDesc = nil file_transport_internet_domainsocket_config_proto_goTypes = nil file_transport_internet_domainsocket_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/domainsocket/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.domainsocket; option csharp_namespace = "V2Ray.Core.Transport.Internet.DomainSocket"; option go_package = "v2ray.com/core/transport/internet/domainsocket"; option java_package = "com.v2ray.core.transport.internet.domainsocket"; option java_multiple_files = true; message Config { // Path of the domain socket. This overrides the IP/Port parameter from // upstream caller. string path = 1; // Abstract speicifies whether to use abstract namespace or not. // Traditionally Unix domain socket is file system based. Abstract domain // socket can be used without acquiring file lock. bool abstract = 2; // Some apps, eg. haproxy, use the full length of sockaddr_un.sun_path to // connect(2) or bind(2) when using abstract UDS. bool padding = 3; bool acceptProxyProtocol = 4; } ================================================ FILE: transport/internet/domainsocket/dial.go ================================================ // +build !windows // +build !wasm // +build !confonly package domainsocket import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { settings := streamSettings.ProtocolSettings.(*Config) addr, err := settings.GetUnixAddr() if err != nil { return nil, err } conn, err := net.DialUnix("unix", nil, addr) if err != nil { return nil, newError("failed to dial unix: ", settings.Path).Base(err).AtWarning() } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { return tls.Client(conn, config.GetTLSConfig(tls.WithDestination(dest))), nil } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { return xtls.Client(conn, config.GetXTLSConfig(xtls.WithDestination(dest))), nil } return conn, nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } ================================================ FILE: transport/internet/domainsocket/errgen.go ================================================ package domainsocket //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/domainsocket/errors.generated.go ================================================ package domainsocket import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/domainsocket/listener.go ================================================ // +build !windows // +build !wasm // +build !confonly package domainsocket import ( "context" gotls "crypto/tls" "os" "strings" "github.com/pires/go-proxyproto" goxtls "github.com/xtls/go" "golang.org/x/sys/unix" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) type Listener struct { addr *net.UnixAddr ln net.Listener tlsConfig *gotls.Config xtlsConfig *goxtls.Config config *Config addConn internet.ConnHandler locker *fileLocker } func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { settings := streamSettings.ProtocolSettings.(*Config) addr, err := settings.GetUnixAddr() if err != nil { return nil, err } unixListener, err := net.ListenUnix("unix", addr) if err != nil { return nil, newError("failed to listen domain socket").Base(err).AtWarning() } var ln *Listener if settings.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } ln = &Listener{ addr: addr, ln: &proxyproto.Listener{Listener: unixListener, Policy: policyFunc}, config: settings, addConn: handler, } newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx)) } else { ln = &Listener{ addr: addr, ln: unixListener, config: settings, addConn: handler, } } if !settings.Abstract { ln.locker = &fileLocker{ path: settings.Path + ".lock", } if err := ln.locker.Acquire(); err != nil { unixListener.Close() return nil, err } } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { ln.tlsConfig = config.GetTLSConfig() } if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { ln.xtlsConfig = config.GetXTLSConfig() } go ln.run() return ln, nil } func (ln *Listener) Addr() net.Addr { return ln.addr } func (ln *Listener) Close() error { if ln.locker != nil { ln.locker.Release() } return ln.ln.Close() } func (ln *Listener) run() { for { conn, err := ln.ln.Accept() if err != nil { if strings.Contains(err.Error(), "closed") { break } newError("failed to accepted raw connections").Base(err).AtWarning().WriteToLog() continue } if ln.tlsConfig != nil { conn = tls.Server(conn, ln.tlsConfig) } else if ln.xtlsConfig != nil { conn = xtls.Server(conn, ln.xtlsConfig) } ln.addConn(internet.Connection(conn)) } } type fileLocker struct { path string file *os.File } func (fl *fileLocker) Acquire() error { f, err := os.Create(fl.path) if err != nil { return err } if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { f.Close() return newError("failed to lock file: ", fl.path).Base(err) } fl.file = f return nil } func (fl *fileLocker) Release() { if err := unix.Flock(int(fl.file.Fd()), unix.LOCK_UN); err != nil { newError("failed to unlock file: ", fl.path).Base(err).WriteToLog() } if err := fl.file.Close(); err != nil { newError("failed to close file: ", fl.path).Base(err).WriteToLog() } if err := os.Remove(fl.path); err != nil { newError("failed to remove file: ", fl.path).Base(err).WriteToLog() } } func init() { common.Must(internet.RegisterTransportListener(protocolName, Listen)) } ================================================ FILE: transport/internet/domainsocket/listener_test.go ================================================ // +build !windows package domainsocket_test import ( "context" "runtime" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" . "v2ray.com/core/transport/internet/domainsocket" ) func TestListen(t *testing.T) { ctx := context.Background() streamSettings := &internet.MemoryStreamConfig{ ProtocolName: "domainsocket", ProtocolSettings: &Config{ Path: "/tmp/ts3", }, } listener, err := Listen(ctx, nil, net.Port(0), streamSettings, func(conn internet.Connection) { defer conn.Close() b := buf.New() defer b.Release() common.Must2(b.ReadFrom(conn)) b.WriteString("Response") common.Must2(conn.Write(b.Bytes())) }) common.Must(err) defer listener.Close() conn, err := Dial(ctx, net.Destination{}, streamSettings) common.Must(err) defer conn.Close() common.Must2(conn.Write([]byte("Request"))) b := buf.New() defer b.Release() common.Must2(b.ReadFrom(conn)) if b.String() != "RequestResponse" { t.Error("expected response as 'RequestResponse' but got ", b.String()) } } func TestListenAbstract(t *testing.T) { if runtime.GOOS != "linux" { return } ctx := context.Background() streamSettings := &internet.MemoryStreamConfig{ ProtocolName: "domainsocket", ProtocolSettings: &Config{ Path: "/tmp/ts3", Abstract: true, }, } listener, err := Listen(ctx, nil, net.Port(0), streamSettings, func(conn internet.Connection) { defer conn.Close() b := buf.New() defer b.Release() common.Must2(b.ReadFrom(conn)) b.WriteString("Response") common.Must2(conn.Write(b.Bytes())) }) common.Must(err) defer listener.Close() conn, err := Dial(ctx, net.Destination{}, streamSettings) common.Must(err) defer conn.Close() common.Must2(conn.Write([]byte("Request"))) b := buf.New() defer b.Release() common.Must2(b.ReadFrom(conn)) if b.String() != "RequestResponse" { t.Error("expected response as 'RequestResponse' but got ", b.String()) } } ================================================ FILE: transport/internet/errors.generated.go ================================================ package internet import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/header.go ================================================ package internet import ( "context" "net" "v2ray.com/core/common" ) type PacketHeader interface { Size() int32 Serialize([]byte) } func CreatePacketHeader(config interface{}) (PacketHeader, error) { header, err := common.CreateObject(context.Background(), config) if err != nil { return nil, err } if h, ok := header.(PacketHeader); ok { return h, nil } return nil, newError("not a packet header") } type ConnectionAuthenticator interface { Client(net.Conn) net.Conn Server(net.Conn) net.Conn } func CreateConnectionAuthenticator(config interface{}) (ConnectionAuthenticator, error) { auth, err := common.CreateObject(context.Background(), config) if err != nil { return nil, err } if a, ok := auth.(ConnectionAuthenticator); ok { return a, nil } return nil, newError("not a ConnectionAuthenticator") } ================================================ FILE: transport/internet/header_test.go ================================================ package internet_test import ( "testing" "v2ray.com/core/common" . "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/headers/noop" "v2ray.com/core/transport/internet/headers/srtp" "v2ray.com/core/transport/internet/headers/utp" "v2ray.com/core/transport/internet/headers/wechat" "v2ray.com/core/transport/internet/headers/wireguard" ) func TestAllHeadersLoadable(t *testing.T) { testCases := []struct { Input interface{} Size int32 }{ { Input: new(noop.Config), Size: 0, }, { Input: new(srtp.Config), Size: 4, }, { Input: new(utp.Config), Size: 4, }, { Input: new(wechat.VideoConfig), Size: 13, }, { Input: new(wireguard.WireguardConfig), Size: 4, }, } for _, testCase := range testCases { header, err := CreatePacketHeader(testCase.Input) common.Must(err) if header.Size() != testCase.Size { t.Error("expected size ", testCase.Size, " but got ", header.Size()) } } } ================================================ FILE: transport/internet/headers/http/config.go ================================================ package http import ( "strings" "v2ray.com/core/common/dice" ) func pickString(arr []string) string { n := len(arr) switch n { case 0: return "" case 1: return arr[0] default: return arr[dice.Roll(n)] } } func (v *RequestConfig) PickUri() string { return pickString(v.Uri) } func (v *RequestConfig) PickHeaders() []string { n := len(v.Header) if n == 0 { return nil } headers := make([]string, n) for idx, headerConfig := range v.Header { headerName := headerConfig.Name headerValue := pickString(headerConfig.Value) headers[idx] = headerName + ": " + headerValue } return headers } func (v *RequestConfig) GetVersionValue() string { if v == nil || v.Version == nil { return "1.1" } return v.Version.Value } func (v *RequestConfig) GetMethodValue() string { if v == nil || v.Method == nil { return "GET" } return v.Method.Value } func (v *RequestConfig) GetFullVersion() string { return "HTTP/" + v.GetVersionValue() } func (v *ResponseConfig) HasHeader(header string) bool { cHeader := strings.ToLower(header) for _, tHeader := range v.Header { if strings.EqualFold(tHeader.Name, cHeader) { return true } } return false } func (v *ResponseConfig) PickHeaders() []string { n := len(v.Header) if n == 0 { return nil } headers := make([]string, n) for idx, headerConfig := range v.Header { headerName := headerConfig.Name headerValue := pickString(headerConfig.Value) headers[idx] = headerName + ": " + headerValue } return headers } func (v *ResponseConfig) GetVersionValue() string { if v == nil || v.Version == nil { return "1.1" } return v.Version.Value } func (v *ResponseConfig) GetFullVersion() string { return "HTTP/" + v.GetVersionValue() } func (v *ResponseConfig) GetStatusValue() *Status { if v == nil || v.Status == nil { return &Status{ Code: "200", Reason: "OK", } } return v.Status } ================================================ FILE: transport/internet/headers/http/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/http/config.proto package http import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Header struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // "Accept", "Cookie", etc Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Each entry must be valid in one piece. Random entry will be chosen if // multiple entries present. Value []string `protobuf:"bytes,2,rep,name=value,proto3" json:"value,omitempty"` } func (x *Header) Reset() { *x = Header{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Header) String() string { return protoimpl.X.MessageStringOf(x) } func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Header.ProtoReflect.Descriptor instead. func (*Header) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{0} } func (x *Header) GetName() string { if x != nil { return x.Name } return "" } func (x *Header) GetValue() []string { if x != nil { return x.Value } return nil } // HTTP version. Default value "1.1". type Version struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *Version) Reset() { *x = Version{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Version) String() string { return protoimpl.X.MessageStringOf(x) } func (*Version) ProtoMessage() {} func (x *Version) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Version.ProtoReflect.Descriptor instead. func (*Version) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{1} } func (x *Version) GetValue() string { if x != nil { return x.Value } return "" } // HTTP method. Default value "GET". type Method struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *Method) Reset() { *x = Method{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Method) String() string { return protoimpl.X.MessageStringOf(x) } func (*Method) ProtoMessage() {} func (x *Method) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Method.ProtoReflect.Descriptor instead. func (*Method) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{2} } func (x *Method) GetValue() string { if x != nil { return x.Value } return "" } type RequestConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Full HTTP version like "1.1". Version *Version `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // GET, POST, CONNECT etc Method *Method `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` // URI like "/login.php" Uri []string `protobuf:"bytes,3,rep,name=uri,proto3" json:"uri,omitempty"` Header []*Header `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty"` } func (x *RequestConfig) Reset() { *x = RequestConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *RequestConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*RequestConfig) ProtoMessage() {} func (x *RequestConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RequestConfig.ProtoReflect.Descriptor instead. func (*RequestConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{3} } func (x *RequestConfig) GetVersion() *Version { if x != nil { return x.Version } return nil } func (x *RequestConfig) GetMethod() *Method { if x != nil { return x.Method } return nil } func (x *RequestConfig) GetUri() []string { if x != nil { return x.Uri } return nil } func (x *RequestConfig) GetHeader() []*Header { if x != nil { return x.Header } return nil } type Status struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Status code. Default "200". Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` // Statue reason. Default "OK". Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` } func (x *Status) Reset() { *x = Status{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Status) String() string { return protoimpl.X.MessageStringOf(x) } func (*Status) ProtoMessage() {} func (x *Status) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Status.ProtoReflect.Descriptor instead. func (*Status) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{4} } func (x *Status) GetCode() string { if x != nil { return x.Code } return "" } func (x *Status) GetReason() string { if x != nil { return x.Reason } return "" } type ResponseConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Version *Version `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` Status *Status `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` Header []*Header `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty"` } func (x *ResponseConfig) Reset() { *x = ResponseConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ResponseConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ResponseConfig) ProtoMessage() {} func (x *ResponseConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ResponseConfig.ProtoReflect.Descriptor instead. func (*ResponseConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{5} } func (x *ResponseConfig) GetVersion() *Version { if x != nil { return x.Version } return nil } func (x *ResponseConfig) GetStatus() *Status { if x != nil { return x.Status } return nil } func (x *ResponseConfig) GetHeader() []*Header { if x != nil { return x.Header } return nil } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Settings for authenticating requests. If not set, client side will not send // authenication header, and server side will bypass authentication. Request *RequestConfig `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"` // Settings for authenticating responses. If not set, client side will bypass // authentication, and server side will not send authentication header. Response *ResponseConfig `protobuf:"bytes,2,opt,name=response,proto3" json:"response,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_http_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_http_config_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{6} } func (x *Config) GetRequest() *RequestConfig { if x != nil { return x.Request } return nil } func (x *Config) GetResponse() *ResponseConfig { if x != nil { return x.Response } return nil } var File_transport_internet_headers_http_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_http_config_proto_rawDesc = []byte{ 0x0a, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x22, 0x32, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1e, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x88, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x4a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xf7, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0xb5, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_http_config_proto_rawDescOnce sync.Once file_transport_internet_headers_http_config_proto_rawDescData = file_transport_internet_headers_http_config_proto_rawDesc ) func file_transport_internet_headers_http_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_http_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_http_config_proto_rawDescData) }) return file_transport_internet_headers_http_config_proto_rawDescData } var file_transport_internet_headers_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_transport_internet_headers_http_config_proto_goTypes = []interface{}{ (*Header)(nil), // 0: v2ray.core.transport.internet.headers.http.Header (*Version)(nil), // 1: v2ray.core.transport.internet.headers.http.Version (*Method)(nil), // 2: v2ray.core.transport.internet.headers.http.Method (*RequestConfig)(nil), // 3: v2ray.core.transport.internet.headers.http.RequestConfig (*Status)(nil), // 4: v2ray.core.transport.internet.headers.http.Status (*ResponseConfig)(nil), // 5: v2ray.core.transport.internet.headers.http.ResponseConfig (*Config)(nil), // 6: v2ray.core.transport.internet.headers.http.Config } var file_transport_internet_headers_http_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.transport.internet.headers.http.RequestConfig.version:type_name -> v2ray.core.transport.internet.headers.http.Version 2, // 1: v2ray.core.transport.internet.headers.http.RequestConfig.method:type_name -> v2ray.core.transport.internet.headers.http.Method 0, // 2: v2ray.core.transport.internet.headers.http.RequestConfig.header:type_name -> v2ray.core.transport.internet.headers.http.Header 1, // 3: v2ray.core.transport.internet.headers.http.ResponseConfig.version:type_name -> v2ray.core.transport.internet.headers.http.Version 4, // 4: v2ray.core.transport.internet.headers.http.ResponseConfig.status:type_name -> v2ray.core.transport.internet.headers.http.Status 0, // 5: v2ray.core.transport.internet.headers.http.ResponseConfig.header:type_name -> v2ray.core.transport.internet.headers.http.Header 3, // 6: v2ray.core.transport.internet.headers.http.Config.request:type_name -> v2ray.core.transport.internet.headers.http.RequestConfig 5, // 7: v2ray.core.transport.internet.headers.http.Config.response:type_name -> v2ray.core.transport.internet.headers.http.ResponseConfig 8, // [8:8] is the sub-list for method output_type 8, // [8:8] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name } func init() { file_transport_internet_headers_http_config_proto_init() } func file_transport_internet_headers_http_config_proto_init() { if File_transport_internet_headers_http_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_http_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Header); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Version); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Method); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RequestConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Status); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ResponseConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_http_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_http_config_proto_rawDesc, NumEnums: 0, NumMessages: 7, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_http_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_http_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_http_config_proto_msgTypes, }.Build() File_transport_internet_headers_http_config_proto = out.File file_transport_internet_headers_http_config_proto_rawDesc = nil file_transport_internet_headers_http_config_proto_goTypes = nil file_transport_internet_headers_http_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/http/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.http; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Http"; option go_package = "v2ray.com/core/transport/internet/headers/http"; option java_package = "com.v2ray.core.transport.internet.headers.http"; option java_multiple_files = true; message Header { // "Accept", "Cookie", etc string name = 1; // Each entry must be valid in one piece. Random entry will be chosen if // multiple entries present. repeated string value = 2; } // HTTP version. Default value "1.1". message Version { string value = 1; } // HTTP method. Default value "GET". message Method { string value = 1; } message RequestConfig { // Full HTTP version like "1.1". Version version = 1; // GET, POST, CONNECT etc Method method = 2; // URI like "/login.php" repeated string uri = 3; repeated Header header = 4; } message Status { // Status code. Default "200". string code = 1; // Statue reason. Default "OK". string reason = 2; } message ResponseConfig { Version version = 1; Status status = 2; repeated Header header = 3; } message Config { // Settings for authenticating requests. If not set, client side will not send // authenication header, and server side will bypass authentication. RequestConfig request = 1; // Settings for authenticating responses. If not set, client side will bypass // authentication, and server side will not send authentication header. ResponseConfig response = 2; } ================================================ FILE: transport/internet/headers/http/errors.generated.go ================================================ package http import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/headers/http/http.go ================================================ package http //go:generate go run v2ray.com/core/common/errors/errorgen import ( "bufio" "bytes" "context" "io" "net" "net/http" "strings" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" ) const ( // CRLF is the line ending in HTTP header CRLF = "\r\n" // ENDING is the double line ending between HTTP header and body. ENDING = CRLF + CRLF // max length of HTTP header. Safety precaution for DDoS attack. maxHeaderLength = 8192 ) var ( ErrHeaderToLong = newError("Header too long.") ErrHeaderMisMatch = newError("Header Mismatch.") ) type Reader interface { Read(io.Reader) (*buf.Buffer, error) } type Writer interface { Write(io.Writer) error } type NoOpReader struct{} func (NoOpReader) Read(io.Reader) (*buf.Buffer, error) { return nil, nil } type NoOpWriter struct{} func (NoOpWriter) Write(io.Writer) error { return nil } type HeaderReader struct { req *http.Request expectedHeader *RequestConfig } func (h *HeaderReader) ExpectThisRequest(expectedHeader *RequestConfig) *HeaderReader { h.expectedHeader = expectedHeader return h } func (h *HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { buffer := buf.New() totalBytes := int32(0) endingDetected := false var headerBuf bytes.Buffer for totalBytes < maxHeaderLength { _, err := buffer.ReadFrom(reader) if err != nil { buffer.Release() return nil, err } if n := bytes.Index(buffer.Bytes(), []byte(ENDING)); n != -1 { headerBuf.Write(buffer.BytesRange(0, int32(n+len(ENDING)))) buffer.Advance(int32(n + len(ENDING))) endingDetected = true break } lenEnding := int32(len(ENDING)) if buffer.Len() >= lenEnding { totalBytes += buffer.Len() - lenEnding headerBuf.Write(buffer.BytesRange(0, buffer.Len()-lenEnding)) leftover := buffer.BytesFrom(-lenEnding) buffer.Clear() copy(buffer.Extend(lenEnding), leftover) if _, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes())), false); err != io.ErrUnexpectedEOF { return nil, err } } } if !endingDetected { buffer.Release() return nil, ErrHeaderToLong } if h.expectedHeader == nil { if buffer.IsEmpty() { buffer.Release() return nil, nil } return buffer, nil } //Parse the request if req, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes())), false); err != nil { return nil, err } else { h.req = req } //Check req path := h.req.URL.Path hasThisUri := false for _, u := range h.expectedHeader.Uri { if u == path { hasThisUri = true } } if !hasThisUri { return nil, ErrHeaderMisMatch } if buffer.IsEmpty() { buffer.Release() return nil, nil } return buffer, nil } type HeaderWriter struct { header *buf.Buffer } func NewHeaderWriter(header *buf.Buffer) *HeaderWriter { return &HeaderWriter{ header: header, } } func (w *HeaderWriter) Write(writer io.Writer) error { if w.header == nil { return nil } err := buf.WriteAllBytes(writer, w.header.Bytes()) w.header.Release() w.header = nil return err } type HttpConn struct { net.Conn readBuffer *buf.Buffer oneTimeReader Reader oneTimeWriter Writer errorWriter Writer errorMismatchWriter Writer errorTooLongWriter Writer errReason error } func NewHttpConn(conn net.Conn, reader Reader, writer Writer, errorWriter Writer, errorMismatchWriter Writer, errorTooLongWriter Writer) *HttpConn { return &HttpConn{ Conn: conn, oneTimeReader: reader, oneTimeWriter: writer, errorWriter: errorWriter, errorMismatchWriter: errorMismatchWriter, errorTooLongWriter: errorTooLongWriter, } } func (c *HttpConn) Read(b []byte) (int, error) { if c.oneTimeReader != nil { buffer, err := c.oneTimeReader.Read(c.Conn) if err != nil { c.errReason = err return 0, err } c.readBuffer = buffer c.oneTimeReader = nil } if !c.readBuffer.IsEmpty() { nBytes, _ := c.readBuffer.Read(b) if c.readBuffer.IsEmpty() { c.readBuffer.Release() c.readBuffer = nil } return nBytes, nil } return c.Conn.Read(b) } // Write implements io.Writer. func (c *HttpConn) Write(b []byte) (int, error) { if c.oneTimeWriter != nil { err := c.oneTimeWriter.Write(c.Conn) c.oneTimeWriter = nil if err != nil { return 0, err } } return c.Conn.Write(b) } // Close implements net.Conn.Close(). func (c *HttpConn) Close() error { if c.oneTimeWriter != nil && c.errorWriter != nil { // Connection is being closed but header wasn't sent. This means the client request // is probably not valid. Sending back a server error header in this case. //Write response based on error reason if c.errReason == ErrHeaderMisMatch { c.errorMismatchWriter.Write(c.Conn) } else if c.errReason == ErrHeaderToLong { c.errorTooLongWriter.Write(c.Conn) } else { c.errorWriter.Write(c.Conn) } } return c.Conn.Close() } func formResponseHeader(config *ResponseConfig) *HeaderWriter { header := buf.New() common.Must2(header.WriteString(strings.Join([]string{config.GetFullVersion(), config.GetStatusValue().Code, config.GetStatusValue().Reason}, " "))) common.Must2(header.WriteString(CRLF)) headers := config.PickHeaders() for _, h := range headers { common.Must2(header.WriteString(h)) common.Must2(header.WriteString(CRLF)) } if !config.HasHeader("Date") { common.Must2(header.WriteString("Date: ")) common.Must2(header.WriteString(time.Now().Format(http.TimeFormat))) common.Must2(header.WriteString(CRLF)) } common.Must2(header.WriteString(CRLF)) return &HeaderWriter{ header: header, } } type HttpAuthenticator struct { config *Config } func (a HttpAuthenticator) GetClientWriter() *HeaderWriter { header := buf.New() config := a.config.Request common.Must2(header.WriteString(strings.Join([]string{config.GetMethodValue(), config.PickUri(), config.GetFullVersion()}, " "))) common.Must2(header.WriteString(CRLF)) headers := config.PickHeaders() for _, h := range headers { common.Must2(header.WriteString(h)) common.Must2(header.WriteString(CRLF)) } common.Must2(header.WriteString(CRLF)) return &HeaderWriter{ header: header, } } func (a HttpAuthenticator) GetServerWriter() *HeaderWriter { return formResponseHeader(a.config.Response) } func (a HttpAuthenticator) Client(conn net.Conn) net.Conn { if a.config.Request == nil && a.config.Response == nil { return conn } var reader Reader = NoOpReader{} if a.config.Request != nil { reader = new(HeaderReader) } var writer Writer = NoOpWriter{} if a.config.Response != nil { writer = a.GetClientWriter() } return NewHttpConn(conn, reader, writer, NoOpWriter{}, NoOpWriter{}, NoOpWriter{}) } func (a HttpAuthenticator) Server(conn net.Conn) net.Conn { if a.config.Request == nil && a.config.Response == nil { return conn } return NewHttpConn(conn, new(HeaderReader).ExpectThisRequest(a.config.Request), a.GetServerWriter(), formResponseHeader(resp400), formResponseHeader(resp404), formResponseHeader(resp400)) } func NewHttpAuthenticator(ctx context.Context, config *Config) (HttpAuthenticator, error) { return HttpAuthenticator{ config: config, }, nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewHttpAuthenticator(ctx, config.(*Config)) })) } ================================================ FILE: transport/internet/headers/http/http_test.go ================================================ package http_test import ( "bufio" "bytes" "context" "crypto/rand" "strings" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" . "v2ray.com/core/transport/internet/headers/http" ) func TestReaderWriter(t *testing.T) { cache := buf.New() b := buf.New() common.Must2(b.WriteString("abcd" + ENDING)) writer := NewHeaderWriter(b) err := writer.Write(cache) common.Must(err) if v := cache.Len(); v != 8 { t.Error("cache len: ", v) } _, err = cache.Write([]byte{'e', 'f', 'g'}) common.Must(err) reader := &HeaderReader{} buffer, err := reader.Read(cache) if err != nil && !strings.HasPrefix(err.Error(), "malformed HTTP request") { t.Error("unknown error ", err) } _ = buffer /* if buffer.String() != "efg" { t.Error("buffer: ", buffer.String()) }*/ } func TestRequestHeader(t *testing.T) { auth, err := NewHttpAuthenticator(context.Background(), &Config{ Request: &RequestConfig{ Uri: []string{"/"}, Header: []*Header{ { Name: "Test", Value: []string{"Value"}, }, }, }, }) common.Must(err) cache := buf.New() err = auth.GetClientWriter().Write(cache) common.Must(err) if cache.String() != "GET / HTTP/1.1\r\nTest: Value\r\n\r\n" { t.Error("cache: ", cache.String()) } } func TestLongRequestHeader(t *testing.T) { payload := make([]byte, buf.Size+2) common.Must2(rand.Read(payload[:buf.Size-2])) copy(payload[buf.Size-2:], []byte(ENDING)) payload = append(payload, []byte("abcd")...) reader := HeaderReader{} b, err := reader.Read(bytes.NewReader(payload)) if err != nil && !(strings.HasPrefix(err.Error(), "invalid") || strings.HasPrefix(err.Error(), "malformed")) { t.Error("unknown error ", err) } _ = b /* common.Must(err) if b.String() != "abcd" { t.Error("expect content abcd, but actually ", b.String()) }*/ } func TestConnection(t *testing.T) { auth, err := NewHttpAuthenticator(context.Background(), &Config{ Request: &RequestConfig{ Method: &Method{Value: "Post"}, Uri: []string{"/testpath"}, Header: []*Header{ { Name: "Host", Value: []string{"www.v2ray.com", "www.google.com"}, }, { Name: "User-Agent", Value: []string{"Test-Agent"}, }, }, }, Response: &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "404", Reason: "Not Found", }, }, }) common.Must(err) listener, err := net.Listen("tcp", "127.0.0.1:0") common.Must(err) go func() { conn, err := listener.Accept() common.Must(err) authConn := auth.Server(conn) b := make([]byte, 256) for { n, err := authConn.Read(b) if err != nil { break } _, err = authConn.Write(b[:n]) common.Must(err) } }() conn, err := net.DialTCP("tcp", nil, listener.Addr().(*net.TCPAddr)) common.Must(err) authConn := auth.Client(conn) defer authConn.Close() authConn.Write([]byte("Test payload")) authConn.Write([]byte("Test payload 2")) expectedResponse := "Test payloadTest payload 2" actualResponse := make([]byte, 256) deadline := time.Now().Add(time.Second * 5) totalBytes := 0 for { n, err := authConn.Read(actualResponse[totalBytes:]) common.Must(err) totalBytes += n if totalBytes >= len(expectedResponse) || time.Now().After(deadline) { break } } if string(actualResponse[:totalBytes]) != expectedResponse { t.Error("response: ", string(actualResponse[:totalBytes])) } } func TestConnectionInvPath(t *testing.T) { auth, err := NewHttpAuthenticator(context.Background(), &Config{ Request: &RequestConfig{ Method: &Method{Value: "Post"}, Uri: []string{"/testpath"}, Header: []*Header{ { Name: "Host", Value: []string{"www.v2ray.com", "www.google.com"}, }, { Name: "User-Agent", Value: []string{"Test-Agent"}, }, }, }, Response: &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "404", Reason: "Not Found", }, }, }) common.Must(err) authR, err := NewHttpAuthenticator(context.Background(), &Config{ Request: &RequestConfig{ Method: &Method{Value: "Post"}, Uri: []string{"/testpathErr"}, Header: []*Header{ { Name: "Host", Value: []string{"www.v2ray.com", "www.google.com"}, }, { Name: "User-Agent", Value: []string{"Test-Agent"}, }, }, }, Response: &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "404", Reason: "Not Found", }, }, }) common.Must(err) listener, err := net.Listen("tcp", "127.0.0.1:0") common.Must(err) go func() { conn, err := listener.Accept() common.Must(err) authConn := auth.Server(conn) b := make([]byte, 256) for { n, err := authConn.Read(b) if err != nil { authConn.Close() break } _, err = authConn.Write(b[:n]) common.Must(err) } }() conn, err := net.DialTCP("tcp", nil, listener.Addr().(*net.TCPAddr)) common.Must(err) authConn := authR.Client(conn) defer authConn.Close() authConn.Write([]byte("Test payload")) authConn.Write([]byte("Test payload 2")) expectedResponse := "Test payloadTest payload 2" actualResponse := make([]byte, 256) deadline := time.Now().Add(time.Second * 5) totalBytes := 0 for { n, err := authConn.Read(actualResponse[totalBytes:]) if err == nil { t.Error("Error Expected", err) } else { return } totalBytes += n if totalBytes >= len(expectedResponse) || time.Now().After(deadline) { break } } } func TestConnectionInvReq(t *testing.T) { auth, err := NewHttpAuthenticator(context.Background(), &Config{ Request: &RequestConfig{ Method: &Method{Value: "Post"}, Uri: []string{"/testpath"}, Header: []*Header{ { Name: "Host", Value: []string{"www.v2ray.com", "www.google.com"}, }, { Name: "User-Agent", Value: []string{"Test-Agent"}, }, }, }, Response: &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "404", Reason: "Not Found", }, }, }) common.Must(err) listener, err := net.Listen("tcp", "127.0.0.1:0") common.Must(err) go func() { conn, err := listener.Accept() common.Must(err) authConn := auth.Server(conn) b := make([]byte, 256) for { n, err := authConn.Read(b) if err != nil { authConn.Close() break } _, err = authConn.Write(b[:n]) common.Must(err) } }() conn, err := net.DialTCP("tcp", nil, listener.Addr().(*net.TCPAddr)) common.Must(err) conn.Write([]byte("ABCDEFGHIJKMLN\r\n\r\n")) l, _, err := bufio.NewReader(conn).ReadLine() common.Must(err) if !strings.HasPrefix(string(l), "HTTP/1.1 400 Bad Request") { t.Error("Resp to non http conn", string(l)) } } ================================================ FILE: transport/internet/headers/http/linkedreadRequest.go ================================================ package http import ( "bufio" "net/http" _ "unsafe" // required to use //go:linkname ) //go:linkname readRequest net/http.readRequest func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *http.Request, err error) ================================================ FILE: transport/internet/headers/http/resp.go ================================================ package http var resp400 = &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "400", Reason: "Bad Request", }, Header: []*Header{ { Name: "Connection", Value: []string{"close"}, }, { Name: "Cache-Control", Value: []string{"private"}, }, { Name: "Content-Length", Value: []string{"0"}, }, }, } var resp404 = &ResponseConfig{ Version: &Version{ Value: "1.1", }, Status: &Status{ Code: "404", Reason: "Not Found", }, Header: []*Header{ { Name: "Connection", Value: []string{"close"}, }, { Name: "Cache-Control", Value: []string{"private"}, }, { Name: "Content-Length", Value: []string{"0"}, }, }, } ================================================ FILE: transport/internet/headers/noop/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/noop/config.proto package noop import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_noop_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_noop_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_headers_noop_config_proto_rawDescGZIP(), []int{0} } type ConnectionConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *ConnectionConfig) Reset() { *x = ConnectionConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_noop_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ConnectionConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*ConnectionConfig) ProtoMessage() {} func (x *ConnectionConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_noop_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ConnectionConfig.ProtoReflect.Descriptor instead. func (*ConnectionConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_noop_config_proto_rawDescGZIP(), []int{1} } var File_transport_internet_headers_noop_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_noop_config_proto_rawDesc = []byte{ 0x0a, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x6e, 0x6f, 0x6f, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x6e, 0x6f, 0x6f, 0x70, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x12, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x6e, 0x6f, 0x6f, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x6e, 0x6f, 0x6f, 0x70, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x4e, 0x6f, 0x6f, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_noop_config_proto_rawDescOnce sync.Once file_transport_internet_headers_noop_config_proto_rawDescData = file_transport_internet_headers_noop_config_proto_rawDesc ) func file_transport_internet_headers_noop_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_noop_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_noop_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_noop_config_proto_rawDescData) }) return file_transport_internet_headers_noop_config_proto_rawDescData } var file_transport_internet_headers_noop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_transport_internet_headers_noop_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.headers.noop.Config (*ConnectionConfig)(nil), // 1: v2ray.core.transport.internet.headers.noop.ConnectionConfig } var file_transport_internet_headers_noop_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_noop_config_proto_init() } func file_transport_internet_headers_noop_config_proto_init() { if File_transport_internet_headers_noop_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_noop_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_headers_noop_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ConnectionConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_noop_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_noop_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_noop_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_noop_config_proto_msgTypes, }.Build() File_transport_internet_headers_noop_config_proto = out.File file_transport_internet_headers_noop_config_proto_rawDesc = nil file_transport_internet_headers_noop_config_proto_goTypes = nil file_transport_internet_headers_noop_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/noop/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.noop; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Noop"; option go_package = "v2ray.com/core/transport/internet/headers/noop"; option java_package = "com.v2ray.core.transport.internet.headers.noop"; option java_multiple_files = true; message Config {} message ConnectionConfig {} ================================================ FILE: transport/internet/headers/noop/noop.go ================================================ package noop import ( "context" "net" "v2ray.com/core/common" ) type NoOpHeader struct{} func (NoOpHeader) Size() int32 { return 0 } // Serialize implements PacketHeader. func (NoOpHeader) Serialize([]byte) {} func NewNoOpHeader(context.Context, interface{}) (interface{}, error) { return NoOpHeader{}, nil } type NoOpConnectionHeader struct{} func (NoOpConnectionHeader) Client(conn net.Conn) net.Conn { return conn } func (NoOpConnectionHeader) Server(conn net.Conn) net.Conn { return conn } func NewNoOpConnectionHeader(context.Context, interface{}) (interface{}, error) { return NoOpConnectionHeader{}, nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), NewNoOpHeader)) common.Must(common.RegisterConfig((*ConnectionConfig)(nil), NewNoOpConnectionHeader)) } ================================================ FILE: transport/internet/headers/srtp/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/srtp/config.proto package srtp import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` Padding bool `protobuf:"varint,2,opt,name=padding,proto3" json:"padding,omitempty"` Extension bool `protobuf:"varint,3,opt,name=extension,proto3" json:"extension,omitempty"` CsrcCount uint32 `protobuf:"varint,4,opt,name=csrc_count,json=csrcCount,proto3" json:"csrc_count,omitempty"` Marker bool `protobuf:"varint,5,opt,name=marker,proto3" json:"marker,omitempty"` PayloadType uint32 `protobuf:"varint,6,opt,name=payload_type,json=payloadType,proto3" json:"payload_type,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_srtp_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_srtp_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_headers_srtp_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetVersion() uint32 { if x != nil { return x.Version } return 0 } func (x *Config) GetPadding() bool { if x != nil { return x.Padding } return false } func (x *Config) GetExtension() bool { if x != nil { return x.Extension } return false } func (x *Config) GetCsrcCount() uint32 { if x != nil { return x.CsrcCount } return 0 } func (x *Config) GetMarker() bool { if x != nil { return x.Marker } return false } func (x *Config) GetPayloadType() uint32 { if x != nil { return x.PayloadType } return 0 } var File_transport_internet_headers_srtp_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_srtp_config_proto_rawDesc = []byte{ 0x0a, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x72, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x72, 0x74, 0x70, 0x22, 0xb4, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x73, 0x72, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x73, 0x72, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x72, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x72, 0x74, 0x70, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x72, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_srtp_config_proto_rawDescOnce sync.Once file_transport_internet_headers_srtp_config_proto_rawDescData = file_transport_internet_headers_srtp_config_proto_rawDesc ) func file_transport_internet_headers_srtp_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_srtp_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_srtp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_srtp_config_proto_rawDescData) }) return file_transport_internet_headers_srtp_config_proto_rawDescData } var file_transport_internet_headers_srtp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_headers_srtp_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.headers.srtp.Config } var file_transport_internet_headers_srtp_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_srtp_config_proto_init() } func file_transport_internet_headers_srtp_config_proto_init() { if File_transport_internet_headers_srtp_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_srtp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_srtp_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_srtp_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_srtp_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_srtp_config_proto_msgTypes, }.Build() File_transport_internet_headers_srtp_config_proto = out.File file_transport_internet_headers_srtp_config_proto_rawDesc = nil file_transport_internet_headers_srtp_config_proto_goTypes = nil file_transport_internet_headers_srtp_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/srtp/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.srtp; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Srtp"; option go_package = "v2ray.com/core/transport/internet/headers/srtp"; option java_package = "com.v2ray.core.transport.internet.headers.srtp"; option java_multiple_files = true; message Config { uint32 version = 1; bool padding = 2; bool extension = 3; uint32 csrc_count = 4; bool marker = 5; uint32 payload_type = 6; } ================================================ FILE: transport/internet/headers/srtp/srtp.go ================================================ package srtp import ( "context" "encoding/binary" "v2ray.com/core/common" "v2ray.com/core/common/dice" ) type SRTP struct { header uint16 number uint16 } func (*SRTP) Size() int32 { return 4 } // Serialize implements PacketHeader. func (s *SRTP) Serialize(b []byte) { s.number++ binary.BigEndian.PutUint16(b, s.header) binary.BigEndian.PutUint16(b[2:], s.number) } // New returns a new SRTP instance based on the given config. func New(ctx context.Context, config interface{}) (interface{}, error) { return &SRTP{ header: 0xB5E8, number: dice.RollUint16(), }, nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), New)) } ================================================ FILE: transport/internet/headers/srtp/srtp_test.go ================================================ package srtp_test import ( "context" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/internet/headers/srtp" ) func TestSRTPWrite(t *testing.T) { content := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'} srtpRaw, err := New(context.Background(), &Config{}) common.Must(err) srtp := srtpRaw.(*SRTP) payload := buf.New() srtp.Serialize(payload.Extend(srtp.Size())) payload.Write(content) expectedLen := int32(len(content)) + srtp.Size() if payload.Len() != expectedLen { t.Error("expected ", expectedLen, " of bytes, but got ", payload.Len()) } } ================================================ FILE: transport/internet/headers/tls/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/tls/config.proto package tls import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type PacketConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *PacketConfig) Reset() { *x = PacketConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_tls_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PacketConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*PacketConfig) ProtoMessage() {} func (x *PacketConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_tls_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PacketConfig.ProtoReflect.Descriptor instead. func (*PacketConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_tls_config_proto_rawDescGZIP(), []int{0} } var File_transport_internet_headers_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_tls_config_proto_rawDesc = []byte{ 0x0a, 0x2b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x8c, 0x01, 0x0a, 0x2d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x2d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x29, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_tls_config_proto_rawDescOnce sync.Once file_transport_internet_headers_tls_config_proto_rawDescData = file_transport_internet_headers_tls_config_proto_rawDesc ) func file_transport_internet_headers_tls_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_tls_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_tls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_tls_config_proto_rawDescData) }) return file_transport_internet_headers_tls_config_proto_rawDescData } var file_transport_internet_headers_tls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_headers_tls_config_proto_goTypes = []interface{}{ (*PacketConfig)(nil), // 0: v2ray.core.transport.internet.headers.tls.PacketConfig } var file_transport_internet_headers_tls_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_tls_config_proto_init() } func file_transport_internet_headers_tls_config_proto_init() { if File_transport_internet_headers_tls_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_tls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PacketConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_tls_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_tls_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_tls_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_tls_config_proto_msgTypes, }.Build() File_transport_internet_headers_tls_config_proto = out.File file_transport_internet_headers_tls_config_proto_rawDesc = nil file_transport_internet_headers_tls_config_proto_goTypes = nil file_transport_internet_headers_tls_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/tls/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.tls; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Tls"; option go_package = "v2ray.com/core/transport/internet/headers/tls"; option java_package = "com.v2ray.core.transport.internet.headers.tls"; option java_multiple_files = true; message PacketConfig {} ================================================ FILE: transport/internet/headers/tls/dtls.go ================================================ package tls import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/dice" ) // DTLS writes header as DTLS. See https://tools.ietf.org/html/rfc6347 type DTLS struct { epoch uint16 length uint16 sequence uint32 } // Size implements PacketHeader. func (*DTLS) Size() int32 { return 1 + 2 + 2 + 6 + 2 } // Serialize implements PacketHeader. func (d *DTLS) Serialize(b []byte) { b[0] = 23 // application data b[1] = 254 b[2] = 253 b[3] = byte(d.epoch >> 8) b[4] = byte(d.epoch) b[5] = 0 b[6] = 0 b[7] = byte(d.sequence >> 24) b[8] = byte(d.sequence >> 16) b[9] = byte(d.sequence >> 8) b[10] = byte(d.sequence) d.sequence++ b[11] = byte(d.length >> 8) b[12] = byte(d.length) d.length += 17 if d.length > 100 { d.length -= 50 } } // New creates a new UTP header for the given config. func New(ctx context.Context, config interface{}) (interface{}, error) { return &DTLS{ epoch: dice.RollUint16(), sequence: 0, length: 17, }, nil } func init() { common.Must(common.RegisterConfig((*PacketConfig)(nil), New)) } ================================================ FILE: transport/internet/headers/tls/dtls_test.go ================================================ package tls_test import ( "context" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/internet/headers/tls" ) func TestDTLSWrite(t *testing.T) { content := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'} dtlsRaw, err := New(context.Background(), &PacketConfig{}) common.Must(err) dtls := dtlsRaw.(*DTLS) payload := buf.New() dtls.Serialize(payload.Extend(dtls.Size())) payload.Write(content) if payload.Len() != int32(len(content))+dtls.Size() { t.Error("payload len: ", payload.Len(), " want ", int32(len(content))+dtls.Size()) } } ================================================ FILE: transport/internet/headers/utp/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/utp/config.proto package utp import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_utp_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_utp_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_headers_utp_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetVersion() uint32 { if x != nil { return x.Version } return 0 } var File_transport_internet_headers_utp_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_utp_config_proto_rawDesc = []byte{ 0x0a, 0x2b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x75, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x75, 0x74, 0x70, 0x22, 0x22, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x8c, 0x01, 0x0a, 0x2d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x75, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x2d, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x75, 0x74, 0x70, 0xaa, 0x02, 0x29, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x55, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_utp_config_proto_rawDescOnce sync.Once file_transport_internet_headers_utp_config_proto_rawDescData = file_transport_internet_headers_utp_config_proto_rawDesc ) func file_transport_internet_headers_utp_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_utp_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_utp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_utp_config_proto_rawDescData) }) return file_transport_internet_headers_utp_config_proto_rawDescData } var file_transport_internet_headers_utp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_headers_utp_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.headers.utp.Config } var file_transport_internet_headers_utp_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_utp_config_proto_init() } func file_transport_internet_headers_utp_config_proto_init() { if File_transport_internet_headers_utp_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_utp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_utp_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_utp_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_utp_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_utp_config_proto_msgTypes, }.Build() File_transport_internet_headers_utp_config_proto = out.File file_transport_internet_headers_utp_config_proto_rawDesc = nil file_transport_internet_headers_utp_config_proto_goTypes = nil file_transport_internet_headers_utp_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/utp/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.utp; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Utp"; option go_package = "v2ray.com/core/transport/internet/headers/utp"; option java_package = "com.v2ray.core.transport.internet.headers.utp"; option java_multiple_files = true; message Config { uint32 version = 1; } ================================================ FILE: transport/internet/headers/utp/utp.go ================================================ package utp import ( "context" "encoding/binary" "v2ray.com/core/common" "v2ray.com/core/common/dice" ) type UTP struct { header byte extension byte connectionId uint16 } func (*UTP) Size() int32 { return 4 } // Serialize implements PacketHeader. func (u *UTP) Serialize(b []byte) { binary.BigEndian.PutUint16(b, u.connectionId) b[2] = u.header b[3] = u.extension } // New creates a new UTP header for the given config. func New(ctx context.Context, config interface{}) (interface{}, error) { return &UTP{ header: 1, extension: 0, connectionId: dice.RollUint16(), }, nil } func init() { common.Must(common.RegisterConfig((*Config)(nil), New)) } ================================================ FILE: transport/internet/headers/utp/utp_test.go ================================================ package utp_test import ( "context" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/internet/headers/utp" ) func TestUTPWrite(t *testing.T) { content := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'} utpRaw, err := New(context.Background(), &Config{}) common.Must(err) utp := utpRaw.(*UTP) payload := buf.New() utp.Serialize(payload.Extend(utp.Size())) payload.Write(content) if payload.Len() != int32(len(content))+utp.Size() { t.Error("unexpected payload length: ", payload.Len()) } } ================================================ FILE: transport/internet/headers/wechat/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/wechat/config.proto package wechat import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type VideoConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *VideoConfig) Reset() { *x = VideoConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_wechat_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *VideoConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*VideoConfig) ProtoMessage() {} func (x *VideoConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_wechat_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VideoConfig.ProtoReflect.Descriptor instead. func (*VideoConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_wechat_config_proto_rawDescGZIP(), []int{0} } var File_transport_internet_headers_wechat_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_wechat_config_proto_rawDesc = []byte{ 0x0a, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2c, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x22, 0x0d, 0x0a, 0x0b, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x95, 0x01, 0x0a, 0x30, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x50, 0x01, 0x5a, 0x30, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0xaa, 0x02, 0x2c, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_wechat_config_proto_rawDescOnce sync.Once file_transport_internet_headers_wechat_config_proto_rawDescData = file_transport_internet_headers_wechat_config_proto_rawDesc ) func file_transport_internet_headers_wechat_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_wechat_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_wechat_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_wechat_config_proto_rawDescData) }) return file_transport_internet_headers_wechat_config_proto_rawDescData } var file_transport_internet_headers_wechat_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_headers_wechat_config_proto_goTypes = []interface{}{ (*VideoConfig)(nil), // 0: v2ray.core.transport.internet.headers.wechat.VideoConfig } var file_transport_internet_headers_wechat_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_wechat_config_proto_init() } func file_transport_internet_headers_wechat_config_proto_init() { if File_transport_internet_headers_wechat_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_wechat_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VideoConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_wechat_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_wechat_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_wechat_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_wechat_config_proto_msgTypes, }.Build() File_transport_internet_headers_wechat_config_proto = out.File file_transport_internet_headers_wechat_config_proto_rawDesc = nil file_transport_internet_headers_wechat_config_proto_goTypes = nil file_transport_internet_headers_wechat_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/wechat/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.wechat; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Wechat"; option go_package = "v2ray.com/core/transport/internet/headers/wechat"; option java_package = "com.v2ray.core.transport.internet.headers.wechat"; option java_multiple_files = true; message VideoConfig {} ================================================ FILE: transport/internet/headers/wechat/wechat.go ================================================ package wechat import ( "context" "encoding/binary" "v2ray.com/core/common" "v2ray.com/core/common/dice" ) type VideoChat struct { sn uint32 } func (vc *VideoChat) Size() int32 { return 13 } // Serialize implements PacketHeader. func (vc *VideoChat) Serialize(b []byte) { vc.sn++ b[0] = 0xa1 b[1] = 0x08 binary.BigEndian.PutUint32(b[2:], vc.sn) // b[2:6] b[6] = 0x00 b[7] = 0x10 b[8] = 0x11 b[9] = 0x18 b[10] = 0x30 b[11] = 0x22 b[12] = 0x30 } // NewVideoChat returns a new VideoChat instance based on given config. func NewVideoChat(ctx context.Context, config interface{}) (interface{}, error) { return &VideoChat{ sn: uint32(dice.RollUint16()), }, nil } func init() { common.Must(common.RegisterConfig((*VideoConfig)(nil), NewVideoChat)) } ================================================ FILE: transport/internet/headers/wechat/wechat_test.go ================================================ package wechat_test import ( "context" "testing" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/internet/headers/wechat" ) func TestUTPWrite(t *testing.T) { videoRaw, err := NewVideoChat(context.Background(), &VideoConfig{}) common.Must(err) video := videoRaw.(*VideoChat) payload := buf.New() video.Serialize(payload.Extend(video.Size())) if payload.Len() != video.Size() { t.Error("expected payload size ", video.Size(), " but got ", payload.Len()) } } ================================================ FILE: transport/internet/headers/wireguard/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/headers/wireguard/config.proto package wireguard import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type WireguardConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *WireguardConfig) Reset() { *x = WireguardConfig{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_headers_wireguard_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *WireguardConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*WireguardConfig) ProtoMessage() {} func (x *WireguardConfig) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_headers_wireguard_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use WireguardConfig.ProtoReflect.Descriptor instead. func (*WireguardConfig) Descriptor() ([]byte, []int) { return file_transport_internet_headers_wireguard_config_proto_rawDescGZIP(), []int{0} } var File_transport_internet_headers_wireguard_config_proto protoreflect.FileDescriptor var file_transport_internet_headers_wireguard_config_proto_rawDesc = []byte{ 0x0a, 0x31, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x9e, 0x01, 0x0a, 0x33, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x01, 0x5a, 0x33, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0xaa, 0x02, 0x2f, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_headers_wireguard_config_proto_rawDescOnce sync.Once file_transport_internet_headers_wireguard_config_proto_rawDescData = file_transport_internet_headers_wireguard_config_proto_rawDesc ) func file_transport_internet_headers_wireguard_config_proto_rawDescGZIP() []byte { file_transport_internet_headers_wireguard_config_proto_rawDescOnce.Do(func() { file_transport_internet_headers_wireguard_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_wireguard_config_proto_rawDescData) }) return file_transport_internet_headers_wireguard_config_proto_rawDescData } var file_transport_internet_headers_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_headers_wireguard_config_proto_goTypes = []interface{}{ (*WireguardConfig)(nil), // 0: v2ray.core.transport.internet.headers.wireguard.WireguardConfig } var file_transport_internet_headers_wireguard_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_headers_wireguard_config_proto_init() } func file_transport_internet_headers_wireguard_config_proto_init() { if File_transport_internet_headers_wireguard_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_headers_wireguard_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WireguardConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_headers_wireguard_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_headers_wireguard_config_proto_goTypes, DependencyIndexes: file_transport_internet_headers_wireguard_config_proto_depIdxs, MessageInfos: file_transport_internet_headers_wireguard_config_proto_msgTypes, }.Build() File_transport_internet_headers_wireguard_config_proto = out.File file_transport_internet_headers_wireguard_config_proto_rawDesc = nil file_transport_internet_headers_wireguard_config_proto_goTypes = nil file_transport_internet_headers_wireguard_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/headers/wireguard/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.headers.wireguard; option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Wireguard"; option go_package = "v2ray.com/core/transport/internet/headers/wireguard"; option java_package = "com.v2ray.core.transport.internet.headers.wireguard"; option java_multiple_files = true; message WireguardConfig {} ================================================ FILE: transport/internet/headers/wireguard/wireguard.go ================================================ package wireguard import ( "context" "v2ray.com/core/common" ) type Wireguard struct{} func (Wireguard) Size() int32 { return 4 } // Serialize implements PacketHeader. func (Wireguard) Serialize(b []byte) { b[0] = 0x04 b[1] = 0x00 b[2] = 0x00 b[3] = 0x00 } // NewWireguard returns a new VideoChat instance based on given config. func NewWireguard(ctx context.Context, config interface{}) (interface{}, error) { return Wireguard{}, nil } func init() { common.Must(common.RegisterConfig((*WireguardConfig)(nil), NewWireguard)) } ================================================ FILE: transport/internet/http/config.go ================================================ // +build !confonly package http import ( "v2ray.com/core/common" "v2ray.com/core/common/dice" "v2ray.com/core/transport/internet" ) const protocolName = "http" func (c *Config) getHosts() []string { if len(c.Host) == 0 { return []string{"www.example.com"} } return c.Host } func (c *Config) isValidHost(host string) bool { hosts := c.getHosts() for _, h := range hosts { if h == host { return true } } return false } func (c *Config) getRandomHost() string { hosts := c.getHosts() return hosts[dice.Roll(len(hosts))] } func (c *Config) getNormalizedPath() string { if c.Path == "" { return "/" } if c.Path[0] != '/' { return "/" + c.Path } return c.Path } func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/http/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/http/config.proto package http import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Host []string `protobuf:"bytes,1,rep,name=host,proto3" json:"host,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_http_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_http_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_http_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetHost() []string { if x != nil { return x.Host } return nil } func (x *Config) GetPath() string { if x != nil { return x.Path } return "" } var File_transport_internet_http_config_proto protoreflect.FileDescriptor var file_transport_internet_http_config_proto_rawDesc = []byte{ 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x22, 0x30, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x77, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x26, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_http_config_proto_rawDescOnce sync.Once file_transport_internet_http_config_proto_rawDescData = file_transport_internet_http_config_proto_rawDesc ) func file_transport_internet_http_config_proto_rawDescGZIP() []byte { file_transport_internet_http_config_proto_rawDescOnce.Do(func() { file_transport_internet_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_http_config_proto_rawDescData) }) return file_transport_internet_http_config_proto_rawDescData } var file_transport_internet_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_http_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.http.Config } var file_transport_internet_http_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_http_config_proto_init() } func file_transport_internet_http_config_proto_init() { if File_transport_internet_http_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_http_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_http_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_http_config_proto_goTypes, DependencyIndexes: file_transport_internet_http_config_proto_depIdxs, MessageInfos: file_transport_internet_http_config_proto_msgTypes, }.Build() File_transport_internet_http_config_proto = out.File file_transport_internet_http_config_proto_rawDesc = nil file_transport_internet_http_config_proto_goTypes = nil file_transport_internet_http_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/http/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.http; option csharp_namespace = "V2Ray.Core.Transport.Internet.Http"; option go_package = "v2ray.com/core/transport/internet/http"; option java_package = "com.v2ray.core.transport.internet.http"; option java_multiple_files = true; message Config { repeated string host = 1; string path = 2; } ================================================ FILE: transport/internet/http/dialer.go ================================================ // +build !confonly package http import ( "context" gotls "crypto/tls" "net/http" "net/url" "sync" "golang.org/x/net/http2" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/pipe" ) var ( globalDialerMap map[net.Destination]*http.Client globalDialerAccess sync.Mutex ) func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.Config) (*http.Client, error) { globalDialerAccess.Lock() defer globalDialerAccess.Unlock() if globalDialerMap == nil { globalDialerMap = make(map[net.Destination]*http.Client) } if client, found := globalDialerMap[dest]; found { return client, nil } transport := &http2.Transport{ DialTLS: func(network string, addr string, tlsConfig *gotls.Config) (net.Conn, error) { rawHost, rawPort, err := net.SplitHostPort(addr) if err != nil { return nil, err } if len(rawPort) == 0 { rawPort = "443" } port, err := net.PortFromString(rawPort) if err != nil { return nil, err } address := net.ParseAddress(rawHost) pconn, err := internet.DialSystem(context.Background(), net.TCPDestination(address, port), nil) if err != nil { return nil, err } cn := gotls.Client(pconn, tlsConfig) if err := cn.Handshake(); err != nil { return nil, err } if !tlsConfig.InsecureSkipVerify { if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil { return nil, err } } state := cn.ConnectionState() if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { return nil, newError("http2: unexpected ALPN protocol " + p + "; want q" + http2.NextProtoTLS).AtError() } if !state.NegotiatedProtocolIsMutual { return nil, newError("http2: could not negotiate protocol mutually").AtError() } return cn, nil }, TLSClientConfig: tlsSettings.GetTLSConfig(tls.WithDestination(dest)), } client := &http.Client{ Transport: transport, } globalDialerMap[dest] = client return client, nil } // Dial dials a new TCP connection to the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { httpSettings := streamSettings.ProtocolSettings.(*Config) tlsConfig := tls.ConfigFromStreamSettings(streamSettings) if tlsConfig == nil { return nil, newError("TLS must be enabled for http transport.").AtWarning() } client, err := getHTTPClient(ctx, dest, tlsConfig) if err != nil { return nil, err } opts := pipe.OptionsFromContext(ctx) preader, pwriter := pipe.New(opts...) breader := &buf.BufferedReader{Reader: preader} request := &http.Request{ Method: "PUT", Host: httpSettings.getRandomHost(), Body: breader, URL: &url.URL{ Scheme: "https", Host: dest.NetAddr(), Path: httpSettings.getNormalizedPath(), }, Proto: "HTTP/2", ProtoMajor: 2, ProtoMinor: 0, Header: make(http.Header), } // Disable any compression method from server. request.Header.Set("Accept-Encoding", "identity") response, err := client.Do(request) if err != nil { return nil, newError("failed to dial to ", dest).Base(err).AtWarning() } if response.StatusCode != 200 { return nil, newError("unexpected status", response.StatusCode).AtWarning() } bwriter := buf.NewBufferedWriter(pwriter) common.Must(bwriter.SetBuffered(false)) return net.NewConnection( net.ConnectionOutput(response.Body), net.ConnectionInput(bwriter), net.ConnectionOnClose(common.ChainedClosable{breader, bwriter, response.Body}), ), nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } ================================================ FILE: transport/internet/http/errors.generated.go ================================================ package http import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/http/http.go ================================================ package http //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/http/http_test.go ================================================ package http_test import ( "context" "crypto/rand" "testing" "time" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/transport/internet" . "v2ray.com/core/transport/internet/http" "v2ray.com/core/transport/internet/tls" ) func TestHTTPConnection(t *testing.T) { port := tcp.PickPort() listener, err := Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{ ProtocolName: "http", ProtocolSettings: &Config{}, SecurityType: "tls", SecuritySettings: &tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("www.v2ray.com")))}, }, }, func(conn internet.Connection) { go func() { defer conn.Close() b := buf.New() defer b.Release() for { if _, err := b.ReadFrom(conn); err != nil { return } _, err := conn.Write(b.Bytes()) common.Must(err) } }() }) common.Must(err) defer listener.Close() time.Sleep(time.Second) dctx := context.Background() conn, err := Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "http", ProtocolSettings: &Config{}, SecurityType: "tls", SecuritySettings: &tls.Config{ ServerName: "www.v2ray.com", AllowInsecure: true, }, }) common.Must(err) defer conn.Close() const N = 1024 b1 := make([]byte, N) common.Must2(rand.Read(b1)) b2 := buf.New() nBytes, err := conn.Write(b1) common.Must(err) if nBytes != N { t.Error("write: ", nBytes) } b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } nBytes, err = conn.Write(b1) common.Must(err) if nBytes != N { t.Error("write: ", nBytes) } b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } } ================================================ FILE: transport/internet/http/hub.go ================================================ // +build !confonly package http import ( "context" "io" "net/http" "strings" "time" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "v2ray.com/core/common" "v2ray.com/core/common/net" http_proto "v2ray.com/core/common/protocol/http" "v2ray.com/core/common/serial" "v2ray.com/core/common/session" "v2ray.com/core/common/signal/done" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" ) type Listener struct { server *http.Server handler internet.ConnHandler local net.Addr config *Config } func (l *Listener) Addr() net.Addr { return l.local } func (l *Listener) Close() error { return l.server.Close() } type flushWriter struct { w io.Writer d *done.Instance } func (fw flushWriter) Write(p []byte) (n int, err error) { if fw.d.Done() { return 0, io.ErrClosedPipe } n, err = fw.w.Write(p) if f, ok := fw.w.(http.Flusher); ok { f.Flush() } return } func (l *Listener) ServeHTTP(writer http.ResponseWriter, request *http.Request) { host := request.Host if !l.config.isValidHost(host) { writer.WriteHeader(404) return } path := l.config.getNormalizedPath() if !strings.HasPrefix(request.URL.Path, path) { writer.WriteHeader(404) return } writer.Header().Set("Cache-Control", "no-store") writer.WriteHeader(200) if f, ok := writer.(http.Flusher); ok { f.Flush() } remoteAddr := l.Addr() dest, err := net.ParseDestination(request.RemoteAddr) if err != nil { newError("failed to parse request remote addr: ", request.RemoteAddr).Base(err).WriteToLog() } else { remoteAddr = &net.TCPAddr{ IP: dest.Address.IP(), Port: int(dest.Port), } } forwardedAddrs := http_proto.ParseXForwardedFor(request.Header) if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { remoteAddr.(*net.TCPAddr).IP = forwardedAddrs[0].IP() } done := done.New() conn := net.NewConnection( net.ConnectionOutput(request.Body), net.ConnectionInput(flushWriter{w: writer, d: done}), net.ConnectionOnClose(common.ChainedClosable{done, request.Body}), net.ConnectionLocalAddr(l.Addr()), net.ConnectionRemoteAddr(remoteAddr), ) l.handler(conn) <-done.Wait() } func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { httpSettings := streamSettings.ProtocolSettings.(*Config) listener := &Listener{ handler: handler, local: &net.TCPAddr{ IP: address.IP(), Port: int(port), }, config: httpSettings, } var server *http.Server config := tls.ConfigFromStreamSettings(streamSettings) if config == nil { h2s := &http2.Server{} server = &http.Server{ Addr: serial.Concat(address, ":", port), Handler: h2c.NewHandler(listener, h2s), ReadHeaderTimeout: time.Second * 4, } } else { server = &http.Server{ Addr: serial.Concat(address, ":", port), TLSConfig: config.GetTLSConfig(tls.WithNextProto("h2")), Handler: listener, ReadHeaderTimeout: time.Second * 4, } } listener.server = server go func() { tcpListener, err := internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), Port: int(port), }, streamSettings.SocketSettings) if err != nil { newError("failed to listen on", address, ":", port).Base(err).WriteToLog(session.ExportIDToError(ctx)) return } if config == nil { err = server.Serve(tcpListener) if err != nil { newError("stoping serving H2C").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } else { err = server.ServeTLS(tcpListener, "", "") if err != nil { newError("stoping serving TLS").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } }() return listener, nil } func init() { common.Must(internet.RegisterTransportListener(protocolName, Listen)) } ================================================ FILE: transport/internet/internet.go ================================================ package internet //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/kcp/config.go ================================================ // +build !confonly package kcp import ( "crypto/cipher" "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) const protocolName = "mkcp" // GetMTUValue returns the value of MTU settings. func (c *Config) GetMTUValue() uint32 { if c == nil || c.Mtu == nil { return 1350 } return c.Mtu.Value } // GetTTIValue returns the value of TTI settings. func (c *Config) GetTTIValue() uint32 { if c == nil || c.Tti == nil { return 50 } return c.Tti.Value } // GetUplinkCapacityValue returns the value of UplinkCapacity settings. func (c *Config) GetUplinkCapacityValue() uint32 { if c == nil || c.UplinkCapacity == nil { return 5 } return c.UplinkCapacity.Value } // GetDownlinkCapacityValue returns the value of DownlinkCapacity settings. func (c *Config) GetDownlinkCapacityValue() uint32 { if c == nil || c.DownlinkCapacity == nil { return 20 } return c.DownlinkCapacity.Value } // GetWriteBufferSize returns the size of WriterBuffer in bytes. func (c *Config) GetWriteBufferSize() uint32 { if c == nil || c.WriteBuffer == nil { return 2 * 1024 * 1024 } return c.WriteBuffer.Size } // GetReadBufferSize returns the size of ReadBuffer in bytes. func (c *Config) GetReadBufferSize() uint32 { if c == nil || c.ReadBuffer == nil { return 2 * 1024 * 1024 } return c.ReadBuffer.Size } // GetSecurity returns the security settings. func (c *Config) GetSecurity() (cipher.AEAD, error) { if c.Seed != nil { return NewAEADAESGCMBasedOnSeed(c.Seed.Seed), nil } return NewSimpleAuthenticator(), nil } func (c *Config) GetPackerHeader() (internet.PacketHeader, error) { if c.HeaderConfig != nil { rawConfig, err := c.HeaderConfig.GetInstance() if err != nil { return nil, err } return internet.CreatePacketHeader(rawConfig) } return nil, nil } func (c *Config) GetSendingInFlightSize() uint32 { size := c.GetUplinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue()) if size < 8 { size = 8 } return size } func (c *Config) GetSendingBufferSize() uint32 { return c.GetWriteBufferSize() / c.GetMTUValue() } func (c *Config) GetReceivingInFlightSize() uint32 { size := c.GetDownlinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue()) if size < 8 { size = 8 } return size } func (c *Config) GetReceivingBufferSize() uint32 { return c.GetReadBufferSize() / c.GetMTUValue() } func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/kcp/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/kcp/config.proto package kcp import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // Maximum Transmission Unit, in bytes. type MTU struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *MTU) Reset() { *x = MTU{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MTU) String() string { return protoimpl.X.MessageStringOf(x) } func (*MTU) ProtoMessage() {} func (x *MTU) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MTU.ProtoReflect.Descriptor instead. func (*MTU) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{0} } func (x *MTU) GetValue() uint32 { if x != nil { return x.Value } return 0 } // Transmission Time Interview, in milli-sec. type TTI struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *TTI) Reset() { *x = TTI{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TTI) String() string { return protoimpl.X.MessageStringOf(x) } func (*TTI) ProtoMessage() {} func (x *TTI) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TTI.ProtoReflect.Descriptor instead. func (*TTI) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{1} } func (x *TTI) GetValue() uint32 { if x != nil { return x.Value } return 0 } // Uplink capacity, in MB. type UplinkCapacity struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *UplinkCapacity) Reset() { *x = UplinkCapacity{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UplinkCapacity) String() string { return protoimpl.X.MessageStringOf(x) } func (*UplinkCapacity) ProtoMessage() {} func (x *UplinkCapacity) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UplinkCapacity.ProtoReflect.Descriptor instead. func (*UplinkCapacity) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{2} } func (x *UplinkCapacity) GetValue() uint32 { if x != nil { return x.Value } return 0 } // Downlink capacity, in MB. type DownlinkCapacity struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` } func (x *DownlinkCapacity) Reset() { *x = DownlinkCapacity{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DownlinkCapacity) String() string { return protoimpl.X.MessageStringOf(x) } func (*DownlinkCapacity) ProtoMessage() {} func (x *DownlinkCapacity) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DownlinkCapacity.ProtoReflect.Descriptor instead. func (*DownlinkCapacity) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{3} } func (x *DownlinkCapacity) GetValue() uint32 { if x != nil { return x.Value } return 0 } type WriteBuffer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Buffer size in bytes. Size uint32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` } func (x *WriteBuffer) Reset() { *x = WriteBuffer{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *WriteBuffer) String() string { return protoimpl.X.MessageStringOf(x) } func (*WriteBuffer) ProtoMessage() {} func (x *WriteBuffer) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use WriteBuffer.ProtoReflect.Descriptor instead. func (*WriteBuffer) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{4} } func (x *WriteBuffer) GetSize() uint32 { if x != nil { return x.Size } return 0 } type ReadBuffer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Buffer size in bytes. Size uint32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` } func (x *ReadBuffer) Reset() { *x = ReadBuffer{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ReadBuffer) String() string { return protoimpl.X.MessageStringOf(x) } func (*ReadBuffer) ProtoMessage() {} func (x *ReadBuffer) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ReadBuffer.ProtoReflect.Descriptor instead. func (*ReadBuffer) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{5} } func (x *ReadBuffer) GetSize() uint32 { if x != nil { return x.Size } return 0 } type ConnectionReuse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Enable bool `protobuf:"varint,1,opt,name=enable,proto3" json:"enable,omitempty"` } func (x *ConnectionReuse) Reset() { *x = ConnectionReuse{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ConnectionReuse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ConnectionReuse) ProtoMessage() {} func (x *ConnectionReuse) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ConnectionReuse.ProtoReflect.Descriptor instead. func (*ConnectionReuse) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{6} } func (x *ConnectionReuse) GetEnable() bool { if x != nil { return x.Enable } return false } // Maximum Transmission Unit, in bytes. type EncryptionSeed struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Seed string `protobuf:"bytes,1,opt,name=seed,proto3" json:"seed,omitempty"` } func (x *EncryptionSeed) Reset() { *x = EncryptionSeed{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EncryptionSeed) String() string { return protoimpl.X.MessageStringOf(x) } func (*EncryptionSeed) ProtoMessage() {} func (x *EncryptionSeed) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EncryptionSeed.ProtoReflect.Descriptor instead. func (*EncryptionSeed) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{7} } func (x *EncryptionSeed) GetSeed() string { if x != nil { return x.Seed } return "" } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Mtu *MTU `protobuf:"bytes,1,opt,name=mtu,proto3" json:"mtu,omitempty"` Tti *TTI `protobuf:"bytes,2,opt,name=tti,proto3" json:"tti,omitempty"` UplinkCapacity *UplinkCapacity `protobuf:"bytes,3,opt,name=uplink_capacity,json=uplinkCapacity,proto3" json:"uplink_capacity,omitempty"` DownlinkCapacity *DownlinkCapacity `protobuf:"bytes,4,opt,name=downlink_capacity,json=downlinkCapacity,proto3" json:"downlink_capacity,omitempty"` Congestion bool `protobuf:"varint,5,opt,name=congestion,proto3" json:"congestion,omitempty"` WriteBuffer *WriteBuffer `protobuf:"bytes,6,opt,name=write_buffer,json=writeBuffer,proto3" json:"write_buffer,omitempty"` ReadBuffer *ReadBuffer `protobuf:"bytes,7,opt,name=read_buffer,json=readBuffer,proto3" json:"read_buffer,omitempty"` HeaderConfig *serial.TypedMessage `protobuf:"bytes,8,opt,name=header_config,json=headerConfig,proto3" json:"header_config,omitempty"` Seed *EncryptionSeed `protobuf:"bytes,10,opt,name=seed,proto3" json:"seed,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_kcp_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_kcp_config_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{8} } func (x *Config) GetMtu() *MTU { if x != nil { return x.Mtu } return nil } func (x *Config) GetTti() *TTI { if x != nil { return x.Tti } return nil } func (x *Config) GetUplinkCapacity() *UplinkCapacity { if x != nil { return x.UplinkCapacity } return nil } func (x *Config) GetDownlinkCapacity() *DownlinkCapacity { if x != nil { return x.DownlinkCapacity } return nil } func (x *Config) GetCongestion() bool { if x != nil { return x.Congestion } return false } func (x *Config) GetWriteBuffer() *WriteBuffer { if x != nil { return x.WriteBuffer } return nil } func (x *Config) GetReadBuffer() *ReadBuffer { if x != nil { return x.ReadBuffer } return nil } func (x *Config) GetHeaderConfig() *serial.TypedMessage { if x != nil { return x.HeaderConfig } return nil } func (x *Config) GetSeed() *EncryptionSeed { if x != nil { return x.Seed } return nil } var File_transport_internet_kcp_config_proto protoreflect.FileDescriptor var file_transport_internet_kcp_config_proto_rawDesc = []byte{ 0x0a, 0x23, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x63, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x03, 0x4d, 0x54, 0x55, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1b, 0x0a, 0x03, 0x54, 0x54, 0x49, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x28, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x21, 0x0a, 0x0b, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x20, 0x0a, 0x0a, 0x52, 0x65, 0x61, 0x64, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x29, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x75, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x24, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x65, 0x64, 0x22, 0x97, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x4d, 0x54, 0x55, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x38, 0x0a, 0x03, 0x74, 0x74, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x54, 0x54, 0x49, 0x52, 0x03, 0x74, 0x74, 0x69, 0x12, 0x5a, 0x0a, 0x0f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x52, 0x0e, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x60, 0x0a, 0x11, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x4b, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x45, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x04, 0x73, 0x65, 0x65, 0x64, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x42, 0x74, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x50, 0x01, 0x5a, 0x25, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x63, 0x70, 0xaa, 0x02, 0x21, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x4b, 0x63, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_kcp_config_proto_rawDescOnce sync.Once file_transport_internet_kcp_config_proto_rawDescData = file_transport_internet_kcp_config_proto_rawDesc ) func file_transport_internet_kcp_config_proto_rawDescGZIP() []byte { file_transport_internet_kcp_config_proto_rawDescOnce.Do(func() { file_transport_internet_kcp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_kcp_config_proto_rawDescData) }) return file_transport_internet_kcp_config_proto_rawDescData } var file_transport_internet_kcp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_transport_internet_kcp_config_proto_goTypes = []interface{}{ (*MTU)(nil), // 0: v2ray.core.transport.internet.kcp.MTU (*TTI)(nil), // 1: v2ray.core.transport.internet.kcp.TTI (*UplinkCapacity)(nil), // 2: v2ray.core.transport.internet.kcp.UplinkCapacity (*DownlinkCapacity)(nil), // 3: v2ray.core.transport.internet.kcp.DownlinkCapacity (*WriteBuffer)(nil), // 4: v2ray.core.transport.internet.kcp.WriteBuffer (*ReadBuffer)(nil), // 5: v2ray.core.transport.internet.kcp.ReadBuffer (*ConnectionReuse)(nil), // 6: v2ray.core.transport.internet.kcp.ConnectionReuse (*EncryptionSeed)(nil), // 7: v2ray.core.transport.internet.kcp.EncryptionSeed (*Config)(nil), // 8: v2ray.core.transport.internet.kcp.Config (*serial.TypedMessage)(nil), // 9: v2ray.core.common.serial.TypedMessage } var file_transport_internet_kcp_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.transport.internet.kcp.Config.mtu:type_name -> v2ray.core.transport.internet.kcp.MTU 1, // 1: v2ray.core.transport.internet.kcp.Config.tti:type_name -> v2ray.core.transport.internet.kcp.TTI 2, // 2: v2ray.core.transport.internet.kcp.Config.uplink_capacity:type_name -> v2ray.core.transport.internet.kcp.UplinkCapacity 3, // 3: v2ray.core.transport.internet.kcp.Config.downlink_capacity:type_name -> v2ray.core.transport.internet.kcp.DownlinkCapacity 4, // 4: v2ray.core.transport.internet.kcp.Config.write_buffer:type_name -> v2ray.core.transport.internet.kcp.WriteBuffer 5, // 5: v2ray.core.transport.internet.kcp.Config.read_buffer:type_name -> v2ray.core.transport.internet.kcp.ReadBuffer 9, // 6: v2ray.core.transport.internet.kcp.Config.header_config:type_name -> v2ray.core.common.serial.TypedMessage 7, // 7: v2ray.core.transport.internet.kcp.Config.seed:type_name -> v2ray.core.transport.internet.kcp.EncryptionSeed 8, // [8:8] is the sub-list for method output_type 8, // [8:8] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name } func init() { file_transport_internet_kcp_config_proto_init() } func file_transport_internet_kcp_config_proto_init() { if File_transport_internet_kcp_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_kcp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MTU); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TTI); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UplinkCapacity); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DownlinkCapacity); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WriteBuffer); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ReadBuffer); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ConnectionReuse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EncryptionSeed); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_kcp_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_kcp_config_proto_rawDesc, NumEnums: 0, NumMessages: 9, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_kcp_config_proto_goTypes, DependencyIndexes: file_transport_internet_kcp_config_proto_depIdxs, MessageInfos: file_transport_internet_kcp_config_proto_msgTypes, }.Build() File_transport_internet_kcp_config_proto = out.File file_transport_internet_kcp_config_proto_rawDesc = nil file_transport_internet_kcp_config_proto_goTypes = nil file_transport_internet_kcp_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/kcp/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.kcp; option csharp_namespace = "V2Ray.Core.Transport.Internet.Kcp"; option go_package = "v2ray.com/core/transport/internet/kcp"; option java_package = "com.v2ray.core.transport.internet.kcp"; option java_multiple_files = true; import "common/serial/typed_message.proto"; // Maximum Transmission Unit, in bytes. message MTU { uint32 value = 1; } // Transmission Time Interview, in milli-sec. message TTI { uint32 value = 1; } // Uplink capacity, in MB. message UplinkCapacity { uint32 value = 1; } // Downlink capacity, in MB. message DownlinkCapacity { uint32 value = 1; } message WriteBuffer { // Buffer size in bytes. uint32 size = 1; } message ReadBuffer { // Buffer size in bytes. uint32 size = 1; } message ConnectionReuse { bool enable = 1; } // Maximum Transmission Unit, in bytes. message EncryptionSeed { string seed = 1; } message Config { MTU mtu = 1; TTI tti = 2; UplinkCapacity uplink_capacity = 3; DownlinkCapacity downlink_capacity = 4; bool congestion = 5; WriteBuffer write_buffer = 6; ReadBuffer read_buffer = 7; v2ray.core.common.serial.TypedMessage header_config = 8; reserved 9; EncryptionSeed seed = 10; } ================================================ FILE: transport/internet/kcp/connection.go ================================================ // +build !confonly package kcp import ( "bytes" "io" "net" "runtime" "sync" "sync/atomic" "time" "v2ray.com/core/common/buf" "v2ray.com/core/common/signal" "v2ray.com/core/common/signal/semaphore" ) var ( ErrIOTimeout = newError("Read/Write timeout") ErrClosedListener = newError("Listener closed.") ErrClosedConnection = newError("Connection closed.") ) // State of the connection type State int32 // Is returns true if current State is one of the candidates. func (s State) Is(states ...State) bool { for _, state := range states { if s == state { return true } } return false } const ( StateActive State = 0 // Connection is active StateReadyToClose State = 1 // Connection is closed locally StatePeerClosed State = 2 // Connection is closed on remote StateTerminating State = 3 // Connection is ready to be destroyed locally StatePeerTerminating State = 4 // Connection is ready to be destroyed on remote StateTerminated State = 5 // Connection is destroyed. ) func nowMillisec() int64 { now := time.Now() return now.Unix()*1000 + int64(now.Nanosecond()/1000000) } type RoundTripInfo struct { sync.RWMutex variation uint32 srtt uint32 rto uint32 minRtt uint32 updatedTimestamp uint32 } func (info *RoundTripInfo) UpdatePeerRTO(rto uint32, current uint32) { info.Lock() defer info.Unlock() if current-info.updatedTimestamp < 3000 { return } info.updatedTimestamp = current info.rto = rto } func (info *RoundTripInfo) Update(rtt uint32, current uint32) { if rtt > 0x7FFFFFFF { return } info.Lock() defer info.Unlock() // https://tools.ietf.org/html/rfc6298 if info.srtt == 0 { info.srtt = rtt info.variation = rtt / 2 } else { delta := rtt - info.srtt if info.srtt > rtt { delta = info.srtt - rtt } info.variation = (3*info.variation + delta) / 4 info.srtt = (7*info.srtt + rtt) / 8 if info.srtt < info.minRtt { info.srtt = info.minRtt } } var rto uint32 if info.minRtt < 4*info.variation { rto = info.srtt + 4*info.variation } else { rto = info.srtt + info.variation } if rto > 10000 { rto = 10000 } info.rto = rto * 5 / 4 info.updatedTimestamp = current } func (info *RoundTripInfo) Timeout() uint32 { info.RLock() defer info.RUnlock() return info.rto } func (info *RoundTripInfo) SmoothedTime() uint32 { info.RLock() defer info.RUnlock() return info.srtt } type Updater struct { interval int64 shouldContinue func() bool shouldTerminate func() bool updateFunc func() notifier *semaphore.Instance } func NewUpdater(interval uint32, shouldContinue func() bool, shouldTerminate func() bool, updateFunc func()) *Updater { u := &Updater{ interval: int64(time.Duration(interval) * time.Millisecond), shouldContinue: shouldContinue, shouldTerminate: shouldTerminate, updateFunc: updateFunc, notifier: semaphore.New(1), } return u } func (u *Updater) WakeUp() { select { case <-u.notifier.Wait(): go u.run() default: } } func (u *Updater) run() { defer u.notifier.Signal() if u.shouldTerminate() { return } ticker := time.NewTicker(u.Interval()) for u.shouldContinue() { u.updateFunc() <-ticker.C } ticker.Stop() } func (u *Updater) Interval() time.Duration { return time.Duration(atomic.LoadInt64(&u.interval)) } func (u *Updater) SetInterval(d time.Duration) { atomic.StoreInt64(&u.interval, int64(d)) } type ConnMetadata struct { LocalAddr net.Addr RemoteAddr net.Addr Conversation uint16 } // Connection is a KCP connection over UDP. type Connection struct { meta ConnMetadata closer io.Closer rd time.Time wd time.Time // write deadline since int64 dataInput *signal.Notifier dataOutput *signal.Notifier Config *Config state State stateBeginTime uint32 lastIncomingTime uint32 lastPingTime uint32 mss uint32 roundTrip *RoundTripInfo receivingWorker *ReceivingWorker sendingWorker *SendingWorker output SegmentWriter dataUpdater *Updater pingUpdater *Updater } // NewConnection create a new KCP connection between local and remote. func NewConnection(meta ConnMetadata, writer PacketWriter, closer io.Closer, config *Config) *Connection { newError("#", meta.Conversation, " creating connection to ", meta.RemoteAddr).WriteToLog() conn := &Connection{ meta: meta, closer: closer, since: nowMillisec(), dataInput: signal.NewNotifier(), dataOutput: signal.NewNotifier(), Config: config, output: NewRetryableWriter(NewSegmentWriter(writer)), mss: config.GetMTUValue() - uint32(writer.Overhead()) - DataSegmentOverhead, roundTrip: &RoundTripInfo{ rto: 100, minRtt: config.GetTTIValue(), }, } conn.receivingWorker = NewReceivingWorker(conn) conn.sendingWorker = NewSendingWorker(conn) isTerminating := func() bool { return conn.State().Is(StateTerminating, StateTerminated) } isTerminated := func() bool { return conn.State() == StateTerminated } conn.dataUpdater = NewUpdater( config.GetTTIValue(), func() bool { return !isTerminating() && (conn.sendingWorker.UpdateNecessary() || conn.receivingWorker.UpdateNecessary()) }, isTerminating, conn.updateTask) conn.pingUpdater = NewUpdater( 5000, // 5 seconds func() bool { return !isTerminated() }, isTerminated, conn.updateTask) conn.pingUpdater.WakeUp() return conn } func (c *Connection) Elapsed() uint32 { return uint32(nowMillisec() - c.since) } // ReadMultiBuffer implements buf.Reader. func (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) { if c == nil { return nil, io.EOF } for { if c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) { return nil, io.EOF } mb := c.receivingWorker.ReadMultiBuffer() if !mb.IsEmpty() { c.dataUpdater.WakeUp() return mb, nil } if c.State() == StatePeerTerminating { return nil, io.EOF } if err := c.waitForDataInput(); err != nil { return nil, err } } } func (c *Connection) waitForDataInput() error { for i := 0; i < 16; i++ { select { case <-c.dataInput.Wait(): return nil default: runtime.Gosched() } } duration := time.Second * 16 if !c.rd.IsZero() { duration = time.Until(c.rd) if duration < 0 { return ErrIOTimeout } } timeout := time.NewTimer(duration) defer timeout.Stop() select { case <-c.dataInput.Wait(): case <-timeout.C: if !c.rd.IsZero() && c.rd.Before(time.Now()) { return ErrIOTimeout } } return nil } // Read implements the Conn Read method. func (c *Connection) Read(b []byte) (int, error) { if c == nil { return 0, io.EOF } for { if c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) { return 0, io.EOF } nBytes := c.receivingWorker.Read(b) if nBytes > 0 { c.dataUpdater.WakeUp() return nBytes, nil } if err := c.waitForDataInput(); err != nil { return 0, err } } } func (c *Connection) waitForDataOutput() error { for i := 0; i < 16; i++ { select { case <-c.dataOutput.Wait(): return nil default: runtime.Gosched() } } duration := time.Second * 16 if !c.wd.IsZero() { duration = time.Until(c.wd) if duration < 0 { return ErrIOTimeout } } timeout := time.NewTimer(duration) defer timeout.Stop() select { case <-c.dataOutput.Wait(): case <-timeout.C: if !c.wd.IsZero() && c.wd.Before(time.Now()) { return ErrIOTimeout } } return nil } // Write implements io.Writer. func (c *Connection) Write(b []byte) (int, error) { reader := bytes.NewReader(b) if err := c.writeMultiBufferInternal(reader); err != nil { return 0, err } return len(b), nil } // WriteMultiBuffer implements buf.Writer. func (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error { reader := &buf.MultiBufferContainer{ MultiBuffer: mb, } defer reader.Close() return c.writeMultiBufferInternal(reader) } func (c *Connection) writeMultiBufferInternal(reader io.Reader) error { updatePending := false defer func() { if updatePending { c.dataUpdater.WakeUp() } }() var b *buf.Buffer defer b.Release() for { for { if c == nil || c.State() != StateActive { return io.ErrClosedPipe } if b == nil { b = buf.New() _, err := b.ReadFrom(io.LimitReader(reader, int64(c.mss))) if err != nil { return nil } } if !c.sendingWorker.Push(b) { break } updatePending = true b = nil } if updatePending { c.dataUpdater.WakeUp() updatePending = false } if err := c.waitForDataOutput(); err != nil { return err } } } func (c *Connection) SetState(state State) { current := c.Elapsed() atomic.StoreInt32((*int32)(&c.state), int32(state)) atomic.StoreUint32(&c.stateBeginTime, current) newError("#", c.meta.Conversation, " entering state ", state, " at ", current).AtDebug().WriteToLog() switch state { case StateReadyToClose: c.receivingWorker.CloseRead() case StatePeerClosed: c.sendingWorker.CloseWrite() case StateTerminating: c.receivingWorker.CloseRead() c.sendingWorker.CloseWrite() c.pingUpdater.SetInterval(time.Second) case StatePeerTerminating: c.sendingWorker.CloseWrite() c.pingUpdater.SetInterval(time.Second) case StateTerminated: c.receivingWorker.CloseRead() c.sendingWorker.CloseWrite() c.pingUpdater.SetInterval(time.Second) c.dataUpdater.WakeUp() c.pingUpdater.WakeUp() go c.Terminate() } } // Close closes the connection. func (c *Connection) Close() error { if c == nil { return ErrClosedConnection } c.dataInput.Signal() c.dataOutput.Signal() switch c.State() { case StateReadyToClose, StateTerminating, StateTerminated: return ErrClosedConnection case StateActive: c.SetState(StateReadyToClose) case StatePeerClosed: c.SetState(StateTerminating) case StatePeerTerminating: c.SetState(StateTerminated) } newError("#", c.meta.Conversation, " closing connection to ", c.meta.RemoteAddr).WriteToLog() return nil } // LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it. func (c *Connection) LocalAddr() net.Addr { if c == nil { return nil } return c.meta.LocalAddr } // RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it. func (c *Connection) RemoteAddr() net.Addr { if c == nil { return nil } return c.meta.RemoteAddr } // SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline. func (c *Connection) SetDeadline(t time.Time) error { if err := c.SetReadDeadline(t); err != nil { return err } return c.SetWriteDeadline(t) } // SetReadDeadline implements the Conn SetReadDeadline method. func (c *Connection) SetReadDeadline(t time.Time) error { if c == nil || c.State() != StateActive { return ErrClosedConnection } c.rd = t return nil } // SetWriteDeadline implements the Conn SetWriteDeadline method. func (c *Connection) SetWriteDeadline(t time.Time) error { if c == nil || c.State() != StateActive { return ErrClosedConnection } c.wd = t return nil } // kcp update, input loop func (c *Connection) updateTask() { c.flush() } func (c *Connection) Terminate() { if c == nil { return } newError("#", c.meta.Conversation, " terminating connection to ", c.RemoteAddr()).WriteToLog() //v.SetState(StateTerminated) c.dataInput.Signal() c.dataOutput.Signal() c.closer.Close() c.sendingWorker.Release() c.receivingWorker.Release() } func (c *Connection) HandleOption(opt SegmentOption) { if (opt & SegmentOptionClose) == SegmentOptionClose { c.OnPeerClosed() } } func (c *Connection) OnPeerClosed() { switch c.State() { case StateReadyToClose: c.SetState(StateTerminating) case StateActive: c.SetState(StatePeerClosed) } } // Input when you received a low level packet (eg. UDP packet), call it func (c *Connection) Input(segments []Segment) { current := c.Elapsed() atomic.StoreUint32(&c.lastIncomingTime, current) for _, seg := range segments { if seg.Conversation() != c.meta.Conversation { break } switch seg := seg.(type) { case *DataSegment: c.HandleOption(seg.Option) c.receivingWorker.ProcessSegment(seg) if c.receivingWorker.IsDataAvailable() { c.dataInput.Signal() } c.dataUpdater.WakeUp() case *AckSegment: c.HandleOption(seg.Option) c.sendingWorker.ProcessSegment(current, seg, c.roundTrip.Timeout()) c.dataOutput.Signal() c.dataUpdater.WakeUp() case *CmdOnlySegment: c.HandleOption(seg.Option) if seg.Command() == CommandTerminate { switch c.State() { case StateActive, StatePeerClosed: c.SetState(StatePeerTerminating) case StateReadyToClose: c.SetState(StateTerminating) case StateTerminating: c.SetState(StateTerminated) } } if seg.Option == SegmentOptionClose || seg.Command() == CommandTerminate { c.dataInput.Signal() c.dataOutput.Signal() } c.sendingWorker.ProcessReceivingNext(seg.ReceivingNext) c.receivingWorker.ProcessSendingNext(seg.SendingNext) c.roundTrip.UpdatePeerRTO(seg.PeerRTO, current) seg.Release() default: } } } func (c *Connection) flush() { current := c.Elapsed() if c.State() == StateTerminated { return } if c.State() == StateActive && current-atomic.LoadUint32(&c.lastIncomingTime) >= 30000 { c.Close() } if c.State() == StateReadyToClose && c.sendingWorker.IsEmpty() { c.SetState(StateTerminating) } if c.State() == StateTerminating { newError("#", c.meta.Conversation, " sending terminating cmd.").AtDebug().WriteToLog() c.Ping(current, CommandTerminate) if current-atomic.LoadUint32(&c.stateBeginTime) > 8000 { c.SetState(StateTerminated) } return } if c.State() == StatePeerTerminating && current-atomic.LoadUint32(&c.stateBeginTime) > 4000 { c.SetState(StateTerminating) } if c.State() == StateReadyToClose && current-atomic.LoadUint32(&c.stateBeginTime) > 15000 { c.SetState(StateTerminating) } // flush acknowledges c.receivingWorker.Flush(current) c.sendingWorker.Flush(current) if current-atomic.LoadUint32(&c.lastPingTime) >= 3000 { c.Ping(current, CommandPing) } } func (c *Connection) State() State { return State(atomic.LoadInt32((*int32)(&c.state))) } func (c *Connection) Ping(current uint32, cmd Command) { seg := NewCmdOnlySegment() seg.Conv = c.meta.Conversation seg.Cmd = cmd seg.ReceivingNext = c.receivingWorker.NextNumber() seg.SendingNext = c.sendingWorker.FirstUnacknowledged() seg.PeerRTO = c.roundTrip.Timeout() if c.State() == StateReadyToClose { seg.Option = SegmentOptionClose } c.output.Write(seg) atomic.StoreUint32(&c.lastPingTime, current) seg.Release() } ================================================ FILE: transport/internet/kcp/connection_test.go ================================================ package kcp_test import ( "io" "testing" "time" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/internet/kcp" ) type NoOpCloser int func (NoOpCloser) Close() error { return nil } func TestConnectionReadTimeout(t *testing.T) { conn := NewConnection(ConnMetadata{Conversation: 1}, &KCPPacketWriter{ Writer: buf.DiscardBytes, }, NoOpCloser(0), &Config{}) conn.SetReadDeadline(time.Now().Add(time.Second)) b := make([]byte, 1024) nBytes, err := conn.Read(b) if nBytes != 0 || err == nil { t.Error("unexpected read: ", nBytes, err) } conn.Terminate() } func TestConnectionInterface(t *testing.T) { _ = (io.Writer)(new(Connection)) _ = (io.Reader)(new(Connection)) _ = (buf.Reader)(new(Connection)) _ = (buf.Writer)(new(Connection)) } ================================================ FILE: transport/internet/kcp/crypt.go ================================================ // +build !confonly package kcp import ( "crypto/cipher" "encoding/binary" "hash/fnv" "v2ray.com/core/common" ) // SimpleAuthenticator is a legacy AEAD used for KCP encryption. type SimpleAuthenticator struct{} // NewSimpleAuthenticator creates a new SimpleAuthenticator func NewSimpleAuthenticator() cipher.AEAD { return &SimpleAuthenticator{} } // NonceSize implements cipher.AEAD.NonceSize(). func (*SimpleAuthenticator) NonceSize() int { return 0 } // Overhead implements cipher.AEAD.NonceSize(). func (*SimpleAuthenticator) Overhead() int { return 6 } // Seal implements cipher.AEAD.Seal(). func (a *SimpleAuthenticator) Seal(dst, nonce, plain, extra []byte) []byte { dst = append(dst, 0, 0, 0, 0, 0, 0) // 4 bytes for hash, and then 2 bytes for length binary.BigEndian.PutUint16(dst[4:], uint16(len(plain))) dst = append(dst, plain...) fnvHash := fnv.New32a() common.Must2(fnvHash.Write(dst[4:])) fnvHash.Sum(dst[:0]) dstLen := len(dst) xtra := 4 - dstLen%4 if xtra != 4 { dst = append(dst, make([]byte, xtra)...) } xorfwd(dst) if xtra != 4 { dst = dst[:dstLen] } return dst } // Open implements cipher.AEAD.Open(). func (a *SimpleAuthenticator) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) { dst = append(dst, cipherText...) dstLen := len(dst) xtra := 4 - dstLen%4 if xtra != 4 { dst = append(dst, make([]byte, xtra)...) } xorbkd(dst) if xtra != 4 { dst = dst[:dstLen] } fnvHash := fnv.New32a() common.Must2(fnvHash.Write(dst[4:])) if binary.BigEndian.Uint32(dst[:4]) != fnvHash.Sum32() { return nil, newError("invalid auth") } length := binary.BigEndian.Uint16(dst[4:6]) if len(dst)-6 != int(length) { return nil, newError("invalid auth") } return dst[6:], nil } ================================================ FILE: transport/internet/kcp/crypt_test.go ================================================ package kcp_test import ( "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" . "v2ray.com/core/transport/internet/kcp" ) func TestSimpleAuthenticator(t *testing.T) { cache := make([]byte, 512) payload := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'} auth := NewSimpleAuthenticator() b := auth.Seal(cache[:0], nil, payload, nil) c, err := auth.Open(cache[:0], nil, b, nil) common.Must(err) if r := cmp.Diff(c, payload); r != "" { t.Error(r) } } func TestSimpleAuthenticator2(t *testing.T) { cache := make([]byte, 512) payload := []byte{'a', 'b'} auth := NewSimpleAuthenticator() b := auth.Seal(cache[:0], nil, payload, nil) c, err := auth.Open(cache[:0], nil, b, nil) common.Must(err) if r := cmp.Diff(c, payload); r != "" { t.Error(r) } } ================================================ FILE: transport/internet/kcp/cryptreal.go ================================================ package kcp import ( "crypto/aes" "crypto/cipher" "crypto/sha256" "v2ray.com/core/common" ) func NewAEADAESGCMBasedOnSeed(seed string) cipher.AEAD { HashedSeed := sha256.Sum256([]byte(seed)) aesBlock := common.Must2(aes.NewCipher(HashedSeed[:16])).(cipher.Block) return common.Must2(cipher.NewGCM(aesBlock)).(cipher.AEAD) } ================================================ FILE: transport/internet/kcp/dialer.go ================================================ // +build !confonly package kcp import ( "context" "io" "sync/atomic" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/dice" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) var ( globalConv = uint32(dice.RollUint16()) ) func fetchInput(ctx context.Context, input io.Reader, reader PacketReader, conn *Connection) { cache := make(chan *buf.Buffer, 1024) go func() { for { payload := buf.New() if _, err := payload.ReadFrom(input); err != nil { payload.Release() close(cache) return } select { case cache <- payload: default: payload.Release() } } }() for payload := range cache { segments := reader.Read(payload.Bytes()) payload.Release() if len(segments) > 0 { conn.Input(segments) } } } // DialKCP dials a new KCP connections to the specific destination. func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { dest.Network = net.Network_UDP newError("dialing mKCP to ", dest).WriteToLog() rawConn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) if err != nil { return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err) } kcpSettings := streamSettings.ProtocolSettings.(*Config) header, err := kcpSettings.GetPackerHeader() if err != nil { return nil, newError("failed to create packet header").Base(err) } security, err := kcpSettings.GetSecurity() if err != nil { return nil, newError("failed to create security").Base(err) } reader := &KCPPacketReader{ Header: header, Security: security, } writer := &KCPPacketWriter{ Header: header, Security: security, Writer: rawConn, } conv := uint16(atomic.AddUint32(&globalConv, 1)) session := NewConnection(ConnMetadata{ LocalAddr: rawConn.LocalAddr(), RemoteAddr: rawConn.RemoteAddr(), Conversation: conv, }, writer, rawConn, kcpSettings) go fetchInput(ctx, rawConn, reader, session) var iConn internet.Connection = session if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { iConn = xtls.Client(iConn, config.GetXTLSConfig(xtls.WithDestination(dest))) } return iConn, nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, DialKCP)) } ================================================ FILE: transport/internet/kcp/errors.generated.go ================================================ package kcp import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/kcp/io.go ================================================ // +build !confonly package kcp import ( "crypto/cipher" "crypto/rand" "io" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/transport/internet" ) type PacketReader interface { Read([]byte) []Segment } type PacketWriter interface { Overhead() int io.Writer } type KCPPacketReader struct { Security cipher.AEAD Header internet.PacketHeader } func (r *KCPPacketReader) Read(b []byte) []Segment { if r.Header != nil { if int32(len(b)) <= r.Header.Size() { return nil } b = b[r.Header.Size():] } if r.Security != nil { nonceSize := r.Security.NonceSize() overhead := r.Security.Overhead() if len(b) <= nonceSize+overhead { return nil } out, err := r.Security.Open(b[nonceSize:nonceSize], b[:nonceSize], b[nonceSize:], nil) if err != nil { return nil } b = out } var result []Segment for len(b) > 0 { seg, x := ReadSegment(b) if seg == nil { break } result = append(result, seg) b = x } return result } type KCPPacketWriter struct { Header internet.PacketHeader Security cipher.AEAD Writer io.Writer } func (w *KCPPacketWriter) Overhead() int { overhead := 0 if w.Header != nil { overhead += int(w.Header.Size()) } if w.Security != nil { overhead += w.Security.Overhead() } return overhead } func (w *KCPPacketWriter) Write(b []byte) (int, error) { bb := buf.StackNew() defer bb.Release() if w.Header != nil { w.Header.Serialize(bb.Extend(w.Header.Size())) } if w.Security != nil { nonceSize := w.Security.NonceSize() common.Must2(bb.ReadFullFrom(rand.Reader, int32(nonceSize))) nonce := bb.BytesFrom(int32(-nonceSize)) encrypted := bb.Extend(int32(w.Security.Overhead() + len(b))) w.Security.Seal(encrypted[:0], nonce, b, nil) } else { bb.Write(b) } _, err := w.Writer.Write(bb.Bytes()) return len(b), err } ================================================ FILE: transport/internet/kcp/io_test.go ================================================ package kcp_test import ( "testing" . "v2ray.com/core/transport/internet/kcp" ) func TestKCPPacketReader(t *testing.T) { reader := KCPPacketReader{ Security: &SimpleAuthenticator{}, } testCases := []struct { Input []byte Output []Segment }{ { Input: []byte{}, Output: nil, }, { Input: []byte{1}, Output: nil, }, } for _, testCase := range testCases { seg := reader.Read(testCase.Input) if testCase.Output == nil && seg != nil { t.Errorf("Expect nothing returned, but actually %v", seg) } else if testCase.Output != nil && seg == nil { t.Errorf("Expect some output, but got nil") } } } ================================================ FILE: transport/internet/kcp/kcp.go ================================================ // Package kcp - A Fast and Reliable ARQ Protocol // // Acknowledgement: // skywind3000@github for inventing the KCP protocol // xtaci@github for translating to Golang package kcp //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/kcp/kcp_test.go ================================================ package kcp_test import ( "context" "crypto/rand" "io" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" "v2ray.com/core/common" "v2ray.com/core/common/errors" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" . "v2ray.com/core/transport/internet/kcp" ) func TestDialAndListen(t *testing.T) { listerner, err := NewListener(context.Background(), net.LocalHostIP, net.Port(0), &internet.MemoryStreamConfig{ ProtocolName: "mkcp", ProtocolSettings: &Config{}, }, func(conn internet.Connection) { go func(c internet.Connection) { payload := make([]byte, 4096) for { nBytes, err := c.Read(payload) if err != nil { break } for idx, b := range payload[:nBytes] { payload[idx] = b ^ 'c' } c.Write(payload[:nBytes]) } c.Close() }(conn) }) common.Must(err) defer listerner.Close() port := net.Port(listerner.Addr().(*net.UDPAddr).Port) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(func() error { clientConn, err := DialKCP(context.Background(), net.UDPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "mkcp", ProtocolSettings: &Config{}, }) if err != nil { return err } defer clientConn.Close() clientSend := make([]byte, 1024*1024) rand.Read(clientSend) go clientConn.Write(clientSend) clientReceived := make([]byte, 1024*1024) common.Must2(io.ReadFull(clientConn, clientReceived)) clientExpected := make([]byte, 1024*1024) for idx, b := range clientSend { clientExpected[idx] = b ^ 'c' } if r := cmp.Diff(clientReceived, clientExpected); r != "" { return errors.New(r) } return nil }) } if err := errg.Wait(); err != nil { t.Fatal(err) } for i := 0; i < 60 && listerner.ActiveConnections() > 0; i++ { time.Sleep(500 * time.Millisecond) } if v := listerner.ActiveConnections(); v != 0 { t.Error("active connections: ", v) } } ================================================ FILE: transport/internet/kcp/listener.go ================================================ // +build !confonly package kcp import ( "context" "crypto/cipher" gotls "crypto/tls" "sync" goxtls "github.com/xtls/go" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/internet/xtls" ) type ConnectionID struct { Remote net.Address Port net.Port Conv uint16 } // Listener defines a server listening for connections type Listener struct { sync.Mutex sessions map[ConnectionID]*Connection hub *udp.Hub tlsConfig *gotls.Config xtlsConfig *goxtls.Config config *Config reader PacketReader header internet.PacketHeader security cipher.AEAD addConn internet.ConnHandler } func NewListener(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (*Listener, error) { kcpSettings := streamSettings.ProtocolSettings.(*Config) header, err := kcpSettings.GetPackerHeader() if err != nil { return nil, newError("failed to create packet header").Base(err).AtError() } security, err := kcpSettings.GetSecurity() if err != nil { return nil, newError("failed to create security").Base(err).AtError() } l := &Listener{ header: header, security: security, reader: &KCPPacketReader{ Header: header, Security: security, }, sessions: make(map[ConnectionID]*Connection), config: kcpSettings, addConn: addConn, } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { l.tlsConfig = config.GetTLSConfig() } if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { l.xtlsConfig = config.GetXTLSConfig() } hub, err := udp.ListenUDP(ctx, address, port, streamSettings, udp.HubCapacity(1024)) if err != nil { return nil, err } l.Lock() l.hub = hub l.Unlock() newError("listening on ", address, ":", port).WriteToLog() go l.handlePackets() return l, nil } func (l *Listener) handlePackets() { receive := l.hub.Receive() for payload := range receive { l.OnReceive(payload.Payload, payload.Source) } } func (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination) { segments := l.reader.Read(payload.Bytes()) payload.Release() if len(segments) == 0 { newError("discarding invalid payload from ", src).WriteToLog() return } conv := segments[0].Conversation() cmd := segments[0].Command() id := ConnectionID{ Remote: src.Address, Port: src.Port, Conv: conv, } l.Lock() defer l.Unlock() conn, found := l.sessions[id] if !found { if cmd == CommandTerminate { return } writer := &Writer{ id: id, hub: l.hub, dest: src, listener: l, } remoteAddr := &net.UDPAddr{ IP: src.Address.IP(), Port: int(src.Port), } localAddr := l.hub.Addr() conn = NewConnection(ConnMetadata{ LocalAddr: localAddr, RemoteAddr: remoteAddr, Conversation: conv, }, &KCPPacketWriter{ Header: l.header, Security: l.security, Writer: writer, }, writer, l.config) var netConn internet.Connection = conn if l.tlsConfig != nil { netConn = gotls.Server(conn, l.tlsConfig) } else if l.xtlsConfig != nil { netConn = goxtls.Server(conn, l.xtlsConfig) } l.addConn(netConn) l.sessions[id] = conn } conn.Input(segments) } func (l *Listener) Remove(id ConnectionID) { l.Lock() delete(l.sessions, id) l.Unlock() } // Close stops listening on the UDP address. Already Accepted connections are not closed. func (l *Listener) Close() error { l.hub.Close() l.Lock() defer l.Unlock() for _, conn := range l.sessions { go conn.Terminate() } return nil } func (l *Listener) ActiveConnections() int { l.Lock() defer l.Unlock() return len(l.sessions) } // Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it. func (l *Listener) Addr() net.Addr { return l.hub.Addr() } type Writer struct { id ConnectionID dest net.Destination hub *udp.Hub listener *Listener } func (w *Writer) Write(payload []byte) (int, error) { return w.hub.WriteTo(payload, w.dest) } func (w *Writer) Close() error { w.listener.Remove(w.id) return nil } func ListenKCP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { return NewListener(ctx, address, port, streamSettings, addConn) } func init() { common.Must(internet.RegisterTransportListener(protocolName, ListenKCP)) } ================================================ FILE: transport/internet/kcp/output.go ================================================ // +build !confonly package kcp import ( "io" "sync" "v2ray.com/core/common/retry" "v2ray.com/core/common/buf" ) type SegmentWriter interface { Write(seg Segment) error } type SimpleSegmentWriter struct { sync.Mutex buffer *buf.Buffer writer io.Writer } func NewSegmentWriter(writer io.Writer) SegmentWriter { return &SimpleSegmentWriter{ writer: writer, buffer: buf.New(), } } func (w *SimpleSegmentWriter) Write(seg Segment) error { w.Lock() defer w.Unlock() w.buffer.Clear() rawBytes := w.buffer.Extend(seg.ByteSize()) seg.Serialize(rawBytes) _, err := w.writer.Write(w.buffer.Bytes()) return err } type RetryableWriter struct { writer SegmentWriter } func NewRetryableWriter(writer SegmentWriter) SegmentWriter { return &RetryableWriter{ writer: writer, } } func (w *RetryableWriter) Write(seg Segment) error { return retry.Timed(5, 100).On(func() error { return w.writer.Write(seg) }) } ================================================ FILE: transport/internet/kcp/receiving.go ================================================ // +build !confonly package kcp import ( "sync" "v2ray.com/core/common/buf" ) type ReceivingWindow struct { cache map[uint32]*DataSegment } func NewReceivingWindow() *ReceivingWindow { return &ReceivingWindow{ cache: make(map[uint32]*DataSegment), } } func (w *ReceivingWindow) Set(id uint32, value *DataSegment) bool { _, f := w.cache[id] if f { return false } w.cache[id] = value return true } func (w *ReceivingWindow) Has(id uint32) bool { _, f := w.cache[id] return f } func (w *ReceivingWindow) Remove(id uint32) *DataSegment { v, f := w.cache[id] if !f { return nil } delete(w.cache, id) return v } type AckList struct { writer SegmentWriter timestamps []uint32 numbers []uint32 nextFlush []uint32 flushCandidates []uint32 dirty bool } func NewAckList(writer SegmentWriter) *AckList { return &AckList{ writer: writer, timestamps: make([]uint32, 0, 128), numbers: make([]uint32, 0, 128), nextFlush: make([]uint32, 0, 128), flushCandidates: make([]uint32, 0, 128), } } func (l *AckList) Add(number uint32, timestamp uint32) { l.timestamps = append(l.timestamps, timestamp) l.numbers = append(l.numbers, number) l.nextFlush = append(l.nextFlush, 0) l.dirty = true } func (l *AckList) Clear(una uint32) { count := 0 for i := 0; i < len(l.numbers); i++ { if l.numbers[i] < una { continue } if i != count { l.numbers[count] = l.numbers[i] l.timestamps[count] = l.timestamps[i] l.nextFlush[count] = l.nextFlush[i] } count++ } if count < len(l.numbers) { l.numbers = l.numbers[:count] l.timestamps = l.timestamps[:count] l.nextFlush = l.nextFlush[:count] l.dirty = true } } func (l *AckList) Flush(current uint32, rto uint32) { l.flushCandidates = l.flushCandidates[:0] seg := NewAckSegment() for i := 0; i < len(l.numbers); i++ { if l.nextFlush[i] > current { if len(l.flushCandidates) < cap(l.flushCandidates) { l.flushCandidates = append(l.flushCandidates, l.numbers[i]) } continue } seg.PutNumber(l.numbers[i]) seg.PutTimestamp(l.timestamps[i]) timeout := rto / 2 if timeout < 20 { timeout = 20 } l.nextFlush[i] = current + timeout if seg.IsFull() { l.writer.Write(seg) seg.Release() seg = NewAckSegment() l.dirty = false } } if l.dirty || !seg.IsEmpty() { for _, number := range l.flushCandidates { if seg.IsFull() { break } seg.PutNumber(number) } l.writer.Write(seg) l.dirty = false } seg.Release() } type ReceivingWorker struct { sync.RWMutex conn *Connection leftOver buf.MultiBuffer window *ReceivingWindow acklist *AckList nextNumber uint32 windowSize uint32 } func NewReceivingWorker(kcp *Connection) *ReceivingWorker { worker := &ReceivingWorker{ conn: kcp, window: NewReceivingWindow(), windowSize: kcp.Config.GetReceivingInFlightSize(), } worker.acklist = NewAckList(worker) return worker } func (w *ReceivingWorker) Release() { w.Lock() buf.ReleaseMulti(w.leftOver) w.leftOver = nil w.Unlock() } func (w *ReceivingWorker) ProcessSendingNext(number uint32) { w.Lock() defer w.Unlock() w.acklist.Clear(number) } func (w *ReceivingWorker) ProcessSegment(seg *DataSegment) { w.Lock() defer w.Unlock() number := seg.Number idx := number - w.nextNumber if idx >= w.windowSize { return } w.acklist.Clear(seg.SendingNext) w.acklist.Add(number, seg.Timestamp) if !w.window.Set(seg.Number, seg) { seg.Release() } } func (w *ReceivingWorker) ReadMultiBuffer() buf.MultiBuffer { if w.leftOver != nil { mb := w.leftOver w.leftOver = nil return mb } mb := make(buf.MultiBuffer, 0, 32) w.Lock() defer w.Unlock() for { seg := w.window.Remove(w.nextNumber) if seg == nil { break } w.nextNumber++ mb = append(mb, seg.Detach()) seg.Release() } return mb } func (w *ReceivingWorker) Read(b []byte) int { mb := w.ReadMultiBuffer() if mb.IsEmpty() { return 0 } mb, nBytes := buf.SplitBytes(mb, b) if !mb.IsEmpty() { w.leftOver = mb } return nBytes } func (w *ReceivingWorker) IsDataAvailable() bool { w.RLock() defer w.RUnlock() return w.window.Has(w.nextNumber) } func (w *ReceivingWorker) NextNumber() uint32 { w.RLock() defer w.RUnlock() return w.nextNumber } func (w *ReceivingWorker) Flush(current uint32) { w.Lock() defer w.Unlock() w.acklist.Flush(current, w.conn.roundTrip.Timeout()) } func (w *ReceivingWorker) Write(seg Segment) error { ackSeg := seg.(*AckSegment) ackSeg.Conv = w.conn.meta.Conversation ackSeg.ReceivingNext = w.nextNumber ackSeg.ReceivingWindow = w.nextNumber + w.windowSize ackSeg.Option = 0 if w.conn.State() == StateReadyToClose { ackSeg.Option = SegmentOptionClose } return w.conn.output.Write(ackSeg) } func (*ReceivingWorker) CloseRead() { } func (w *ReceivingWorker) UpdateNecessary() bool { w.RLock() defer w.RUnlock() return len(w.acklist.numbers) > 0 } ================================================ FILE: transport/internet/kcp/segment.go ================================================ // +build !confonly package kcp import ( "encoding/binary" "v2ray.com/core/common/buf" ) // Command is a KCP command that indicate the purpose of a Segment. type Command byte const ( // CommandACK indicates an AckSegment. CommandACK Command = 0 // CommandData indicates a DataSegment. CommandData Command = 1 // CommandTerminate indicates that peer terminates the connection. CommandTerminate Command = 2 // CommandPing indicates a ping. CommandPing Command = 3 ) type SegmentOption byte const ( SegmentOptionClose SegmentOption = 1 ) type Segment interface { Release() Conversation() uint16 Command() Command ByteSize() int32 Serialize([]byte) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) } const ( DataSegmentOverhead = 18 ) type DataSegment struct { Conv uint16 Option SegmentOption Timestamp uint32 Number uint32 SendingNext uint32 payload *buf.Buffer timeout uint32 transmit uint32 } func NewDataSegment() *DataSegment { return new(DataSegment) } func (s *DataSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { s.Conv = conv s.Option = opt if len(buf) < 15 { return false, nil } s.Timestamp = binary.BigEndian.Uint32(buf) buf = buf[4:] s.Number = binary.BigEndian.Uint32(buf) buf = buf[4:] s.SendingNext = binary.BigEndian.Uint32(buf) buf = buf[4:] dataLen := int(binary.BigEndian.Uint16(buf)) buf = buf[2:] if len(buf) < dataLen { return false, nil } s.Data().Clear() s.Data().Write(buf[:dataLen]) buf = buf[dataLen:] return true, buf } func (s *DataSegment) Conversation() uint16 { return s.Conv } func (*DataSegment) Command() Command { return CommandData } func (s *DataSegment) Detach() *buf.Buffer { r := s.payload s.payload = nil return r } func (s *DataSegment) Data() *buf.Buffer { if s.payload == nil { s.payload = buf.New() } return s.payload } func (s *DataSegment) Serialize(b []byte) { binary.BigEndian.PutUint16(b, s.Conv) b[2] = byte(CommandData) b[3] = byte(s.Option) binary.BigEndian.PutUint32(b[4:], s.Timestamp) binary.BigEndian.PutUint32(b[8:], s.Number) binary.BigEndian.PutUint32(b[12:], s.SendingNext) binary.BigEndian.PutUint16(b[16:], uint16(s.payload.Len())) copy(b[18:], s.payload.Bytes()) } func (s *DataSegment) ByteSize() int32 { return 2 + 1 + 1 + 4 + 4 + 4 + 2 + s.payload.Len() } func (s *DataSegment) Release() { s.payload.Release() s.payload = nil } type AckSegment struct { Conv uint16 Option SegmentOption ReceivingWindow uint32 ReceivingNext uint32 Timestamp uint32 NumberList []uint32 } const ackNumberLimit = 128 func NewAckSegment() *AckSegment { return new(AckSegment) } func (s *AckSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { s.Conv = conv s.Option = opt if len(buf) < 13 { return false, nil } s.ReceivingWindow = binary.BigEndian.Uint32(buf) buf = buf[4:] s.ReceivingNext = binary.BigEndian.Uint32(buf) buf = buf[4:] s.Timestamp = binary.BigEndian.Uint32(buf) buf = buf[4:] count := int(buf[0]) buf = buf[1:] if len(buf) < count*4 { return false, nil } for i := 0; i < count; i++ { s.PutNumber(binary.BigEndian.Uint32(buf)) buf = buf[4:] } return true, buf } func (s *AckSegment) Conversation() uint16 { return s.Conv } func (*AckSegment) Command() Command { return CommandACK } func (s *AckSegment) PutTimestamp(timestamp uint32) { if timestamp-s.Timestamp < 0x7FFFFFFF { s.Timestamp = timestamp } } func (s *AckSegment) PutNumber(number uint32) { s.NumberList = append(s.NumberList, number) } func (s *AckSegment) IsFull() bool { return len(s.NumberList) == ackNumberLimit } func (s *AckSegment) IsEmpty() bool { return len(s.NumberList) == 0 } func (s *AckSegment) ByteSize() int32 { return 2 + 1 + 1 + 4 + 4 + 4 + 1 + int32(len(s.NumberList)*4) } func (s *AckSegment) Serialize(b []byte) { binary.BigEndian.PutUint16(b, s.Conv) b[2] = byte(CommandACK) b[3] = byte(s.Option) binary.BigEndian.PutUint32(b[4:], s.ReceivingWindow) binary.BigEndian.PutUint32(b[8:], s.ReceivingNext) binary.BigEndian.PutUint32(b[12:], s.Timestamp) b[16] = byte(len(s.NumberList)) n := 17 for _, number := range s.NumberList { binary.BigEndian.PutUint32(b[n:], number) n += 4 } } func (s *AckSegment) Release() {} type CmdOnlySegment struct { Conv uint16 Cmd Command Option SegmentOption SendingNext uint32 ReceivingNext uint32 PeerRTO uint32 } func NewCmdOnlySegment() *CmdOnlySegment { return new(CmdOnlySegment) } func (s *CmdOnlySegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { s.Conv = conv s.Cmd = cmd s.Option = opt if len(buf) < 12 { return false, nil } s.SendingNext = binary.BigEndian.Uint32(buf) buf = buf[4:] s.ReceivingNext = binary.BigEndian.Uint32(buf) buf = buf[4:] s.PeerRTO = binary.BigEndian.Uint32(buf) buf = buf[4:] return true, buf } func (s *CmdOnlySegment) Conversation() uint16 { return s.Conv } func (s *CmdOnlySegment) Command() Command { return s.Cmd } func (*CmdOnlySegment) ByteSize() int32 { return 2 + 1 + 1 + 4 + 4 + 4 } func (s *CmdOnlySegment) Serialize(b []byte) { binary.BigEndian.PutUint16(b, s.Conv) b[2] = byte(s.Cmd) b[3] = byte(s.Option) binary.BigEndian.PutUint32(b[4:], s.SendingNext) binary.BigEndian.PutUint32(b[8:], s.ReceivingNext) binary.BigEndian.PutUint32(b[12:], s.PeerRTO) } func (*CmdOnlySegment) Release() {} func ReadSegment(buf []byte) (Segment, []byte) { if len(buf) < 4 { return nil, nil } conv := binary.BigEndian.Uint16(buf) buf = buf[2:] cmd := Command(buf[0]) opt := SegmentOption(buf[1]) buf = buf[2:] var seg Segment switch cmd { case CommandData: seg = NewDataSegment() case CommandACK: seg = NewAckSegment() default: seg = NewCmdOnlySegment() } valid, extra := seg.parse(conv, cmd, opt, buf) if !valid { return nil, nil } return seg, extra } ================================================ FILE: transport/internet/kcp/segment_test.go ================================================ package kcp_test import ( "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" . "v2ray.com/core/transport/internet/kcp" ) func TestBadSegment(t *testing.T) { seg, buf := ReadSegment(nil) if seg != nil { t.Error("non-nil seg") } if len(buf) != 0 { t.Error("buf len: ", len(buf)) } } func TestDataSegment(t *testing.T) { seg := &DataSegment{ Conv: 1, Timestamp: 3, Number: 4, SendingNext: 5, } seg.Data().Write([]byte{'a', 'b', 'c', 'd'}) nBytes := seg.ByteSize() bytes := make([]byte, nBytes) seg.Serialize(bytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*DataSegment) if r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != "" { t.Error(r) } if r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != "" { t.Error(r) } } func Test1ByteDataSegment(t *testing.T) { seg := &DataSegment{ Conv: 1, Timestamp: 3, Number: 4, SendingNext: 5, } seg.Data().WriteByte('a') nBytes := seg.ByteSize() bytes := make([]byte, nBytes) seg.Serialize(bytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*DataSegment) if r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != "" { t.Error(r) } if r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != "" { t.Error(r) } } func TestACKSegment(t *testing.T) { seg := &AckSegment{ Conv: 1, ReceivingWindow: 2, ReceivingNext: 3, Timestamp: 10, NumberList: []uint32{1, 3, 5, 7, 9}, } nBytes := seg.ByteSize() bytes := make([]byte, nBytes) seg.Serialize(bytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*AckSegment) if r := cmp.Diff(seg2, seg); r != "" { t.Error(r) } } func TestCmdSegment(t *testing.T) { seg := &CmdOnlySegment{ Conv: 1, Cmd: CommandPing, Option: SegmentOptionClose, SendingNext: 11, ReceivingNext: 13, PeerRTO: 15, } nBytes := seg.ByteSize() bytes := make([]byte, nBytes) seg.Serialize(bytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*CmdOnlySegment) if r := cmp.Diff(seg2, seg); r != "" { t.Error(r) } } ================================================ FILE: transport/internet/kcp/sending.go ================================================ // +build !confonly package kcp import ( "container/list" "sync" "v2ray.com/core/common/buf" ) type SendingWindow struct { cache *list.List totalInFlightSize uint32 writer SegmentWriter onPacketLoss func(uint32) } func NewSendingWindow(writer SegmentWriter, onPacketLoss func(uint32)) *SendingWindow { window := &SendingWindow{ cache: list.New(), writer: writer, onPacketLoss: onPacketLoss, } return window } func (sw *SendingWindow) Release() { if sw == nil { return } for sw.cache.Len() > 0 { seg := sw.cache.Front().Value.(*DataSegment) seg.Release() sw.cache.Remove(sw.cache.Front()) } } func (sw *SendingWindow) Len() uint32 { return uint32(sw.cache.Len()) } func (sw *SendingWindow) IsEmpty() bool { return sw.cache.Len() == 0 } func (sw *SendingWindow) Push(number uint32, b *buf.Buffer) { seg := NewDataSegment() seg.Number = number seg.payload = b sw.cache.PushBack(seg) } func (sw *SendingWindow) FirstNumber() uint32 { return sw.cache.Front().Value.(*DataSegment).Number } func (sw *SendingWindow) Clear(una uint32) { for !sw.IsEmpty() { seg := sw.cache.Front().Value.(*DataSegment) if seg.Number >= una { break } seg.Release() sw.cache.Remove(sw.cache.Front()) } } func (sw *SendingWindow) HandleFastAck(number uint32, rto uint32) { if sw.IsEmpty() { return } sw.Visit(func(seg *DataSegment) bool { if number == seg.Number || number-seg.Number > 0x7FFFFFFF { return false } if seg.transmit > 0 && seg.timeout > rto/3 { seg.timeout -= rto / 3 } return true }) } func (sw *SendingWindow) Visit(visitor func(seg *DataSegment) bool) { if sw.IsEmpty() { return } for e := sw.cache.Front(); e != nil; e = e.Next() { seg := e.Value.(*DataSegment) if !visitor(seg) { break } } } func (sw *SendingWindow) Flush(current uint32, rto uint32, maxInFlightSize uint32) { if sw.IsEmpty() { return } var lost uint32 var inFlightSize uint32 sw.Visit(func(segment *DataSegment) bool { if current-segment.timeout >= 0x7FFFFFFF { return true } if segment.transmit == 0 { // First time sw.totalInFlightSize++ } else { lost++ } segment.timeout = current + rto segment.Timestamp = current segment.transmit++ sw.writer.Write(segment) inFlightSize++ return inFlightSize < maxInFlightSize }) if sw.onPacketLoss != nil && inFlightSize > 0 && sw.totalInFlightSize != 0 { rate := lost * 100 / sw.totalInFlightSize sw.onPacketLoss(rate) } } func (sw *SendingWindow) Remove(number uint32) bool { if sw.IsEmpty() { return false } for e := sw.cache.Front(); e != nil; e = e.Next() { seg := e.Value.(*DataSegment) if seg.Number > number { return false } else if seg.Number == number { if sw.totalInFlightSize > 0 { sw.totalInFlightSize-- } seg.Release() sw.cache.Remove(e) return true } } return false } type SendingWorker struct { sync.RWMutex conn *Connection window *SendingWindow firstUnacknowledged uint32 nextNumber uint32 remoteNextNumber uint32 controlWindow uint32 fastResend uint32 windowSize uint32 firstUnacknowledgedUpdated bool closed bool } func NewSendingWorker(kcp *Connection) *SendingWorker { worker := &SendingWorker{ conn: kcp, fastResend: 2, remoteNextNumber: 32, controlWindow: kcp.Config.GetSendingInFlightSize(), windowSize: kcp.Config.GetSendingBufferSize(), } worker.window = NewSendingWindow(worker, worker.OnPacketLoss) return worker } func (w *SendingWorker) Release() { w.Lock() w.window.Release() w.closed = true w.Unlock() } func (w *SendingWorker) ProcessReceivingNext(nextNumber uint32) { w.Lock() defer w.Unlock() w.ProcessReceivingNextWithoutLock(nextNumber) } func (w *SendingWorker) ProcessReceivingNextWithoutLock(nextNumber uint32) { w.window.Clear(nextNumber) w.FindFirstUnacknowledged() } func (w *SendingWorker) FindFirstUnacknowledged() { first := w.firstUnacknowledged if !w.window.IsEmpty() { w.firstUnacknowledged = w.window.FirstNumber() } else { w.firstUnacknowledged = w.nextNumber } if first != w.firstUnacknowledged { w.firstUnacknowledgedUpdated = true } } func (w *SendingWorker) processAck(number uint32) bool { // number < v.firstUnacknowledged || number >= v.nextNumber if number-w.firstUnacknowledged > 0x7FFFFFFF || number-w.nextNumber < 0x7FFFFFFF { return false } removed := w.window.Remove(number) if removed { w.FindFirstUnacknowledged() } return removed } func (w *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint32) { defer seg.Release() w.Lock() defer w.Unlock() if w.closed { return } if w.remoteNextNumber < seg.ReceivingWindow { w.remoteNextNumber = seg.ReceivingWindow } w.ProcessReceivingNextWithoutLock(seg.ReceivingNext) if seg.IsEmpty() { return } var maxack uint32 var maxackRemoved bool for _, number := range seg.NumberList { removed := w.processAck(number) if maxack < number { maxack = number maxackRemoved = removed } } if maxackRemoved { w.window.HandleFastAck(maxack, rto) if current-seg.Timestamp < 10000 { w.conn.roundTrip.Update(current-seg.Timestamp, current) } } } func (w *SendingWorker) Push(b *buf.Buffer) bool { w.Lock() defer w.Unlock() if w.closed { return false } if w.window.Len() > w.windowSize { return false } w.window.Push(w.nextNumber, b) w.nextNumber++ return true } func (w *SendingWorker) Write(seg Segment) error { dataSeg := seg.(*DataSegment) dataSeg.Conv = w.conn.meta.Conversation dataSeg.SendingNext = w.firstUnacknowledged dataSeg.Option = 0 if w.conn.State() == StateReadyToClose { dataSeg.Option = SegmentOptionClose } return w.conn.output.Write(dataSeg) } func (w *SendingWorker) OnPacketLoss(lossRate uint32) { if !w.conn.Config.Congestion || w.conn.roundTrip.Timeout() == 0 { return } if lossRate >= 15 { w.controlWindow = 3 * w.controlWindow / 4 } else if lossRate <= 5 { w.controlWindow += w.controlWindow / 4 } if w.controlWindow < 16 { w.controlWindow = 16 } if w.controlWindow > 2*w.conn.Config.GetSendingInFlightSize() { w.controlWindow = 2 * w.conn.Config.GetSendingInFlightSize() } } func (w *SendingWorker) Flush(current uint32) { w.Lock() if w.closed { w.Unlock() return } cwnd := w.firstUnacknowledged + w.conn.Config.GetSendingInFlightSize() if cwnd > w.remoteNextNumber { cwnd = w.remoteNextNumber } if w.conn.Config.Congestion && cwnd > w.firstUnacknowledged+w.controlWindow { cwnd = w.firstUnacknowledged + w.controlWindow } if !w.window.IsEmpty() { w.window.Flush(current, w.conn.roundTrip.Timeout(), cwnd) w.firstUnacknowledgedUpdated = false } updated := w.firstUnacknowledgedUpdated w.firstUnacknowledgedUpdated = false w.Unlock() if updated { w.conn.Ping(current, CommandPing) } } func (w *SendingWorker) CloseWrite() { w.Lock() defer w.Unlock() w.window.Clear(0xFFFFFFFF) } func (w *SendingWorker) IsEmpty() bool { w.RLock() defer w.RUnlock() return w.window.IsEmpty() } func (w *SendingWorker) UpdateNecessary() bool { return !w.IsEmpty() } func (w *SendingWorker) FirstUnacknowledged() uint32 { w.RLock() defer w.RUnlock() return w.firstUnacknowledged } ================================================ FILE: transport/internet/kcp/xor.go ================================================ // +build !amd64 package kcp // xorfwd performs XOR forwards in words, x[i] ^= x[i-4], i from 0 to len func xorfwd(x []byte) { for i := 4; i < len(x); i++ { x[i] ^= x[i-4] } } // xorbkd performs XOR backwords in words, x[i] ^= x[i-4], i from len to 0 func xorbkd(x []byte) { for i := len(x) - 1; i >= 4; i-- { x[i] ^= x[i-4] } } ================================================ FILE: transport/internet/kcp/xor_amd64.go ================================================ package kcp //go:noescape func xorfwd(x []byte) //go:noescape func xorbkd(x []byte) ================================================ FILE: transport/internet/kcp/xor_amd64.s ================================================ #include "textflag.h" // func xorfwd(x []byte) TEXT ·xorfwd(SB),NOSPLIT,$0 MOVQ x+0(FP), SI // x[i] MOVQ x_len+8(FP), CX // x.len MOVQ x+0(FP), DI ADDQ $4, DI // x[i+4] SUBQ $4, CX xorfwdloop: MOVL (SI), AX XORL AX, (DI) ADDQ $4, SI ADDQ $4, DI SUBQ $4, CX CMPL CX, $0 JE xorfwddone JMP xorfwdloop xorfwddone: RET // func xorbkd(x []byte) TEXT ·xorbkd(SB),NOSPLIT,$0 MOVQ x+0(FP), SI MOVQ x_len+8(FP), CX // x.len MOVQ x+0(FP), DI ADDQ CX, SI // x[-8] SUBQ $8, SI ADDQ CX, DI // x[-4] SUBQ $4, DI SUBQ $4, CX xorbkdloop: MOVL (SI), AX XORL AX, (DI) SUBQ $4, SI SUBQ $4, DI SUBQ $4, CX CMPL CX, $0 JE xorbkddone JMP xorbkdloop xorbkddone: RET ================================================ FILE: transport/internet/memory_settings.go ================================================ package internet // MemoryStreamConfig is a parsed form of StreamConfig. This is used to reduce number of Protobuf parsing. type MemoryStreamConfig struct { ProtocolName string ProtocolSettings interface{} SecurityType string SecuritySettings interface{} SocketSettings *SocketConfig } // ToMemoryStreamConfig converts a StreamConfig to MemoryStreamConfig. It returns a default non-nil MemoryStreamConfig for nil input. func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) { ets, err := s.GetEffectiveTransportSettings() if err != nil { return nil, err } mss := &MemoryStreamConfig{ ProtocolName: s.GetEffectiveProtocol(), ProtocolSettings: ets, } if s != nil { mss.SocketSettings = s.SocketSettings } if s != nil && s.HasSecuritySettings() { ess, err := s.GetEffectiveSecuritySettings() if err != nil { return nil, err } mss.SecurityType = s.SecurityType mss.SecuritySettings = ess } return mss, nil } ================================================ FILE: transport/internet/quic/config.go ================================================ // +build !confonly package quic import ( "crypto/aes" "crypto/cipher" "crypto/sha256" "golang.org/x/crypto/chacha20poly1305" "v2ray.com/core/common" "v2ray.com/core/common/protocol" "v2ray.com/core/transport/internet" ) func getAuth(config *Config) (cipher.AEAD, error) { security := config.Security.GetSecurityType() if security == protocol.SecurityType_NONE { return nil, nil } salted := []byte(config.Key + "v2ray-quic-salt") key := sha256.Sum256(salted) if security == protocol.SecurityType_AES128_GCM { block, err := aes.NewCipher(key[:16]) common.Must(err) return cipher.NewGCM(block) } if security == protocol.SecurityType_CHACHA20_POLY1305 { return chacha20poly1305.New(key[:]) } return nil, newError("unsupported security type") } func getHeader(config *Config) (internet.PacketHeader, error) { if config.Header == nil { return nil, nil } msg, err := config.Header.GetInstance() if err != nil { return nil, err } return internet.CreatePacketHeader(msg) } ================================================ FILE: transport/internet/quic/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/quic/config.proto package quic import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" protocol "v2ray.com/core/common/protocol" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Security *protocol.SecurityConfig `protobuf:"bytes,2,opt,name=security,proto3" json:"security,omitempty"` Header *serial.TypedMessage `protobuf:"bytes,3,opt,name=header,proto3" json:"header,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_quic_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_quic_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_quic_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetKey() string { if x != nil { return x.Key } return "" } func (x *Config) GetSecurity() *protocol.SecurityConfig { if x != nil { return x.Security } return nil } func (x *Config) GetHeader() *serial.TypedMessage { if x != nil { return x.Header } return nil } var File_transport_internet_quic_config_proto protoreflect.FileDescriptor var file_transport_internet_quic_config_proto_rawDesc = []byte{ 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x71, 0x75, 0x69, 0x63, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x77, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x50, 0x01, 0x5a, 0x26, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x71, 0x75, 0x69, 0x63, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_quic_config_proto_rawDescOnce sync.Once file_transport_internet_quic_config_proto_rawDescData = file_transport_internet_quic_config_proto_rawDesc ) func file_transport_internet_quic_config_proto_rawDescGZIP() []byte { file_transport_internet_quic_config_proto_rawDescOnce.Do(func() { file_transport_internet_quic_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_quic_config_proto_rawDescData) }) return file_transport_internet_quic_config_proto_rawDescData } var file_transport_internet_quic_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_quic_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.quic.Config (*protocol.SecurityConfig)(nil), // 1: v2ray.core.common.protocol.SecurityConfig (*serial.TypedMessage)(nil), // 2: v2ray.core.common.serial.TypedMessage } var file_transport_internet_quic_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.transport.internet.quic.Config.security:type_name -> v2ray.core.common.protocol.SecurityConfig 2, // 1: v2ray.core.transport.internet.quic.Config.header:type_name -> v2ray.core.common.serial.TypedMessage 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_transport_internet_quic_config_proto_init() } func file_transport_internet_quic_config_proto_init() { if File_transport_internet_quic_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_quic_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_quic_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_quic_config_proto_goTypes, DependencyIndexes: file_transport_internet_quic_config_proto_depIdxs, MessageInfos: file_transport_internet_quic_config_proto_msgTypes, }.Build() File_transport_internet_quic_config_proto = out.File file_transport_internet_quic_config_proto_rawDesc = nil file_transport_internet_quic_config_proto_goTypes = nil file_transport_internet_quic_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/quic/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.quic; option csharp_namespace = "V2Ray.Core.Transport.Internet.Quic"; option go_package = "v2ray.com/core/transport/internet/quic"; option java_package = "com.v2ray.core.transport.internet.quic"; option java_multiple_files = true; import "common/serial/typed_message.proto"; import "common/protocol/headers.proto"; message Config { string key = 1; v2ray.core.common.protocol.SecurityConfig security = 2; v2ray.core.common.serial.TypedMessage header = 3; } ================================================ FILE: transport/internet/quic/conn.go ================================================ // +build !confonly package quic import ( "crypto/cipher" "crypto/rand" "errors" "time" "github.com/lucas-clemente/quic-go" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) type sysConn struct { conn net.PacketConn header internet.PacketHeader auth cipher.AEAD } func wrapSysConn(rawConn net.PacketConn, config *Config) (*sysConn, error) { header, err := getHeader(config) if err != nil { return nil, err } auth, err := getAuth(config) if err != nil { return nil, err } return &sysConn{ conn: rawConn, header: header, auth: auth, }, nil } var errInvalidPacket = errors.New("invalid packet") func (c *sysConn) readFromInternal(p []byte) (int, net.Addr, error) { buffer := getBuffer() defer putBuffer(buffer) nBytes, addr, err := c.conn.ReadFrom(buffer) if err != nil { return 0, nil, err } payload := buffer[:nBytes] if c.header != nil { if len(payload) <= int(c.header.Size()) { return 0, nil, errInvalidPacket } payload = payload[c.header.Size():] } if c.auth == nil { n := copy(p, payload) return n, addr, nil } if len(payload) <= c.auth.NonceSize() { return 0, nil, errInvalidPacket } nonce := payload[:c.auth.NonceSize()] payload = payload[c.auth.NonceSize():] p, err = c.auth.Open(p[:0], nonce, payload, nil) if err != nil { return 0, nil, errInvalidPacket } return len(p), addr, nil } func (c *sysConn) ReadFrom(p []byte) (int, net.Addr, error) { if c.header == nil && c.auth == nil { return c.conn.ReadFrom(p) } for { n, addr, err := c.readFromInternal(p) if err != nil && err != errInvalidPacket { return 0, nil, err } if err == nil { return n, addr, nil } } } func (c *sysConn) WriteTo(p []byte, addr net.Addr) (int, error) { if c.header == nil && c.auth == nil { return c.conn.WriteTo(p, addr) } buffer := getBuffer() defer putBuffer(buffer) payload := buffer n := 0 if c.header != nil { c.header.Serialize(payload) n = int(c.header.Size()) } if c.auth == nil { nBytes := copy(payload[n:], p) n += nBytes } else { nounce := payload[n : n+c.auth.NonceSize()] common.Must2(rand.Read(nounce)) n += c.auth.NonceSize() pp := c.auth.Seal(payload[:n], nounce, p, nil) n = len(pp) } return c.conn.WriteTo(payload[:n], addr) } func (c *sysConn) Close() error { return c.conn.Close() } func (c *sysConn) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *sysConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) } func (c *sysConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } func (c *sysConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } type interConn struct { stream quic.Stream local net.Addr remote net.Addr } func (c *interConn) Read(b []byte) (int, error) { return c.stream.Read(b) } func (c *interConn) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } func (c *interConn) Write(b []byte) (int, error) { return c.stream.Write(b) } func (c *interConn) Close() error { return c.stream.Close() } func (c *interConn) LocalAddr() net.Addr { return c.local } func (c *interConn) RemoteAddr() net.Addr { return c.remote } func (c *interConn) SetDeadline(t time.Time) error { return c.stream.SetDeadline(t) } func (c *interConn) SetReadDeadline(t time.Time) error { return c.stream.SetReadDeadline(t) } func (c *interConn) SetWriteDeadline(t time.Time) error { return c.stream.SetWriteDeadline(t) } ================================================ FILE: transport/internet/quic/dialer.go ================================================ // +build !confonly package quic import ( "context" "sync" "time" "github.com/lucas-clemente/quic-go" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/task" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" ) type sessionContext struct { rawConn *sysConn session quic.Session } var errSessionClosed = newError("session closed") func (c *sessionContext) openStream(destAddr net.Addr) (*interConn, error) { if !isActive(c.session) { return nil, errSessionClosed } stream, err := c.session.OpenStream() if err != nil { return nil, err } conn := &interConn{ stream: stream, local: c.session.LocalAddr(), remote: destAddr, } return conn, nil } type clientSessions struct { access sync.Mutex sessions map[net.Destination][]*sessionContext cleanup *task.Periodic } func isActive(s quic.Session) bool { select { case <-s.Context().Done(): return false default: return true } } func removeInactiveSessions(sessions []*sessionContext) []*sessionContext { activeSessions := make([]*sessionContext, 0, len(sessions)) for _, s := range sessions { if isActive(s.session) { activeSessions = append(activeSessions, s) continue } if err := s.session.CloseWithError(0, ""); err != nil { newError("failed to close session").Base(err).WriteToLog() } if err := s.rawConn.Close(); err != nil { newError("failed to close raw connection").Base(err).WriteToLog() } } if len(activeSessions) < len(sessions) { return activeSessions } return sessions } func openStream(sessions []*sessionContext, destAddr net.Addr) *interConn { for _, s := range sessions { if !isActive(s.session) { continue } conn, err := s.openStream(destAddr) if err != nil { continue } return conn } return nil } func (s *clientSessions) cleanSessions() error { s.access.Lock() defer s.access.Unlock() if len(s.sessions) == 0 { return nil } newSessionMap := make(map[net.Destination][]*sessionContext) for dest, sessions := range s.sessions { sessions = removeInactiveSessions(sessions) if len(sessions) > 0 { newSessionMap[dest] = sessions } } s.sessions = newSessionMap return nil } func (s *clientSessions) openConnection(destAddr net.Addr, config *Config, tlsConfig *tls.Config, sockopt *internet.SocketConfig) (internet.Connection, error) { s.access.Lock() defer s.access.Unlock() if s.sessions == nil { s.sessions = make(map[net.Destination][]*sessionContext) } dest := net.DestinationFromAddr(destAddr) var sessions []*sessionContext if s, found := s.sessions[dest]; found { sessions = s } if true { conn := openStream(sessions, destAddr) if conn != nil { return conn, nil } } sessions = removeInactiveSessions(sessions) rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{ IP: []byte{0, 0, 0, 0}, Port: 0, }, sockopt) if err != nil { return nil, err } quicConfig := &quic.Config{ ConnectionIDLength: 12, HandshakeTimeout: time.Second * 8, MaxIdleTimeout: time.Second * 30, } conn, err := wrapSysConn(rawConn, config) if err != nil { rawConn.Close() return nil, err } session, err := quic.DialContext(context.Background(), conn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig) if err != nil { conn.Close() return nil, err } context := &sessionContext{ session: session, rawConn: conn, } s.sessions[dest] = append(sessions, context) return context.openStream(destAddr) } var client clientSessions func init() { client.sessions = make(map[net.Destination][]*sessionContext) client.cleanup = &task.Periodic{ Interval: time.Minute, Execute: client.cleanSessions, } common.Must(client.cleanup.Start()) } func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { tlsConfig := tls.ConfigFromStreamSettings(streamSettings) if tlsConfig == nil { tlsConfig = &tls.Config{ ServerName: internalDomain, AllowInsecure: true, } } var destAddr *net.UDPAddr if dest.Address.Family().IsIP() { destAddr = &net.UDPAddr{ IP: dest.Address.IP(), Port: int(dest.Port), } } else { addr, err := net.ResolveUDPAddr("udp", dest.NetAddr()) if err != nil { return nil, err } destAddr = addr } config := streamSettings.ProtocolSettings.(*Config) return client.openConnection(destAddr, config, tlsConfig, streamSettings.SocketSettings) } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } ================================================ FILE: transport/internet/quic/errors.generated.go ================================================ package quic import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/quic/hub.go ================================================ // +build !confonly package quic import ( "context" "time" "github.com/lucas-clemente/quic-go" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/common/signal/done" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" ) // Listener is an internet.Listener that listens for TCP connections. type Listener struct { rawConn *sysConn listener quic.Listener done *done.Instance addConn internet.ConnHandler } func (l *Listener) acceptStreams(session quic.Session) { for { stream, err := session.AcceptStream(context.Background()) if err != nil { newError("failed to accept stream").Base(err).WriteToLog() select { case <-session.Context().Done(): return case <-l.done.Wait(): if err := session.CloseWithError(0, ""); err != nil { newError("failed to close session").Base(err).WriteToLog() } return default: time.Sleep(time.Second) continue } } conn := &interConn{ stream: stream, local: session.LocalAddr(), remote: session.RemoteAddr(), } l.addConn(conn) } } func (l *Listener) keepAccepting() { for { conn, err := l.listener.Accept(context.Background()) if err != nil { newError("failed to accept QUIC sessions").Base(err).WriteToLog() if l.done.Done() { break } time.Sleep(time.Second) continue } go l.acceptStreams(conn) } } // Addr implements internet.Listener.Addr. func (l *Listener) Addr() net.Addr { return l.listener.Addr() } // Close implements internet.Listener.Close. func (l *Listener) Close() error { l.done.Close() l.listener.Close() l.rawConn.Close() return nil } // Listen creates a new Listener based on configurations. func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { if address.Family().IsDomain() { return nil, newError("domain address is not allows for listening quic") } tlsConfig := tls.ConfigFromStreamSettings(streamSettings) if tlsConfig == nil { tlsConfig = &tls.Config{ Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.DNSNames(internalDomain), cert.CommonName(internalDomain)))}, } } config := streamSettings.ProtocolSettings.(*Config) rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{ IP: address.IP(), Port: int(port), }, streamSettings.SocketSettings) if err != nil { return nil, err } quicConfig := &quic.Config{ ConnectionIDLength: 12, HandshakeTimeout: time.Second * 8, MaxIdleTimeout: time.Second * 45, MaxIncomingStreams: 32, MaxIncomingUniStreams: -1, } conn, err := wrapSysConn(rawConn, config) if err != nil { conn.Close() return nil, err } qListener, err := quic.Listen(conn, tlsConfig.GetTLSConfig(), quicConfig) if err != nil { conn.Close() return nil, err } listener := &Listener{ done: done.New(), rawConn: conn, listener: qListener, addConn: handler, } go listener.keepAccepting() return listener, nil } func init() { common.Must(internet.RegisterTransportListener(protocolName, Listen)) } ================================================ FILE: transport/internet/quic/pool.go ================================================ // +build !confonly package quic import ( "sync" "v2ray.com/core/common/bytespool" ) var pool *sync.Pool func init() { pool = bytespool.GetPool(2048) } func getBuffer() []byte { return pool.Get().([]byte) } func putBuffer(p []byte) { pool.Put(p) } ================================================ FILE: transport/internet/quic/quic.go ================================================ // +build !confonly package quic import ( "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) //go:generate go run v2ray.com/core/common/errors/errorgen // Here is some modification needs to be done before update quic vendor. // * use bytespool in buffer_pool.go // * set MaxReceivePacketSize to 1452 - 32 (16 bytes auth, 16 bytes head) // // const protocolName = "quic" const internalDomain = "quic.internal.v2ray.com" func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/quic/quic_test.go ================================================ package quic_test import ( "context" "crypto/rand" "testing" "time" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/common/serial" "v2ray.com/core/testing/servers/udp" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/headers/wireguard" "v2ray.com/core/transport/internet/quic" "v2ray.com/core/transport/internet/tls" ) func TestQuicConnection(t *testing.T) { port := udp.PickPort() listener, err := quic.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{}, SecurityType: "tls", SecuritySettings: &tls.Config{ Certificate: []*tls.Certificate{ tls.ParseCertificate( cert.MustGenerate(nil, cert.DNSNames("www.v2fly.org"), ), ), }, }, }, func(conn internet.Connection) { go func() { defer conn.Close() b := buf.New() defer b.Release() for { b.Clear() if _, err := b.ReadFrom(conn); err != nil { return } common.Must2(conn.Write(b.Bytes())) } }() }) common.Must(err) defer listener.Close() time.Sleep(time.Second) dctx := context.Background() conn, err := quic.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{}, SecurityType: "tls", SecuritySettings: &tls.Config{ ServerName: "www.v2fly.org", AllowInsecure: true, }, }) common.Must(err) defer conn.Close() const N = 1024 b1 := make([]byte, N) common.Must2(rand.Read(b1)) b2 := buf.New() common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } } func TestQuicConnectionWithoutTLS(t *testing.T) { port := udp.PickPort() listener, err := quic.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{}, }, func(conn internet.Connection) { go func() { defer conn.Close() b := buf.New() defer b.Release() for { b.Clear() if _, err := b.ReadFrom(conn); err != nil { return } common.Must2(conn.Write(b.Bytes())) } }() }) common.Must(err) defer listener.Close() time.Sleep(time.Second) dctx := context.Background() conn, err := quic.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{}, }) common.Must(err) defer conn.Close() const N = 1024 b1 := make([]byte, N) common.Must2(rand.Read(b1)) b2 := buf.New() common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } } func TestQuicConnectionAuthHeader(t *testing.T) { port := udp.PickPort() listener, err := quic.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{ Header: serial.ToTypedMessage(&wireguard.WireguardConfig{}), Key: "abcd", Security: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }, }, func(conn internet.Connection) { go func() { defer conn.Close() b := buf.New() defer b.Release() for { b.Clear() if _, err := b.ReadFrom(conn); err != nil { return } common.Must2(conn.Write(b.Bytes())) } }() }) common.Must(err) defer listener.Close() time.Sleep(time.Second) dctx := context.Background() conn, err := quic.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{ ProtocolName: "quic", ProtocolSettings: &quic.Config{ Header: serial.ToTypedMessage(&wireguard.WireguardConfig{}), Key: "abcd", Security: &protocol.SecurityConfig{ Type: protocol.SecurityType_AES128_GCM, }, }, }) common.Must(err) defer conn.Close() const N = 1024 b1 := make([]byte, N) common.Must2(rand.Read(b1)) b2 := buf.New() common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } common.Must2(conn.Write(b1)) b2.Clear() common.Must2(b2.ReadFullFrom(conn, N)) if r := cmp.Diff(b2.Bytes(), b1); r != "" { t.Error(r) } } ================================================ FILE: transport/internet/sockopt.go ================================================ package internet func isTCPSocket(network string) bool { switch network { case "tcp", "tcp4", "tcp6": return true default: return false } } func isUDPSocket(network string) bool { switch network { case "udp", "udp4", "udp6": return true default: return false } } ================================================ FILE: transport/internet/sockopt_darwin.go ================================================ package internet import ( "syscall" ) const ( // TCP_FASTOPEN is the socket option on darwin for TCP fast open. TCP_FASTOPEN = 0x105 // TCP_FASTOPEN_SERVER is the value to enable TCP fast open on darwin for server connections. TCP_FASTOPEN_SERVER = 0x01 // TCP_FASTOPEN_CLIENT is the value to enable TCP fast open on darwin for client connections. TCP_FASTOPEN_CLIENT = 0x02 ) func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_CLIENT); err != nil { return err } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, 0); err != nil { return err } } } return nil } func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, TCP_FASTOPEN_SERVER); err != nil { return err } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_FASTOPEN, 0); err != nil { return err } } } return nil } func bindAddr(fd uintptr, address []byte, port uint32) error { return nil } func setReuseAddr(fd uintptr) error { return nil } func setReusePort(fd uintptr) error { return nil } ================================================ FILE: transport/internet/sockopt_freebsd.go ================================================ package internet import ( "encoding/binary" "net" "os" "syscall" "unsafe" "golang.org/x/sys/unix" ) const ( sysPFINOUT = 0x0 sysPFIN = 0x1 sysPFOUT = 0x2 sysPFFWD = 0x3 sysDIOCNATLOOK = 0xc04c4417 ) type pfiocNatlook struct { Saddr [16]byte /* pf_addr */ Daddr [16]byte /* pf_addr */ Rsaddr [16]byte /* pf_addr */ Rdaddr [16]byte /* pf_addr */ Sport uint16 Dport uint16 Rsport uint16 Rdport uint16 Af uint8 Proto uint8 Direction uint8 Pad [1]byte } const ( sizeofPfiocNatlook = 0x4c soReUsePort = 0x00000200 soReUsePortLB = 0x00010000 ) func ioctl(s uintptr, ioc int, b []byte) error { if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, s, uintptr(ioc), uintptr(unsafe.Pointer(&b[0]))); errno != 0 { return error(errno) } return nil } func (nl *pfiocNatlook) rdPort() int { return int(binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&nl.Rdport))[:])) } func (nl *pfiocNatlook) setPort(remote, local int) { binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Sport))[:], uint16(remote)) binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Dport))[:], uint16(local)) } // OriginalDst uses ioctl to read original destination from /dev/pf func OriginalDst(la, ra net.Addr) (net.IP, int, error) { f, err := os.Open("/dev/pf") if err != nil { return net.IP{}, -1, newError("failed to open device /dev/pf").Base(err) } defer f.Close() fd := f.Fd() b := make([]byte, sizeofPfiocNatlook) nl := (*pfiocNatlook)(unsafe.Pointer(&b[0])) var raIP, laIP net.IP var raPort, laPort int switch la.(type) { case *net.TCPAddr: raIP = ra.(*net.TCPAddr).IP laIP = la.(*net.TCPAddr).IP raPort = ra.(*net.TCPAddr).Port laPort = la.(*net.TCPAddr).Port nl.Proto = syscall.IPPROTO_TCP case *net.UDPAddr: raIP = ra.(*net.UDPAddr).IP laIP = la.(*net.UDPAddr).IP raPort = ra.(*net.UDPAddr).Port laPort = la.(*net.UDPAddr).Port nl.Proto = syscall.IPPROTO_UDP } if raIP.To4() != nil { if laIP.IsUnspecified() { laIP = net.ParseIP("127.0.0.1") } copy(nl.Saddr[:net.IPv4len], raIP.To4()) copy(nl.Daddr[:net.IPv4len], laIP.To4()) nl.Af = syscall.AF_INET } if raIP.To16() != nil && raIP.To4() == nil { if laIP.IsUnspecified() { laIP = net.ParseIP("::1") } copy(nl.Saddr[:], raIP) copy(nl.Daddr[:], laIP) nl.Af = syscall.AF_INET6 } nl.setPort(raPort, laPort) ioc := uintptr(sysDIOCNATLOOK) for _, dir := range []byte{sysPFOUT, sysPFIN} { nl.Direction = dir err = ioctl(fd, int(ioc), b) if err == nil || err != syscall.ENOENT { break } } if err != nil { return net.IP{}, -1, os.NewSyscallError("ioctl", err) } odPort := nl.rdPort() var odIP net.IP switch nl.Af { case syscall.AF_INET: odIP = make(net.IP, net.IPv4len) copy(odIP, nl.Rdaddr[:net.IPv4len]) case syscall.AF_INET6: odIP = make(net.IP, net.IPv6len) copy(odIP, nl.Rdaddr[:]) } return odIP, odPort, nil } func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if config.Mark != 0 { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil { return newError("failed to set SO_USER_COOKIE").Base(err) } } if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 1); err != nil { return newError("failed to set TCP_FASTOPEN_CONNECT=1").Base(err) } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 0); err != nil { return newError("failed to set TCP_FASTOPEN_CONNECT=0").Base(err) } } } if config.Tproxy.IsEnabled() { ip, _, _ := net.SplitHostPort(address) if net.ParseIP(ip).To4() != nil { if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil { return newError("failed to set outbound IP_BINDANY").Base(err) } } else { if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil { return newError("failed to set outbound IPV6_BINDANY").Base(err) } } } return nil } func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if config.Mark != 0 { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil { return newError("failed to set SO_USER_COOKIE").Base(err) } } if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 1); err != nil { return newError("failed to set TCP_FASTOPEN=1").Base(err) } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 0); err != nil { return newError("failed to set TCP_FASTOPEN=0").Base(err) } } } if config.Tproxy.IsEnabled() { if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil { if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil { return newError("failed to set inbound IP_BINDANY").Base(err) } } } return nil } func bindAddr(fd uintptr, ip []byte, port uint32) error { setReuseAddr(fd) setReusePort(fd) var sockaddr syscall.Sockaddr switch len(ip) { case net.IPv4len: a4 := &syscall.SockaddrInet4{ Port: int(port), } copy(a4.Addr[:], ip) sockaddr = a4 case net.IPv6len: a6 := &syscall.SockaddrInet6{ Port: int(port), } copy(a6.Addr[:], ip) sockaddr = a6 default: return newError("unexpected length of ip") } return syscall.Bind(int(fd), sockaddr) } func setReuseAddr(fd uintptr) error { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { return newError("failed to set SO_REUSEADDR").Base(err).AtWarning() } return nil } func setReusePort(fd uintptr) error { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePortLB, 1); err != nil { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePort, 1); err != nil { return newError("failed to set SO_REUSEPORT").Base(err).AtWarning() } } return nil } ================================================ FILE: transport/internet/sockopt_linux.go ================================================ package internet import ( "net" "syscall" "golang.org/x/sys/unix" ) const ( // For incoming connections. TCP_FASTOPEN = 23 // For out-going connections. TCP_FASTOPEN_CONNECT = 30 ) func bindAddr(fd uintptr, ip []byte, port uint32) error { setReuseAddr(fd) setReusePort(fd) var sockaddr syscall.Sockaddr switch len(ip) { case net.IPv4len: a4 := &syscall.SockaddrInet4{ Port: int(port), } copy(a4.Addr[:], ip) sockaddr = a4 case net.IPv6len: a6 := &syscall.SockaddrInet6{ Port: int(port), } copy(a6.Addr[:], ip) sockaddr = a6 default: return newError("unexpected length of ip") } return syscall.Bind(int(fd), sockaddr) } func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if config.Mark != 0 { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil { return newError("failed to set SO_MARK").Base(err) } } if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN_CONNECT, 1); err != nil { return newError("failed to set TCP_FASTOPEN_CONNECT=1").Base(err) } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN_CONNECT, 0); err != nil { return newError("failed to set TCP_FASTOPEN_CONNECT=0").Base(err) } } } if config.Tproxy.IsEnabled() { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { return newError("failed to set IP_TRANSPARENT").Base(err) } } return nil } func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if config.Mark != 0 { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil { return newError("failed to set SO_MARK").Base(err) } } if isTCPSocket(network) { switch config.Tfo { case SocketConfig_Enable: if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN, 1); err != nil { return newError("failed to set TCP_FASTOPEN=1").Base(err) } case SocketConfig_Disable: if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN, 0); err != nil { return newError("failed to set TCP_FASTOPEN=0").Base(err) } } } if config.Tproxy.IsEnabled() { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { return newError("failed to set IP_TRANSPARENT").Base(err) } } if config.ReceiveOriginalDestAddress && isUDPSocket(network) { err1 := syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) err2 := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) if err1 != nil && err2 != nil { return err1 } } return nil } func setReuseAddr(fd uintptr) error { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { return newError("failed to set SO_REUSEADDR").Base(err).AtWarning() } return nil } func setReusePort(fd uintptr) error { if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { return newError("failed to set SO_REUSEPORT").Base(err).AtWarning() } return nil } ================================================ FILE: transport/internet/sockopt_linux_test.go ================================================ package internet_test import ( "context" "syscall" "testing" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/testing/servers/tcp" . "v2ray.com/core/transport/internet" ) func TestSockOptMark(t *testing.T) { t.Skip("requires CAP_NET_ADMIN") tcpServer := tcp.Server{ MsgProcessor: func(b []byte) []byte { return b }, } dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() const mark = 1 dialer := DefaultSystemDialer{} conn, err := dialer.Dial(context.Background(), nil, dest, &SocketConfig{Mark: mark}) common.Must(err) defer conn.Close() rawConn, err := conn.(*net.TCPConn).SyscallConn() common.Must(err) err = rawConn.Control(func(fd uintptr) { m, err := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK) common.Must(err) if mark != m { t.Fatal("unexpected connection mark", m, " want ", mark) } }) common.Must(err) } ================================================ FILE: transport/internet/sockopt_other.go ================================================ // +build js dragonfly netbsd openbsd solaris package internet func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { return nil } func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { return nil } func bindAddr(fd uintptr, ip []byte, port uint32) error { return nil } func setReuseAddr(fd uintptr) error { return nil } func setReusePort(fd uintptr) error { return nil } ================================================ FILE: transport/internet/sockopt_test.go ================================================ package internet_test import ( "context" "testing" "github.com/google/go-cmp/cmp" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/testing/servers/tcp" . "v2ray.com/core/transport/internet" ) func TestTCPFastOpen(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: func(b []byte) []byte { return b }, } dest, err := tcpServer.StartContext(context.Background(), &SocketConfig{Tfo: SocketConfig_Enable}) common.Must(err) defer tcpServer.Close() ctx := context.Background() dialer := DefaultSystemDialer{} conn, err := dialer.Dial(ctx, nil, dest, &SocketConfig{ Tfo: SocketConfig_Enable, }) common.Must(err) defer conn.Close() _, err = conn.Write([]byte("abcd")) common.Must(err) b := buf.New() common.Must2(b.ReadFrom(conn)) if r := cmp.Diff(b.Bytes(), []byte("abcd")); r != "" { t.Fatal(r) } } ================================================ FILE: transport/internet/sockopt_windows.go ================================================ package internet import ( "syscall" ) const ( TCP_FASTOPEN = 15 ) func setTFO(fd syscall.Handle, settings SocketConfig_TCPFastOpenState) error { switch settings { case SocketConfig_Enable: if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_FASTOPEN, 1); err != nil { return err } case SocketConfig_Disable: if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_FASTOPEN, 0); err != nil { return err } } return nil } func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { if err := setTFO(syscall.Handle(fd), config.Tfo); err != nil { return err } } return nil } func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { if isTCPSocket(network) { if err := setTFO(syscall.Handle(fd), config.Tfo); err != nil { return err } } return nil } func bindAddr(fd uintptr, ip []byte, port uint32) error { return nil } func setReuseAddr(fd uintptr) error { return nil } func setReusePort(fd uintptr) error { return nil } ================================================ FILE: transport/internet/system_dialer.go ================================================ package internet import ( "context" "syscall" "time" "v2ray.com/core/common/net" "v2ray.com/core/common/session" ) var ( effectiveSystemDialer SystemDialer = &DefaultSystemDialer{} ) type SystemDialer interface { Dial(ctx context.Context, source net.Address, destination net.Destination, sockopt *SocketConfig) (net.Conn, error) } type DefaultSystemDialer struct { controllers []controller } func resolveSrcAddr(network net.Network, src net.Address) net.Addr { if src == nil || src == net.AnyIP { return nil } if network == net.Network_TCP { return &net.TCPAddr{ IP: src.IP(), Port: 0, } } return &net.UDPAddr{ IP: src.IP(), Port: 0, } } func hasBindAddr(sockopt *SocketConfig) bool { return sockopt != nil && len(sockopt.BindAddress) > 0 && sockopt.BindPort > 0 } func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) { if dest.Network == net.Network_UDP && !hasBindAddr(sockopt) { srcAddr := resolveSrcAddr(net.Network_UDP, src) if srcAddr == nil { srcAddr = &net.UDPAddr{ IP: []byte{0, 0, 0, 0}, Port: 0, } } packetConn, err := ListenSystemPacket(ctx, srcAddr, sockopt) if err != nil { return nil, err } destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr()) if err != nil { return nil, err } return &packetConnWrapper{ conn: packetConn, dest: destAddr, }, nil } dialer := &net.Dialer{ Timeout: time.Second * 16, DualStack: true, LocalAddr: resolveSrcAddr(dest.Network, src), } if sockopt != nil || len(d.controllers) > 0 { dialer.Control = func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { if sockopt != nil { if err := applyOutboundSocketOptions(network, address, fd, sockopt); err != nil { newError("failed to apply socket options").Base(err).WriteToLog(session.ExportIDToError(ctx)) } if dest.Network == net.Network_UDP && hasBindAddr(sockopt) { if err := bindAddr(fd, sockopt.BindAddress, sockopt.BindPort); err != nil { newError("failed to bind source address to ", sockopt.BindAddress).Base(err).WriteToLog(session.ExportIDToError(ctx)) } } } for _, ctl := range d.controllers { if err := ctl(network, address, fd); err != nil { newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } }) } } return dialer.DialContext(ctx, dest.Network.SystemString(), dest.NetAddr()) } type packetConnWrapper struct { conn net.PacketConn dest net.Addr } func (c *packetConnWrapper) Close() error { return c.conn.Close() } func (c *packetConnWrapper) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *packetConnWrapper) RemoteAddr() net.Addr { return c.dest } func (c *packetConnWrapper) Write(p []byte) (int, error) { return c.conn.WriteTo(p, c.dest) } func (c *packetConnWrapper) Read(p []byte) (int, error) { n, _, err := c.conn.ReadFrom(p) return n, err } func (c *packetConnWrapper) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) } func (c *packetConnWrapper) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } func (c *packetConnWrapper) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } type SystemDialerAdapter interface { Dial(network string, address string) (net.Conn, error) } type SimpleSystemDialer struct { adapter SystemDialerAdapter } func WithAdapter(dialer SystemDialerAdapter) SystemDialer { return &SimpleSystemDialer{ adapter: dialer, } } func (v *SimpleSystemDialer) Dial(ctx context.Context, src net.Address, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) { return v.adapter.Dial(dest.Network.SystemString(), dest.NetAddr()) } // UseAlternativeSystemDialer replaces the current system dialer with a given one. // Caller must ensure there is no race condition. // // v2ray:api:stable func UseAlternativeSystemDialer(dialer SystemDialer) { if dialer == nil { effectiveSystemDialer = &DefaultSystemDialer{} } effectiveSystemDialer = dialer } // RegisterDialerController adds a controller to the effective system dialer. // The controller can be used to operate on file descriptors before they are put into use. // It only works when effective dialer is the default dialer. // // v2ray:api:beta func RegisterDialerController(ctl func(network, address string, fd uintptr) error) error { if ctl == nil { return newError("nil listener controller") } dialer, ok := effectiveSystemDialer.(*DefaultSystemDialer) if !ok { return newError("RegisterListenerController not supported in custom dialer") } dialer.controllers = append(dialer.controllers, ctl) return nil } ================================================ FILE: transport/internet/system_listener.go ================================================ package internet import ( "context" "syscall" "v2ray.com/core/common/net" "v2ray.com/core/common/session" ) var ( effectiveListener = DefaultListener{} ) type controller func(network, address string, fd uintptr) error type DefaultListener struct { controllers []controller } func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { if sockopt != nil { if err := applyInboundSocketOptions(network, fd, sockopt); err != nil { newError("failed to apply socket options to incoming connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } setReusePort(fd) for _, controller := range controllers { if err := controller(network, address, fd); err != nil { newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } }) } } func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) { var lc net.ListenConfig lc.Control = getControlFunc(ctx, sockopt, dl.controllers) return lc.Listen(ctx, addr.Network(), addr.String()) } func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) { var lc net.ListenConfig lc.Control = getControlFunc(ctx, sockopt, dl.controllers) return lc.ListenPacket(ctx, addr.Network(), addr.String()) } // RegisterListenerController adds a controller to the effective system listener. // The controller can be used to operate on file descriptors before they are put into use. // // v2ray:api:beta func RegisterListenerController(controller func(network, address string, fd uintptr) error) error { if controller == nil { return newError("nil listener controller") } effectiveListener.controllers = append(effectiveListener.controllers, controller) return nil } ================================================ FILE: transport/internet/system_listener_test.go ================================================ package internet_test import ( "context" "net" "testing" "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) func TestRegisterListenerController(t *testing.T) { var gotFd uintptr common.Must(internet.RegisterListenerController(func(network string, addr string, fd uintptr) error { gotFd = fd return nil })) conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{ IP: net.IPv4zero, }, nil) common.Must(err) common.Must(conn.Close()) if gotFd == 0 { t.Error("expected none-zero fd, but actually 0") } } ================================================ FILE: transport/internet/tcp/config.go ================================================ // +build !confonly package tcp import ( "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) const protocolName = "tcp" func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/tcp/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/tcp/config.proto package tcp import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" serial "v2ray.com/core/common/serial" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields HeaderSettings *serial.TypedMessage `protobuf:"bytes,2,opt,name=header_settings,json=headerSettings,proto3" json:"header_settings,omitempty"` AcceptProxyProtocol bool `protobuf:"varint,3,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_tcp_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_tcp_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_tcp_config_proto_rawDescGZIP(), []int{0} } func (x *Config) GetHeaderSettings() *serial.TypedMessage { if x != nil { return x.HeaderSettings } return nil } func (x *Config) GetAcceptProxyProtocol() bool { if x != nil { return x.AcceptProxyProtocol } return false } var File_transport_internet_tcp_config_proto protoreflect.FileDescriptor var file_transport_internet_tcp_config_proto_rawDesc = []byte{ 0x0a, 0x23, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x63, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x63, 0x70, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x42, 0x74, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x63, 0x70, 0x50, 0x01, 0x5a, 0x25, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x63, 0x70, 0xaa, 0x02, 0x21, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x63, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_tcp_config_proto_rawDescOnce sync.Once file_transport_internet_tcp_config_proto_rawDescData = file_transport_internet_tcp_config_proto_rawDesc ) func file_transport_internet_tcp_config_proto_rawDescGZIP() []byte { file_transport_internet_tcp_config_proto_rawDescOnce.Do(func() { file_transport_internet_tcp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_tcp_config_proto_rawDescData) }) return file_transport_internet_tcp_config_proto_rawDescData } var file_transport_internet_tcp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_tcp_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.tcp.Config (*serial.TypedMessage)(nil), // 1: v2ray.core.common.serial.TypedMessage } var file_transport_internet_tcp_config_proto_depIdxs = []int32{ 1, // 0: v2ray.core.transport.internet.tcp.Config.header_settings:type_name -> v2ray.core.common.serial.TypedMessage 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_transport_internet_tcp_config_proto_init() } func file_transport_internet_tcp_config_proto_init() { if File_transport_internet_tcp_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_tcp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_tcp_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_tcp_config_proto_goTypes, DependencyIndexes: file_transport_internet_tcp_config_proto_depIdxs, MessageInfos: file_transport_internet_tcp_config_proto_msgTypes, }.Build() File_transport_internet_tcp_config_proto = out.File file_transport_internet_tcp_config_proto_rawDesc = nil file_transport_internet_tcp_config_proto_goTypes = nil file_transport_internet_tcp_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/tcp/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.tcp; option csharp_namespace = "V2Ray.Core.Transport.Internet.Tcp"; option go_package = "v2ray.com/core/transport/internet/tcp"; option java_package = "com.v2ray.core.transport.internet.tcp"; option java_multiple_files = true; import "common/serial/typed_message.proto"; message Config { reserved 1; v2ray.core.common.serial.TypedMessage header_settings = 2; bool accept_proxy_protocol = 3; } ================================================ FILE: transport/internet/tcp/dialer.go ================================================ // +build !confonly package tcp import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) // Dial dials a new TCP connection to the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { newError("dialing TCP to ", dest).WriteToLog(session.ExportIDToError(ctx)) conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) if err != nil { return nil, err } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) /* if config.IsExperiment8357() { conn = tls.UClient(conn, tlsConfig) } else { conn = tls.Client(conn, tlsConfig) } */ conn = tls.Client(conn, tlsConfig) } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { xtlsConfig := config.GetXTLSConfig(xtls.WithDestination(dest)) conn = xtls.Client(conn, xtlsConfig) } tcpSettings := streamSettings.ProtocolSettings.(*Config) if tcpSettings.HeaderSettings != nil { headerConfig, err := tcpSettings.HeaderSettings.GetInstance() if err != nil { return nil, newError("failed to get header settings").Base(err).AtError() } auth, err := internet.CreateConnectionAuthenticator(headerConfig) if err != nil { return nil, newError("failed to create header authenticator").Base(err).AtError() } conn = auth.Client(conn) } return internet.Connection(conn), nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } ================================================ FILE: transport/internet/tcp/errors.generated.go ================================================ package tcp import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/tcp/hub.go ================================================ // +build !confonly package tcp import ( "context" gotls "crypto/tls" "strings" "time" "github.com/pires/go-proxyproto" goxtls "github.com/xtls/go" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/xtls" ) // Listener is an internet.Listener that listens for TCP connections. type Listener struct { listener net.Listener tlsConfig *gotls.Config xtlsConfig *goxtls.Config authConfig internet.ConnectionAuthenticator config *Config addConn internet.ConnHandler } // ListenTCP creates a new Listener based on configurations. func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { listener, err := internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), Port: int(port), }, streamSettings.SocketSettings) if err != nil { return nil, newError("failed to listen TCP on", address, ":", port).Base(err) } newError("listening TCP on ", address, ":", port).WriteToLog(session.ExportIDToError(ctx)) tcpSettings := streamSettings.ProtocolSettings.(*Config) var l *Listener if tcpSettings.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } l = &Listener{ listener: &proxyproto.Listener{Listener: listener, Policy: policyFunc}, config: tcpSettings, addConn: handler, } newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx)) } else { l = &Listener{ listener: listener, config: tcpSettings, addConn: handler, } } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { l.tlsConfig = config.GetTLSConfig(tls.WithNextProto("h2")) } if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { l.xtlsConfig = config.GetXTLSConfig(xtls.WithNextProto("h2")) } if tcpSettings.HeaderSettings != nil { headerConfig, err := tcpSettings.HeaderSettings.GetInstance() if err != nil { return nil, newError("invalid header settings").Base(err).AtError() } auth, err := internet.CreateConnectionAuthenticator(headerConfig) if err != nil { return nil, newError("invalid header settings.").Base(err).AtError() } l.authConfig = auth } go l.keepAccepting() return l, nil } func (v *Listener) keepAccepting() { for { conn, err := v.listener.Accept() if err != nil { errStr := err.Error() if strings.Contains(errStr, "closed") { break } newError("failed to accepted raw connections").Base(err).AtWarning().WriteToLog() if strings.Contains(errStr, "too many") { time.Sleep(time.Millisecond * 500) } continue } if v.tlsConfig != nil { conn = tls.Server(conn, v.tlsConfig) } else if v.xtlsConfig != nil { conn = xtls.Server(conn, v.xtlsConfig) } if v.authConfig != nil { conn = v.authConfig.Server(conn) } v.addConn(internet.Connection(conn)) } } // Addr implements internet.Listener.Addr. func (v *Listener) Addr() net.Addr { return v.listener.Addr() } // Close implements internet.Listener.Close. func (v *Listener) Close() error { return v.listener.Close() } func init() { common.Must(internet.RegisterTransportListener(protocolName, ListenTCP)) } ================================================ FILE: transport/internet/tcp/sockopt_freebsd.go ================================================ // +build freebsd // +build !confonly package tcp import ( "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) // GetOriginalDestination from tcp conn func GetOriginalDestination(conn internet.Connection) (net.Destination, error) { la := conn.LocalAddr() ra := conn.RemoteAddr() ip, port, err := internet.OriginalDst(la, ra) if err != nil { return net.Destination{}, newError("failed to get destination").Base(err) } dest := net.TCPDestination(net.IPAddress(ip), net.Port(port)) if !dest.IsValid() { return net.Destination{}, newError("failed to parse destination.") } return dest, nil } ================================================ FILE: transport/internet/tcp/sockopt_linux.go ================================================ // +build linux // +build !confonly package tcp import ( "syscall" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) const SO_ORIGINAL_DST = 80 func GetOriginalDestination(conn internet.Connection) (net.Destination, error) { sysrawconn, f := conn.(syscall.Conn) if !f { return net.Destination{}, newError("unable to get syscall.Conn") } rawConn, err := sysrawconn.SyscallConn() if err != nil { return net.Destination{}, newError("failed to get sys fd").Base(err) } var dest net.Destination err = rawConn.Control(func(fd uintptr) { addr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST) if err != nil { newError("failed to call getsockopt").Base(err).WriteToLog() return } ip := net.IPAddress(addr.Multiaddr[4:8]) port := uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3]) dest = net.TCPDestination(ip, net.Port(port)) }) if err != nil { return net.Destination{}, newError("failed to control connection").Base(err) } if !dest.IsValid() { return net.Destination{}, newError("failed to call getsockopt") } return dest, nil } ================================================ FILE: transport/internet/tcp/sockopt_linux_test.go ================================================ // +build linux package tcp_test import ( "context" "strings" "testing" "v2ray.com/core/common" "v2ray.com/core/testing/servers/tcp" "v2ray.com/core/transport/internet" . "v2ray.com/core/transport/internet/tcp" ) func TestGetOriginalDestination(t *testing.T) { tcpServer := tcp.Server{} dest, err := tcpServer.Start() common.Must(err) defer tcpServer.Close() config, err := internet.ToMemoryStreamConfig(nil) common.Must(err) conn, err := Dial(context.Background(), dest, config) common.Must(err) defer conn.Close() originalDest, err := GetOriginalDestination(conn) if !(dest == originalDest || strings.Contains(err.Error(), "failed to call getsockopt")) { t.Error("unexpected state") } } ================================================ FILE: transport/internet/tcp/sockopt_other.go ================================================ // +build !linux,!freebsd // +build !confonly package tcp import ( "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) func GetOriginalDestination(conn internet.Connection) (net.Destination, error) { return net.Destination{}, nil } ================================================ FILE: transport/internet/tcp/tcp.go ================================================ package tcp //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/tcp_hub.go ================================================ package internet import ( "context" "v2ray.com/core/common/net" ) var ( transportListenerCache = make(map[string]ListenFunc) ) func RegisterTransportListener(protocol string, listener ListenFunc) error { if _, found := transportListenerCache[protocol]; found { return newError(protocol, " listener already registered.").AtError() } transportListenerCache[protocol] = listener return nil } type ConnHandler func(Connection) type ListenFunc func(ctx context.Context, address net.Address, port net.Port, settings *MemoryStreamConfig, handler ConnHandler) (Listener, error) type Listener interface { Close() error Addr() net.Addr } func ListenTCP(ctx context.Context, address net.Address, port net.Port, settings *MemoryStreamConfig, handler ConnHandler) (Listener, error) { if settings == nil { s, err := ToMemoryStreamConfig(nil) if err != nil { return nil, newError("failed to create default stream settings").Base(err) } settings = s } if address.Family().IsDomain() && address.Domain() == "localhost" { address = net.LocalHostIP } if address.Family().IsDomain() { return nil, newError("domain address is not allowed for listening: ", address.Domain()) } protocol := settings.ProtocolName listenFunc := transportListenerCache[protocol] if listenFunc == nil { return nil, newError(protocol, " listener not registered.").AtError() } listener, err := listenFunc(ctx, address, port, settings, handler) if err != nil { return nil, newError("failed to listen on address: ", address, ":", port).Base(err) } return listener, nil } // ListenSystem listens on a local address for incoming TCP connections. // // v2ray:api:beta func ListenSystem(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) { return effectiveListener.Listen(ctx, addr, sockopt) } // ListenSystemPacket listens on a local address for incoming UDP connections. // // v2ray:api:beta func ListenSystemPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) { return effectiveListener.ListenPacket(ctx, addr, sockopt) } ================================================ FILE: transport/internet/tls/config.go ================================================ // +build !confonly package tls import ( "crypto/tls" "crypto/x509" "strings" "sync" "time" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/transport/internet" ) var ( globalSessionCache = tls.NewLRUClientSessionCache(128) ) const exp8357 = "experiment:8357" // ParseCertificate converts a cert.Certificate to Certificate. func ParseCertificate(c *cert.Certificate) *Certificate { certPEM, keyPEM := c.ToPEM() return &Certificate{ Certificate: certPEM, Key: keyPEM, } } func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { root := x509.NewCertPool() for _, cert := range c.Certificate { if !root.AppendCertsFromPEM(cert.Certificate) { return nil, newError("failed to append cert").AtWarning() } } return root, nil } // BuildCertificates builds a list of TLS certificates from proto definition. func (c *Config) BuildCertificates() []tls.Certificate { certs := make([]tls.Certificate, 0, len(c.Certificate)) for _, entry := range c.Certificate { if entry.Usage != Certificate_ENCIPHERMENT { continue } keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key) if err != nil { newError("ignoring invalid X509 key pair").Base(err).AtWarning().WriteToLog() continue } certs = append(certs, keyPair) } return certs } func isCertificateExpired(c *tls.Certificate) bool { if c.Leaf == nil && len(c.Certificate) > 0 { if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil { c.Leaf = pc } } // If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate. return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(-time.Minute)) } func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) { parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) if err != nil { return nil, newError("failed to parse raw certificate").Base(err) } newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) if err != nil { return nil, newError("failed to generate new certificate for ", domain).Base(err) } newCertPEM, newKeyPEM := newCert.ToPEM() cert, err := tls.X509KeyPair(newCertPEM, newKeyPEM) return &cert, err } func (c *Config) getCustomCA() []*Certificate { certs := make([]*Certificate, 0, len(c.Certificate)) for _, certificate := range c.Certificate { if certificate.Usage == Certificate_AUTHORITY_ISSUE { certs = append(certs, certificate) } } return certs } func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { var access sync.RWMutex return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { domain := hello.ServerName certExpired := false access.RLock() certificate, found := c.NameToCertificate[domain] access.RUnlock() if found { if !isCertificateExpired(certificate) { return certificate, nil } certExpired = true } if certExpired { newCerts := make([]tls.Certificate, 0, len(c.Certificates)) access.Lock() for _, certificate := range c.Certificates { if !isCertificateExpired(&certificate) { newCerts = append(newCerts, certificate) } } c.Certificates = newCerts access.Unlock() } var issuedCertificate *tls.Certificate // Create a new certificate from existing CA if possible for _, rawCert := range ca { if rawCert.Usage == Certificate_AUTHORITY_ISSUE { newCert, err := issueCertificate(rawCert, domain) if err != nil { newError("failed to issue new certificate for ", domain).Base(err).WriteToLog() continue } access.Lock() c.Certificates = append(c.Certificates, *newCert) issuedCertificate = &c.Certificates[len(c.Certificates)-1] access.Unlock() break } } if issuedCertificate == nil { return nil, newError("failed to create a new certificate for ", domain) } access.Lock() c.BuildNameToCertificate() access.Unlock() return issuedCertificate, nil } } func (c *Config) IsExperiment8357() bool { return strings.HasPrefix(c.ServerName, exp8357) } func (c *Config) parseServerName() string { if c.IsExperiment8357() { return c.ServerName[len(exp8357):] } return c.ServerName } // GetTLSConfig converts this Config into tls.Config. func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { root, err := c.getCertPool() if err != nil { newError("failed to load system root certificate").AtError().Base(err).WriteToLog() } config := &tls.Config{ ClientSessionCache: globalSessionCache, RootCAs: root, InsecureSkipVerify: c.AllowInsecure, NextProtos: c.NextProtocol, SessionTicketsDisabled: c.DisableSessionResumption, } if c == nil { return config } for _, opt := range opts { opt(config) } config.Certificates = c.BuildCertificates() config.BuildNameToCertificate() caCerts := c.getCustomCA() if len(caCerts) > 0 { config.GetCertificate = getGetCertificateFunc(config, caCerts) } if sn := c.parseServerName(); len(sn) > 0 { config.ServerName = sn } if len(config.NextProtos) == 0 { config.NextProtos = []string{"h2", "http/1.1"} } return config } // Option for building TLS config. type Option func(*tls.Config) // WithDestination sets the server name in TLS config. func WithDestination(dest net.Destination) Option { return func(config *tls.Config) { if dest.Address.Family().IsDomain() && config.ServerName == "" { config.ServerName = dest.Address.Domain() } } } // WithNextProto sets the ALPN values in TLS config. func WithNextProto(protocol ...string) Option { return func(config *tls.Config) { if len(config.NextProtos) == 0 { config.NextProtos = protocol } } } // ConfigFromStreamSettings fetches Config from stream settings. Nil if not found. func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { if settings == nil { return nil } config, ok := settings.SecuritySettings.(*Config) if !ok { return nil } return config } ================================================ FILE: transport/internet/tls/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/tls/config.proto package tls import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Certificate_Usage int32 const ( Certificate_ENCIPHERMENT Certificate_Usage = 0 Certificate_AUTHORITY_VERIFY Certificate_Usage = 1 Certificate_AUTHORITY_ISSUE Certificate_Usage = 2 ) // Enum value maps for Certificate_Usage. var ( Certificate_Usage_name = map[int32]string{ 0: "ENCIPHERMENT", 1: "AUTHORITY_VERIFY", 2: "AUTHORITY_ISSUE", } Certificate_Usage_value = map[string]int32{ "ENCIPHERMENT": 0, "AUTHORITY_VERIFY": 1, "AUTHORITY_ISSUE": 2, } ) func (x Certificate_Usage) Enum() *Certificate_Usage { p := new(Certificate_Usage) *p = x return p } func (x Certificate_Usage) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Certificate_Usage) Descriptor() protoreflect.EnumDescriptor { return file_transport_internet_tls_config_proto_enumTypes[0].Descriptor() } func (Certificate_Usage) Type() protoreflect.EnumType { return &file_transport_internet_tls_config_proto_enumTypes[0] } func (x Certificate_Usage) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Certificate_Usage.Descriptor instead. func (Certificate_Usage) EnumDescriptor() ([]byte, []int) { return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{0, 0} } type Certificate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // TLS certificate in x509 format. Certificate []byte `protobuf:"bytes,1,opt,name=Certificate,proto3" json:"Certificate,omitempty"` // TLS key in x509 format. Key []byte `protobuf:"bytes,2,opt,name=Key,proto3" json:"Key,omitempty"` Usage Certificate_Usage `protobuf:"varint,3,opt,name=usage,proto3,enum=v2ray.core.transport.internet.tls.Certificate_Usage" json:"usage,omitempty"` } func (x *Certificate) Reset() { *x = Certificate{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_tls_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Certificate) String() string { return protoimpl.X.MessageStringOf(x) } func (*Certificate) ProtoMessage() {} func (x *Certificate) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_tls_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Certificate.ProtoReflect.Descriptor instead. func (*Certificate) Descriptor() ([]byte, []int) { return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{0} } func (x *Certificate) GetCertificate() []byte { if x != nil { return x.Certificate } return nil } func (x *Certificate) GetKey() []byte { if x != nil { return x.Key } return nil } func (x *Certificate) GetUsage() Certificate_Usage { if x != nil { return x.Usage } return Certificate_ENCIPHERMENT } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Whether or not to allow self-signed certificates. AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` // Whether or not to allow insecure cipher suites. AllowInsecureCiphers bool `protobuf:"varint,5,opt,name=allow_insecure_ciphers,json=allowInsecureCiphers,proto3" json:"allow_insecure_ciphers,omitempty"` // List of certificates to be served on server. Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` // Override server name. ServerName string `protobuf:"bytes,3,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` // Lists of string as ALPN values. NextProtocol []string `protobuf:"bytes,4,rep,name=next_protocol,json=nextProtocol,proto3" json:"next_protocol,omitempty"` // Whether or not to disable session (ticket) resumption. DisableSessionResumption bool `protobuf:"varint,6,opt,name=disable_session_resumption,json=disableSessionResumption,proto3" json:"disable_session_resumption,omitempty"` // If true, root certificates on the system will not be loaded for // verification. DisableSystemRoot bool `protobuf:"varint,7,opt,name=disable_system_root,json=disableSystemRoot,proto3" json:"disable_system_root,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_tls_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_tls_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetAllowInsecure() bool { if x != nil { return x.AllowInsecure } return false } func (x *Config) GetAllowInsecureCiphers() bool { if x != nil { return x.AllowInsecureCiphers } return false } func (x *Config) GetCertificate() []*Certificate { if x != nil { return x.Certificate } return nil } func (x *Config) GetServerName() string { if x != nil { return x.ServerName } return "" } func (x *Config) GetNextProtocol() []string { if x != nil { return x.NextProtocol } return nil } func (x *Config) GetDisableSessionResumption() bool { if x != nil { return x.DisableSessionResumption } return false } func (x *Config) GetDisableSystemRoot() bool { if x != nil { return x.DisableSystemRoot } return false } var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x0a, 0x23, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x22, 0xd3, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x4a, 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x22, 0x44, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xeb, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, 0x42, 0x74, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x21, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_tls_config_proto_rawDescOnce sync.Once file_transport_internet_tls_config_proto_rawDescData = file_transport_internet_tls_config_proto_rawDesc ) func file_transport_internet_tls_config_proto_rawDescGZIP() []byte { file_transport_internet_tls_config_proto_rawDescOnce.Do(func() { file_transport_internet_tls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_tls_config_proto_rawDescData) }) return file_transport_internet_tls_config_proto_rawDescData } var file_transport_internet_tls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_transport_internet_tls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_transport_internet_tls_config_proto_goTypes = []interface{}{ (Certificate_Usage)(0), // 0: v2ray.core.transport.internet.tls.Certificate.Usage (*Certificate)(nil), // 1: v2ray.core.transport.internet.tls.Certificate (*Config)(nil), // 2: v2ray.core.transport.internet.tls.Config } var file_transport_internet_tls_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.transport.internet.tls.Certificate.usage:type_name -> v2ray.core.transport.internet.tls.Certificate.Usage 1, // 1: v2ray.core.transport.internet.tls.Config.certificate:type_name -> v2ray.core.transport.internet.tls.Certificate 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_transport_internet_tls_config_proto_init() } func file_transport_internet_tls_config_proto_init() { if File_transport_internet_tls_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_tls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Certificate); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_tls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_tls_config_proto_rawDesc, NumEnums: 1, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_tls_config_proto_goTypes, DependencyIndexes: file_transport_internet_tls_config_proto_depIdxs, EnumInfos: file_transport_internet_tls_config_proto_enumTypes, MessageInfos: file_transport_internet_tls_config_proto_msgTypes, }.Build() File_transport_internet_tls_config_proto = out.File file_transport_internet_tls_config_proto_rawDesc = nil file_transport_internet_tls_config_proto_goTypes = nil file_transport_internet_tls_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/tls/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.tls; option csharp_namespace = "V2Ray.Core.Transport.Internet.Tls"; option go_package = "v2ray.com/core/transport/internet/tls"; option java_package = "com.v2ray.core.transport.internet.tls"; option java_multiple_files = true; message Certificate { // TLS certificate in x509 format. bytes Certificate = 1; // TLS key in x509 format. bytes Key = 2; enum Usage { ENCIPHERMENT = 0; AUTHORITY_VERIFY = 1; AUTHORITY_ISSUE = 2; } Usage usage = 3; } message Config { // Whether or not to allow self-signed certificates. bool allow_insecure = 1; // Whether or not to allow insecure cipher suites. bool allow_insecure_ciphers = 5; // List of certificates to be served on server. repeated Certificate certificate = 2; // Override server name. string server_name = 3; // Lists of string as ALPN values. repeated string next_protocol = 4; // Whether or not to disable session (ticket) resumption. bool disable_session_resumption = 6; // If true, root certificates on the system will not be loaded for // verification. bool disable_system_root = 7; } ================================================ FILE: transport/internet/tls/config_other.go ================================================ // +build !windows // +build !confonly package tls import ( "crypto/x509" "sync" ) type rootCertsCache struct { sync.Mutex pool *x509.CertPool } func (c *rootCertsCache) load() (*x509.CertPool, error) { c.Lock() defer c.Unlock() if c.pool != nil { return c.pool, nil } pool, err := x509.SystemCertPool() if err != nil { return nil, err } c.pool = pool return pool, nil } var rootCerts rootCertsCache func (c *Config) getCertPool() (*x509.CertPool, error) { if c.DisableSystemRoot { return c.loadSelfCertPool() } if len(c.Certificate) == 0 { return rootCerts.load() } pool, err := x509.SystemCertPool() if err != nil { return nil, newError("system root").AtWarning().Base(err) } for _, cert := range c.Certificate { if !pool.AppendCertsFromPEM(cert.Certificate) { return nil, newError("append cert to root").AtWarning().Base(err) } } return pool, err } ================================================ FILE: transport/internet/tls/config_test.go ================================================ package tls_test import ( gotls "crypto/tls" "crypto/x509" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/protocol/tls/cert" . "v2ray.com/core/transport/internet/tls" ) func TestCertificateIssuing(t *testing.T) { certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) certificate.Usage = Certificate_AUTHORITY_ISSUE c := &Config{ Certificate: []*Certificate{ certificate, }, } tlsConfig := c.GetTLSConfig() v2rayCert, err := tlsConfig.GetCertificate(&gotls.ClientHelloInfo{ ServerName: "www.v2ray.com", }) common.Must(err) x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) common.Must(err) if !x509Cert.NotAfter.After(time.Now()) { t.Error("NotAfter: ", x509Cert.NotAfter) } } func TestExpiredCertificate(t *testing.T) { caCert := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign)) expiredCert := cert.MustGenerate(caCert, cert.NotAfter(time.Now().Add(time.Minute*-2)), cert.CommonName("www.v2ray.com"), cert.DNSNames("www.v2ray.com")) certificate := ParseCertificate(caCert) certificate.Usage = Certificate_AUTHORITY_ISSUE certificate2 := ParseCertificate(expiredCert) c := &Config{ Certificate: []*Certificate{ certificate, certificate2, }, } tlsConfig := c.GetTLSConfig() v2rayCert, err := tlsConfig.GetCertificate(&gotls.ClientHelloInfo{ ServerName: "www.v2ray.com", }) common.Must(err) x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) common.Must(err) if !x509Cert.NotAfter.After(time.Now()) { t.Error("NotAfter: ", x509Cert.NotAfter) } } func TestInsecureCertificates(t *testing.T) { c := &Config{ AllowInsecureCiphers: true, } tlsConfig := c.GetTLSConfig() if len(tlsConfig.CipherSuites) > 0 { t.Fatal("Unexpected tls cipher suites list: ", tlsConfig.CipherSuites) } } func BenchmarkCertificateIssuing(b *testing.B) { certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) certificate.Usage = Certificate_AUTHORITY_ISSUE c := &Config{ Certificate: []*Certificate{ certificate, }, } tlsConfig := c.GetTLSConfig() lenCerts := len(tlsConfig.Certificates) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = tlsConfig.GetCertificate(&gotls.ClientHelloInfo{ ServerName: "www.v2ray.com", }) delete(tlsConfig.NameToCertificate, "www.v2ray.com") tlsConfig.Certificates = tlsConfig.Certificates[:lenCerts] } } ================================================ FILE: transport/internet/tls/config_windows.go ================================================ // +build windows // +build !confonly package tls import "crypto/x509" func (c *Config) getCertPool() (*x509.CertPool, error) { if c.DisableSystemRoot { return c.loadSelfCertPool() } return nil, nil } ================================================ FILE: transport/internet/tls/errors.generated.go ================================================ package tls import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/tls/tls.go ================================================ // +build !confonly package tls import ( "crypto/tls" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" ) //go:generate go run v2ray.com/core/common/errors/errorgen var ( _ buf.Writer = (*Conn)(nil) ) type Conn struct { *tls.Conn } func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } func (c *Conn) HandshakeAddress() net.Address { if err := c.Handshake(); err != nil { return nil } state := c.ConnectionState() if state.ServerName == "" { return nil } return net.ParseAddress(state.ServerName) } // Client initiates a TLS client handshake on the given connection. func Client(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Client(c, config) return &Conn{Conn: tlsConn} } /* func copyConfig(c *tls.Config) *utls.Config { return &utls.Config{ NextProtos: c.NextProtos, ServerName: c.ServerName, InsecureSkipVerify: c.InsecureSkipVerify, MinVersion: utls.VersionTLS12, MaxVersion: utls.VersionTLS12, } } func UClient(c net.Conn, config *tls.Config) net.Conn { uConfig := copyConfig(config) return utls.Client(c, uConfig) } */ // Server initiates a TLS server handshake on the given connection. func Server(c net.Conn, config *tls.Config) net.Conn { tlsConn := tls.Server(c, config) return &Conn{Conn: tlsConn} } ================================================ FILE: transport/internet/udp/config.go ================================================ package udp import ( "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/udp/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/udp/config.proto package udp import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_udp_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_udp_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_udp_config_proto_rawDescGZIP(), []int{0} } var File_transport_internet_udp_config_proto protoreflect.FileDescriptor var file_transport_internet_udp_config_proto_rawDesc = []byte{ 0x0a, 0x23, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x75, 0x64, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x75, 0x64, 0x70, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x74, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x75, 0x64, 0x70, 0x50, 0x01, 0x5a, 0x25, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x75, 0x64, 0x70, 0xaa, 0x02, 0x21, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x55, 0x64, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_udp_config_proto_rawDescOnce sync.Once file_transport_internet_udp_config_proto_rawDescData = file_transport_internet_udp_config_proto_rawDesc ) func file_transport_internet_udp_config_proto_rawDescGZIP() []byte { file_transport_internet_udp_config_proto_rawDescOnce.Do(func() { file_transport_internet_udp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_udp_config_proto_rawDescData) }) return file_transport_internet_udp_config_proto_rawDescData } var file_transport_internet_udp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_internet_udp_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v2ray.core.transport.internet.udp.Config } var file_transport_internet_udp_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_transport_internet_udp_config_proto_init() } func file_transport_internet_udp_config_proto_init() { if File_transport_internet_udp_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_udp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_udp_config_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_udp_config_proto_goTypes, DependencyIndexes: file_transport_internet_udp_config_proto_depIdxs, MessageInfos: file_transport_internet_udp_config_proto_msgTypes, }.Build() File_transport_internet_udp_config_proto = out.File file_transport_internet_udp_config_proto_rawDesc = nil file_transport_internet_udp_config_proto_goTypes = nil file_transport_internet_udp_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/udp/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.udp; option csharp_namespace = "V2Ray.Core.Transport.Internet.Udp"; option go_package = "v2ray.com/core/transport/internet/udp"; option java_package = "com.v2ray.core.transport.internet.udp"; option java_multiple_files = true; message Config {} ================================================ FILE: transport/internet/udp/dialer.go ================================================ package udp import ( "context" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) func init() { common.Must(internet.RegisterTransportDialer(protocolName, func(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { var sockopt *internet.SocketConfig if streamSettings != nil { sockopt = streamSettings.SocketSettings } conn, err := internet.DialSystem(ctx, dest, sockopt) if err != nil { return nil, err } // TODO: handle dialer options return internet.Connection(conn), nil })) } ================================================ FILE: transport/internet/udp/dispatcher.go ================================================ package udp import ( "context" "io" "sync" "time" "v2ray.com/core/common/signal/done" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/udp" "v2ray.com/core/common/session" "v2ray.com/core/common/signal" "v2ray.com/core/features/routing" "v2ray.com/core/transport" ) type ResponseCallback func(ctx context.Context, packet *udp.Packet) type connEntry struct { link *transport.Link timer signal.ActivityUpdater cancel context.CancelFunc } type Dispatcher struct { sync.RWMutex conns map[net.Destination]*connEntry dispatcher routing.Dispatcher callback ResponseCallback } func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher { return &Dispatcher{ conns: make(map[net.Destination]*connEntry), dispatcher: dispatcher, callback: callback, } } func (v *Dispatcher) RemoveRay(dest net.Destination) { v.Lock() defer v.Unlock() if conn, found := v.conns[dest]; found { common.Close(conn.link.Reader) common.Close(conn.link.Writer) delete(v.conns, dest) } } func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) *connEntry { v.Lock() defer v.Unlock() if entry, found := v.conns[dest]; found { return entry } newError("establishing new connection for ", dest).WriteToLog() ctx, cancel := context.WithCancel(ctx) removeRay := func() { cancel() v.RemoveRay(dest) } timer := signal.CancelAfterInactivity(ctx, removeRay, time.Second*4) link, _ := v.dispatcher.Dispatch(ctx, dest) entry := &connEntry{ link: link, timer: timer, cancel: removeRay, } v.conns[dest] = entry go handleInput(ctx, entry, dest, v.callback) return entry } func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination, payload *buf.Buffer) { // TODO: Add user to destString newError("dispatch request to: ", destination).AtDebug().WriteToLog(session.ExportIDToError(ctx)) conn := v.getInboundRay(ctx, destination) outputStream := conn.link.Writer if outputStream != nil { if err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil { newError("failed to write first UDP payload").Base(err).WriteToLog(session.ExportIDToError(ctx)) conn.cancel() return } } } func handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback) { defer conn.cancel() input := conn.link.Reader timer := conn.timer for { select { case <-ctx.Done(): return default: } mb, err := input.ReadMultiBuffer() if err != nil { newError("failed to handle UDP input").Base(err).WriteToLog(session.ExportIDToError(ctx)) return } timer.Update() for _, b := range mb { callback(ctx, &udp.Packet{ Payload: b, Source: dest, }) } } } type dispatcherConn struct { dispatcher *Dispatcher cache chan *udp.Packet done *done.Instance } func DialDispatcher(ctx context.Context, dispatcher routing.Dispatcher) (net.PacketConn, error) { c := &dispatcherConn{ cache: make(chan *udp.Packet, 16), done: done.New(), } d := NewDispatcher(dispatcher, c.callback) c.dispatcher = d return c, nil } func (c *dispatcherConn) callback(ctx context.Context, packet *udp.Packet) { select { case <-c.done.Wait(): packet.Payload.Release() return case c.cache <- packet: default: packet.Payload.Release() return } } func (c *dispatcherConn) ReadFrom(p []byte) (int, net.Addr, error) { select { case <-c.done.Wait(): return 0, nil, io.EOF case packet := <-c.cache: n := copy(p, packet.Payload.Bytes()) return n, &net.UDPAddr{ IP: packet.Source.Address.IP(), Port: int(packet.Source.Port), }, nil } } func (c *dispatcherConn) WriteTo(p []byte, addr net.Addr) (int, error) { buffer := buf.New() raw := buffer.Extend(buf.Size) n := copy(raw, p) buffer.Resize(0, int32(n)) ctx := context.Background() c.dispatcher.Dispatch(ctx, net.DestinationFromAddr(addr), buffer) return n, nil } func (c *dispatcherConn) Close() error { return c.done.Close() } func (c *dispatcherConn) LocalAddr() net.Addr { return &net.UDPAddr{ IP: []byte{0, 0, 0, 0}, Port: 0, } } func (c *dispatcherConn) SetDeadline(t time.Time) error { return nil } func (c *dispatcherConn) SetReadDeadline(t time.Time) error { return nil } func (c *dispatcherConn) SetWriteDeadline(t time.Time) error { return nil } ================================================ FILE: transport/internet/udp/dispatcher_test.go ================================================ package udp_test import ( "context" "sync/atomic" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/udp" "v2ray.com/core/features/routing" "v2ray.com/core/transport" . "v2ray.com/core/transport/internet/udp" "v2ray.com/core/transport/pipe" ) type TestDispatcher struct { OnDispatch func(ctx context.Context, dest net.Destination) (*transport.Link, error) } func (d *TestDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { return d.OnDispatch(ctx, dest) } func (d *TestDispatcher) Start() error { return nil } func (d *TestDispatcher) Close() error { return nil } func (*TestDispatcher) Type() interface{} { return routing.DispatcherType() } func TestSameDestinationDispatching(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) uplinkReader, uplinkWriter := pipe.New(pipe.WithSizeLimit(1024)) downlinkReader, downlinkWriter := pipe.New(pipe.WithSizeLimit(1024)) go func() { for { data, err := uplinkReader.ReadMultiBuffer() if err != nil { break } err = downlinkWriter.WriteMultiBuffer(data) common.Must(err) } }() var count uint32 td := &TestDispatcher{ OnDispatch: func(ctx context.Context, dest net.Destination) (*transport.Link, error) { atomic.AddUint32(&count, 1) return &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, nil }, } dest := net.UDPDestination(net.LocalHostIP, 53) b := buf.New() b.WriteString("abcd") var msgCount uint32 dispatcher := NewDispatcher(td, func(ctx context.Context, packet *udp.Packet) { atomic.AddUint32(&msgCount, 1) }) dispatcher.Dispatch(ctx, dest, b) for i := 0; i < 5; i++ { dispatcher.Dispatch(ctx, dest, b) } time.Sleep(time.Second) cancel() if count != 1 { t.Error("count: ", count) } if v := atomic.LoadUint32(&msgCount); v != 6 { t.Error("msgCount: ", v) } } ================================================ FILE: transport/internet/udp/errors.generated.go ================================================ package udp import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/udp/hub.go ================================================ package udp import ( "context" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/udp" "v2ray.com/core/transport/internet" ) type HubOption func(h *Hub) func HubCapacity(capacity int) HubOption { return func(h *Hub) { h.capacity = capacity } } func HubReceiveOriginalDestination(r bool) HubOption { return func(h *Hub) { h.recvOrigDest = r } } type Hub struct { conn *net.UDPConn cache chan *udp.Packet capacity int recvOrigDest bool } func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, options ...HubOption) (*Hub, error) { hub := &Hub{ capacity: 256, recvOrigDest: false, } for _, opt := range options { opt(hub) } var sockopt *internet.SocketConfig if streamSettings != nil { sockopt = streamSettings.SocketSettings } if sockopt != nil && sockopt.ReceiveOriginalDestAddress { hub.recvOrigDest = true } udpConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{ IP: address.IP(), Port: int(port), }, sockopt) if err != nil { return nil, err } newError("listening UDP on ", address, ":", port).WriteToLog() hub.conn = udpConn.(*net.UDPConn) hub.cache = make(chan *udp.Packet, hub.capacity) go hub.start() return hub, nil } // Close implements net.Listener. func (h *Hub) Close() error { h.conn.Close() return nil } func (h *Hub) WriteTo(payload []byte, dest net.Destination) (int, error) { return h.conn.WriteToUDP(payload, &net.UDPAddr{ IP: dest.Address.IP(), Port: int(dest.Port), }) } func (h *Hub) start() { c := h.cache defer close(c) oobBytes := make([]byte, 256) for { buffer := buf.New() var noob int var addr *net.UDPAddr rawBytes := buffer.Extend(buf.Size) n, noob, _, addr, err := ReadUDPMsg(h.conn, rawBytes, oobBytes) if err != nil { newError("failed to read UDP msg").Base(err).WriteToLog() buffer.Release() break } buffer.Resize(0, int32(n)) if buffer.IsEmpty() { buffer.Release() continue } payload := &udp.Packet{ Payload: buffer, Source: net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), } if h.recvOrigDest && noob > 0 { payload.Target = RetrieveOriginalDest(oobBytes[:noob]) if payload.Target.IsValid() { newError("UDP original destination: ", payload.Target).AtDebug().WriteToLog() } else { newError("failed to read UDP original destination").WriteToLog() } } select { case c <- payload: default: buffer.Release() payload.Payload = nil } } } // Addr implements net.Listener. func (h *Hub) Addr() net.Addr { return h.conn.LocalAddr() } func (h *Hub) Receive() <-chan *udp.Packet { return h.cache } ================================================ FILE: transport/internet/udp/hub_freebsd.go ================================================ // +build freebsd package udp import ( "bytes" "encoding/gob" "io" "v2ray.com/core/common/net" "v2ray.com/core/transport/internet" ) // RetrieveOriginalDest from stored laddr, caddr func RetrieveOriginalDest(oob []byte) net.Destination { dec := gob.NewDecoder(bytes.NewBuffer(oob)) var la, ra net.UDPAddr dec.Decode(&la) dec.Decode(&ra) ip, port, err := internet.OriginalDst(&la, &ra) if err != nil { return net.Destination{} } return net.UDPDestination(net.IPAddress(ip), net.Port(port)) } // ReadUDPMsg stores laddr, caddr for later use func ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) { nBytes, addr, err := conn.ReadFromUDP(payload) var buf bytes.Buffer enc := gob.NewEncoder(&buf) enc.Encode(conn.LocalAddr().(*net.UDPAddr)) enc.Encode(addr) var reader io.Reader = &buf noob, _ := reader.Read(oob) return nBytes, noob, 0, addr, err } ================================================ FILE: transport/internet/udp/hub_linux.go ================================================ // +build linux package udp import ( "syscall" "golang.org/x/sys/unix" "v2ray.com/core/common/net" ) func RetrieveOriginalDest(oob []byte) net.Destination { msgs, err := syscall.ParseSocketControlMessage(oob) if err != nil { return net.Destination{} } for _, msg := range msgs { if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { ip := net.IPAddress(msg.Data[4:8]) port := net.PortFromBytes(msg.Data[2:4]) return net.UDPDestination(ip, port) } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == unix.IPV6_RECVORIGDSTADDR { ip := net.IPAddress(msg.Data[8:24]) port := net.PortFromBytes(msg.Data[2:4]) return net.UDPDestination(ip, port) } } return net.Destination{} } func ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) { return conn.ReadMsgUDP(payload, oob) } ================================================ FILE: transport/internet/udp/hub_other.go ================================================ // +build !linux,!freebsd package udp import ( "v2ray.com/core/common/net" ) func RetrieveOriginalDest(oob []byte) net.Destination { return net.Destination{} } func ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) { nBytes, addr, err := conn.ReadFromUDP(payload) return nBytes, 0, 0, addr, err } ================================================ FILE: transport/internet/udp/udp.go ================================================ package udp //go:generate go run v2ray.com/core/common/errors/errorgen const protocolName = "udp" ================================================ FILE: transport/internet/websocket/config.go ================================================ // +build !confonly package websocket import ( "net/http" "v2ray.com/core/common" "v2ray.com/core/transport/internet" ) const protocolName = "websocket" func (c *Config) GetNormalizedPath() string { path := c.Path if path == "" { return "/" } if path[0] != '/' { return "/" + path } return path } func (c *Config) GetRequestHeader() http.Header { header := http.Header{} for _, h := range c.Header { header.Add(h.Key, h.Value) } return header } func init() { common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} { return new(Config) })) } ================================================ FILE: transport/internet/websocket/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/websocket/config.proto package websocket import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Header struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *Header) Reset() { *x = Header{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_websocket_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Header) String() string { return protoimpl.X.MessageStringOf(x) } func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_websocket_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Header.ProtoReflect.Descriptor instead. func (*Header) Descriptor() ([]byte, []int) { return file_transport_internet_websocket_config_proto_rawDescGZIP(), []int{0} } func (x *Header) GetKey() string { if x != nil { return x.Key } return "" } func (x *Header) GetValue() string { if x != nil { return x.Value } return "" } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // URL path to the WebSocket service. Empty value means root(/). Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` Header []*Header `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty"` AcceptProxyProtocol bool `protobuf:"varint,4,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_websocket_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_websocket_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_websocket_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetPath() string { if x != nil { return x.Path } return "" } func (x *Config) GetHeader() []*Header { if x != nil { return x.Header } return nil } func (x *Config) GetAcceptProxyProtocol() bool { if x != nil { return x.AcceptProxyProtocol } return false } var File_transport_internet_websocket_config_proto protoreflect.FileDescriptor var file_transport_internet_websocket_config_proto_rawDesc = []byte{ 0x0a, 0x29, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x47, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x42, 0x86, 0x01, 0x0a, 0x2b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2b, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x27, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_websocket_config_proto_rawDescOnce sync.Once file_transport_internet_websocket_config_proto_rawDescData = file_transport_internet_websocket_config_proto_rawDesc ) func file_transport_internet_websocket_config_proto_rawDescGZIP() []byte { file_transport_internet_websocket_config_proto_rawDescOnce.Do(func() { file_transport_internet_websocket_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_websocket_config_proto_rawDescData) }) return file_transport_internet_websocket_config_proto_rawDescData } var file_transport_internet_websocket_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_transport_internet_websocket_config_proto_goTypes = []interface{}{ (*Header)(nil), // 0: v2ray.core.transport.internet.websocket.Header (*Config)(nil), // 1: v2ray.core.transport.internet.websocket.Config } var file_transport_internet_websocket_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.transport.internet.websocket.Config.header:type_name -> v2ray.core.transport.internet.websocket.Header 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_transport_internet_websocket_config_proto_init() } func file_transport_internet_websocket_config_proto_init() { if File_transport_internet_websocket_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_websocket_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Header); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_websocket_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_websocket_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_websocket_config_proto_goTypes, DependencyIndexes: file_transport_internet_websocket_config_proto_depIdxs, MessageInfos: file_transport_internet_websocket_config_proto_msgTypes, }.Build() File_transport_internet_websocket_config_proto = out.File file_transport_internet_websocket_config_proto_rawDesc = nil file_transport_internet_websocket_config_proto_goTypes = nil file_transport_internet_websocket_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/websocket/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.websocket; option csharp_namespace = "V2Ray.Core.Transport.Internet.Websocket"; option go_package = "v2ray.com/core/transport/internet/websocket"; option java_package = "com.v2ray.core.transport.internet.websocket"; option java_multiple_files = true; message Header { string key = 1; string value = 2; } message Config { reserved 1; // URL path to the WebSocket service. Empty value means root(/). string path = 2; repeated Header header = 3; bool accept_proxy_protocol = 4; } ================================================ FILE: transport/internet/websocket/connection.go ================================================ // +build !confonly package websocket import ( "io" "net" "time" "github.com/gorilla/websocket" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" "v2ray.com/core/common/serial" ) var ( _ buf.Writer = (*connection)(nil) ) // connection is a wrapper for net.Conn over WebSocket connection. type connection struct { conn *websocket.Conn reader io.Reader remoteAddr net.Addr } func newConnection(conn *websocket.Conn, remoteAddr net.Addr) *connection { return &connection{ conn: conn, remoteAddr: remoteAddr, } } // Read implements net.Conn.Read() func (c *connection) Read(b []byte) (int, error) { for { reader, err := c.getReader() if err != nil { return 0, err } nBytes, err := reader.Read(b) if errors.Cause(err) == io.EOF { c.reader = nil continue } return nBytes, err } } func (c *connection) getReader() (io.Reader, error) { if c.reader != nil { return c.reader, nil } _, reader, err := c.conn.NextReader() if err != nil { return nil, err } c.reader = reader return reader, nil } // Write implements io.Writer. func (c *connection) Write(b []byte) (int, error) { if err := c.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { return 0, err } return len(b), nil } func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } func (c *connection) Close() error { var errors []interface{} if err := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { errors = append(errors, err) } if err := c.conn.Close(); err != nil { errors = append(errors, err) } if len(errors) > 0 { return newError("failed to close connection").Base(newError(serial.Concat(errors...))) } return nil } func (c *connection) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *connection) RemoteAddr() net.Addr { return c.remoteAddr } func (c *connection) SetDeadline(t time.Time) error { if err := c.SetReadDeadline(t); err != nil { return err } return c.SetWriteDeadline(t) } func (c *connection) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } func (c *connection) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } ================================================ FILE: transport/internet/websocket/dialer.go ================================================ // +build !confonly package websocket import ( "context" "time" "github.com/gorilla/websocket" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" ) // Dial dials a WebSocket connection to the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx)) conn, err := dialWebsocket(ctx, dest, streamSettings) if err != nil { return nil, newError("failed to dial WebSocket").Base(err) } return internet.Connection(conn), nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } func dialWebsocket(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { wsSettings := streamSettings.ProtocolSettings.(*Config) dialer := &websocket.Dialer{ NetDial: func(network, addr string) (net.Conn, error) { return internet.DialSystem(ctx, dest, streamSettings.SocketSettings) }, ReadBufferSize: 4 * 1024, WriteBufferSize: 4 * 1024, HandshakeTimeout: time.Second * 8, } protocol := "ws" if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { protocol = "wss" dialer.TLSClientConfig = config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) } host := dest.NetAddr() if (protocol == "ws" && dest.Port == 80) || (protocol == "wss" && dest.Port == 443) { host = dest.Address.String() } uri := protocol + "://" + host + wsSettings.GetNormalizedPath() conn, resp, err := dialer.Dial(uri, wsSettings.GetRequestHeader()) if err != nil { var reason string if resp != nil { reason = resp.Status } return nil, newError("failed to dial to (", uri, "): ", reason).Base(err) } return newConnection(conn, conn.RemoteAddr()), nil } ================================================ FILE: transport/internet/websocket/errors.generated.go ================================================ package websocket import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/websocket/hub.go ================================================ // +build !confonly package websocket import ( "context" "crypto/tls" "net/http" "sync" "time" "github.com/gorilla/websocket" "github.com/pires/go-proxyproto" "v2ray.com/core/common" "v2ray.com/core/common/net" http_proto "v2ray.com/core/common/protocol/http" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" v2tls "v2ray.com/core/transport/internet/tls" ) type requestHandler struct { path string ln *Listener } var upgrader = &websocket.Upgrader{ ReadBufferSize: 4 * 1024, WriteBufferSize: 4 * 1024, HandshakeTimeout: time.Second * 4, CheckOrigin: func(r *http.Request) bool { return true }, } func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { if request.URL.Path != h.path { writer.WriteHeader(http.StatusNotFound) return } conn, err := upgrader.Upgrade(writer, request, nil) if err != nil { newError("failed to convert to WebSocket connection").Base(err).WriteToLog() return } forwardedAddrs := http_proto.ParseXForwardedFor(request.Header) remoteAddr := conn.RemoteAddr() if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { remoteAddr.(*net.TCPAddr).IP = forwardedAddrs[0].IP() } h.ln.addConn(newConnection(conn, remoteAddr)) } type Listener struct { sync.Mutex server http.Server listener net.Listener config *Config addConn internet.ConnHandler } func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { listener, err := internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), Port: int(port), }, streamSettings.SocketSettings) if err != nil { return nil, newError("failed to listen TCP(for WS) on", address, ":", port).Base(err) } newError("listening TCP(for WS) on ", address, ":", port).WriteToLog(session.ExportIDToError(ctx)) wsSettings := streamSettings.ProtocolSettings.(*Config) if wsSettings.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } listener = &proxyproto.Listener{Listener: listener, Policy: policyFunc} newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx)) } if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil { if tlsConfig := config.GetTLSConfig(); tlsConfig != nil { listener = tls.NewListener(listener, tlsConfig) } } l := &Listener{ config: wsSettings, addConn: addConn, listener: listener, } l.server = http.Server{ Handler: &requestHandler{ path: wsSettings.GetNormalizedPath(), ln: l, }, ReadHeaderTimeout: time.Second * 4, MaxHeaderBytes: 2048, } go func() { if err := l.server.Serve(l.listener); err != nil { newError("failed to serve http for WebSocket").Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } }() return l, err } // Addr implements net.Listener.Addr(). func (ln *Listener) Addr() net.Addr { return ln.listener.Addr() } // Close implements net.Listener.Close(). func (ln *Listener) Close() error { return ln.listener.Close() } func init() { common.Must(internet.RegisterTransportListener(protocolName, ListenWS)) } ================================================ FILE: transport/internet/websocket/ws.go ================================================ /*Package websocket implements Websocket transport Websocket transport implements an HTTP(S) compliable, surveillance proof transport method with plausible deniability. */ package websocket //go:generate go run v2ray.com/core/common/errors/errorgen ================================================ FILE: transport/internet/websocket/ws_test.go ================================================ package websocket_test import ( "context" "runtime" "testing" "time" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" . "v2ray.com/core/transport/internet/websocket" ) func Test_listenWSAndDial(t *testing.T) { listen, err := ListenWS(context.Background(), net.LocalHostIP, 13146, &internet.MemoryStreamConfig{ ProtocolName: "websocket", ProtocolSettings: &Config{ Path: "ws", }, }, func(conn internet.Connection) { go func(c internet.Connection) { defer c.Close() var b [1024]byte _, err := c.Read(b[:]) if err != nil { return } common.Must2(c.Write([]byte("Response"))) }(conn) }) common.Must(err) ctx := context.Background() streamSettings := &internet.MemoryStreamConfig{ ProtocolName: "websocket", ProtocolSettings: &Config{Path: "ws"}, } conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146), streamSettings) common.Must(err) _, err = conn.Write([]byte("Test connection 1")) common.Must(err) var b [1024]byte n, err := conn.Read(b[:]) common.Must(err) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) } common.Must(conn.Close()) <-time.After(time.Second * 5) conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146), streamSettings) common.Must(err) _, err = conn.Write([]byte("Test connection 2")) common.Must(err) n, err = conn.Read(b[:]) common.Must(err) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) } common.Must(conn.Close()) common.Must(listen.Close()) } func TestDialWithRemoteAddr(t *testing.T) { listen, err := ListenWS(context.Background(), net.LocalHostIP, 13148, &internet.MemoryStreamConfig{ ProtocolName: "websocket", ProtocolSettings: &Config{ Path: "ws", }, }, func(conn internet.Connection) { go func(c internet.Connection) { defer c.Close() var b [1024]byte _, err := c.Read(b[:]) //common.Must(err) if err != nil { return } _, err = c.Write([]byte("Response")) common.Must(err) }(conn) }) common.Must(err) conn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress("localhost"), 13148), &internet.MemoryStreamConfig{ ProtocolName: "websocket", ProtocolSettings: &Config{Path: "ws", Header: []*Header{{Key: "X-Forwarded-For", Value: "1.1.1.1"}}}, }) common.Must(err) _, err = conn.Write([]byte("Test connection 1")) common.Must(err) var b [1024]byte n, err := conn.Read(b[:]) common.Must(err) if string(b[:n]) != "Response" { t.Error("response: ", string(b[:n])) } common.Must(listen.Close()) } func Test_listenWSAndDial_TLS(t *testing.T) { if runtime.GOARCH == "arm64" { return } start := time.Now() streamSettings := &internet.MemoryStreamConfig{ ProtocolName: "websocket", ProtocolSettings: &Config{ Path: "wss", }, SecurityType: "tls", SecuritySettings: &tls.Config{ AllowInsecure: true, Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("localhost")))}, }, } listen, err := ListenWS(context.Background(), net.LocalHostIP, 13143, streamSettings, func(conn internet.Connection) { go func() { _ = conn.Close() }() }) common.Must(err) defer listen.Close() conn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress("localhost"), 13143), streamSettings) common.Must(err) _ = conn.Close() end := time.Now() if !end.Before(start.Add(time.Second * 5)) { t.Error("end: ", end, " start: ", start) } } ================================================ FILE: transport/internet/xtls/config.go ================================================ // +build !confonly package xtls import ( "crypto/x509" "sync" "time" xtls "github.com/xtls/go" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol/tls/cert" "v2ray.com/core/transport/internet" ) var ( globalSessionCache = xtls.NewLRUClientSessionCache(128) ) // ParseCertificate converts a cert.Certificate to Certificate. func ParseCertificate(c *cert.Certificate) *Certificate { certPEM, keyPEM := c.ToPEM() return &Certificate{ Certificate: certPEM, Key: keyPEM, } } func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { root := x509.NewCertPool() for _, cert := range c.Certificate { if !root.AppendCertsFromPEM(cert.Certificate) { return nil, newError("failed to append cert").AtWarning() } } return root, nil } // BuildCertificates builds a list of TLS certificates from proto definition. func (c *Config) BuildCertificates() []xtls.Certificate { certs := make([]xtls.Certificate, 0, len(c.Certificate)) for _, entry := range c.Certificate { if entry.Usage != Certificate_ENCIPHERMENT { continue } keyPair, err := xtls.X509KeyPair(entry.Certificate, entry.Key) if err != nil { newError("ignoring invalid X509 key pair").Base(err).AtWarning().WriteToLog() continue } certs = append(certs, keyPair) } return certs } func isCertificateExpired(c *xtls.Certificate) bool { if c.Leaf == nil && len(c.Certificate) > 0 { if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil { c.Leaf = pc } } // If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate. return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(-time.Minute)) } func issueCertificate(rawCA *Certificate, domain string) (*xtls.Certificate, error) { parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) if err != nil { return nil, newError("failed to parse raw certificate").Base(err) } newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) if err != nil { return nil, newError("failed to generate new certificate for ", domain).Base(err) } newCertPEM, newKeyPEM := newCert.ToPEM() cert, err := xtls.X509KeyPair(newCertPEM, newKeyPEM) return &cert, err } func (c *Config) getCustomCA() []*Certificate { certs := make([]*Certificate, 0, len(c.Certificate)) for _, certificate := range c.Certificate { if certificate.Usage == Certificate_AUTHORITY_ISSUE { certs = append(certs, certificate) } } return certs } func getGetCertificateFunc(c *xtls.Config, ca []*Certificate) func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { var access sync.RWMutex return func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { domain := hello.ServerName certExpired := false access.RLock() certificate, found := c.NameToCertificate[domain] access.RUnlock() if found { if !isCertificateExpired(certificate) { return certificate, nil } certExpired = true } if certExpired { newCerts := make([]xtls.Certificate, 0, len(c.Certificates)) access.Lock() for _, certificate := range c.Certificates { if !isCertificateExpired(&certificate) { newCerts = append(newCerts, certificate) } } c.Certificates = newCerts access.Unlock() } var issuedCertificate *xtls.Certificate // Create a new certificate from existing CA if possible for _, rawCert := range ca { if rawCert.Usage == Certificate_AUTHORITY_ISSUE { newCert, err := issueCertificate(rawCert, domain) if err != nil { newError("failed to issue new certificate for ", domain).Base(err).WriteToLog() continue } access.Lock() c.Certificates = append(c.Certificates, *newCert) issuedCertificate = &c.Certificates[len(c.Certificates)-1] access.Unlock() break } } if issuedCertificate == nil { return nil, newError("failed to create a new certificate for ", domain) } access.Lock() c.BuildNameToCertificate() access.Unlock() return issuedCertificate, nil } } func (c *Config) parseServerName() string { return c.ServerName } // GetXTLSConfig converts this Config into xtls.Config. func (c *Config) GetXTLSConfig(opts ...Option) *xtls.Config { root, err := c.getCertPool() if err != nil { newError("failed to load system root certificate").AtError().Base(err).WriteToLog() } config := &xtls.Config{ ClientSessionCache: globalSessionCache, RootCAs: root, InsecureSkipVerify: c.AllowInsecure, NextProtos: c.NextProtocol, SessionTicketsDisabled: c.DisableSessionResumption, } if c == nil { return config } for _, opt := range opts { opt(config) } config.Certificates = c.BuildCertificates() config.BuildNameToCertificate() caCerts := c.getCustomCA() if len(caCerts) > 0 { config.GetCertificate = getGetCertificateFunc(config, caCerts) } if sn := c.parseServerName(); len(sn) > 0 { config.ServerName = sn } if len(config.NextProtos) == 0 { config.NextProtos = []string{"h2", "http/1.1"} } return config } // Option for building XTLS config. type Option func(*xtls.Config) // WithDestination sets the server name in XTLS config. func WithDestination(dest net.Destination) Option { return func(config *xtls.Config) { if dest.Address.Family().IsDomain() && config.ServerName == "" { config.ServerName = dest.Address.Domain() } } } // WithNextProto sets the ALPN values in XTLS config. func WithNextProto(protocol ...string) Option { return func(config *xtls.Config) { if len(config.NextProtos) == 0 { config.NextProtos = protocol } } } // ConfigFromStreamSettings fetches Config from stream settings. Nil if not found. func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { if settings == nil { return nil } config, ok := settings.SecuritySettings.(*Config) if !ok { return nil } return config } ================================================ FILE: transport/internet/xtls/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.13.0 // source: transport/internet/xtls/config.proto package xtls import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Certificate_Usage int32 const ( Certificate_ENCIPHERMENT Certificate_Usage = 0 Certificate_AUTHORITY_VERIFY Certificate_Usage = 1 Certificate_AUTHORITY_ISSUE Certificate_Usage = 2 ) // Enum value maps for Certificate_Usage. var ( Certificate_Usage_name = map[int32]string{ 0: "ENCIPHERMENT", 1: "AUTHORITY_VERIFY", 2: "AUTHORITY_ISSUE", } Certificate_Usage_value = map[string]int32{ "ENCIPHERMENT": 0, "AUTHORITY_VERIFY": 1, "AUTHORITY_ISSUE": 2, } ) func (x Certificate_Usage) Enum() *Certificate_Usage { p := new(Certificate_Usage) *p = x return p } func (x Certificate_Usage) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Certificate_Usage) Descriptor() protoreflect.EnumDescriptor { return file_transport_internet_xtls_config_proto_enumTypes[0].Descriptor() } func (Certificate_Usage) Type() protoreflect.EnumType { return &file_transport_internet_xtls_config_proto_enumTypes[0] } func (x Certificate_Usage) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Certificate_Usage.Descriptor instead. func (Certificate_Usage) EnumDescriptor() ([]byte, []int) { return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0, 0} } type Certificate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // XTLS certificate in x509 format. Certificate []byte `protobuf:"bytes,1,opt,name=Certificate,proto3" json:"Certificate,omitempty"` // XTLS key in x509 format. Key []byte `protobuf:"bytes,2,opt,name=Key,proto3" json:"Key,omitempty"` Usage Certificate_Usage `protobuf:"varint,3,opt,name=usage,proto3,enum=v2ray.core.transport.internet.xtls.Certificate_Usage" json:"usage,omitempty"` } func (x *Certificate) Reset() { *x = Certificate{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_xtls_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Certificate) String() string { return protoimpl.X.MessageStringOf(x) } func (*Certificate) ProtoMessage() {} func (x *Certificate) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_xtls_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Certificate.ProtoReflect.Descriptor instead. func (*Certificate) Descriptor() ([]byte, []int) { return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0} } func (x *Certificate) GetCertificate() []byte { if x != nil { return x.Certificate } return nil } func (x *Certificate) GetKey() []byte { if x != nil { return x.Key } return nil } func (x *Certificate) GetUsage() Certificate_Usage { if x != nil { return x.Usage } return Certificate_ENCIPHERMENT } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Whether or not to allow self-signed certificates. AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` // Whether or not to allow insecure cipher suites. AllowInsecureCiphers bool `protobuf:"varint,5,opt,name=allow_insecure_ciphers,json=allowInsecureCiphers,proto3" json:"allow_insecure_ciphers,omitempty"` // List of certificates to be served on server. Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` // Override server name. ServerName string `protobuf:"bytes,3,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` // Lists of string as ALPN values. NextProtocol []string `protobuf:"bytes,4,rep,name=next_protocol,json=nextProtocol,proto3" json:"next_protocol,omitempty"` // Whether or not to disable session (ticket) resumption. DisableSessionResumption bool `protobuf:"varint,6,opt,name=disable_session_resumption,json=disableSessionResumption,proto3" json:"disable_session_resumption,omitempty"` // If true, root certificates on the system will not be loaded for // verification. DisableSystemRoot bool `protobuf:"varint,7,opt,name=disable_system_root,json=disableSystemRoot,proto3" json:"disable_system_root,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_transport_internet_xtls_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_transport_internet_xtls_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetAllowInsecure() bool { if x != nil { return x.AllowInsecure } return false } func (x *Config) GetAllowInsecureCiphers() bool { if x != nil { return x.AllowInsecureCiphers } return false } func (x *Config) GetCertificate() []*Certificate { if x != nil { return x.Certificate } return nil } func (x *Config) GetServerName() string { if x != nil { return x.ServerName } return "" } func (x *Config) GetNextProtocol() []string { if x != nil { return x.NextProtocol } return nil } func (x *Config) GetDisableSessionResumption() bool { if x != nil { return x.DisableSessionResumption } return false } func (x *Config) GetDisableSystemRoot() bool { if x != nil { return x.DisableSystemRoot } return false } var File_transport_internet_xtls_config_proto protoreflect.FileDescriptor var file_transport_internet_xtls_config_proto_rawDesc = []byte{ 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x22, 0xd4, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x4b, 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x35, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x22, 0x44, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xec, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x12, 0x51, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, 0x42, 0x77, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x26, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x58, 0x74, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_transport_internet_xtls_config_proto_rawDescOnce sync.Once file_transport_internet_xtls_config_proto_rawDescData = file_transport_internet_xtls_config_proto_rawDesc ) func file_transport_internet_xtls_config_proto_rawDescGZIP() []byte { file_transport_internet_xtls_config_proto_rawDescOnce.Do(func() { file_transport_internet_xtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_xtls_config_proto_rawDescData) }) return file_transport_internet_xtls_config_proto_rawDescData } var file_transport_internet_xtls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_transport_internet_xtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_transport_internet_xtls_config_proto_goTypes = []interface{}{ (Certificate_Usage)(0), // 0: v2ray.core.transport.internet.xtls.Certificate.Usage (*Certificate)(nil), // 1: v2ray.core.transport.internet.xtls.Certificate (*Config)(nil), // 2: v2ray.core.transport.internet.xtls.Config } var file_transport_internet_xtls_config_proto_depIdxs = []int32{ 0, // 0: v2ray.core.transport.internet.xtls.Certificate.usage:type_name -> v2ray.core.transport.internet.xtls.Certificate.Usage 1, // 1: v2ray.core.transport.internet.xtls.Config.certificate:type_name -> v2ray.core.transport.internet.xtls.Certificate 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_transport_internet_xtls_config_proto_init() } func file_transport_internet_xtls_config_proto_init() { if File_transport_internet_xtls_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_transport_internet_xtls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Certificate); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_transport_internet_xtls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_transport_internet_xtls_config_proto_rawDesc, NumEnums: 1, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_transport_internet_xtls_config_proto_goTypes, DependencyIndexes: file_transport_internet_xtls_config_proto_depIdxs, EnumInfos: file_transport_internet_xtls_config_proto_enumTypes, MessageInfos: file_transport_internet_xtls_config_proto_msgTypes, }.Build() File_transport_internet_xtls_config_proto = out.File file_transport_internet_xtls_config_proto_rawDesc = nil file_transport_internet_xtls_config_proto_goTypes = nil file_transport_internet_xtls_config_proto_depIdxs = nil } ================================================ FILE: transport/internet/xtls/config.proto ================================================ syntax = "proto3"; package v2ray.core.transport.internet.xtls; option csharp_namespace = "V2Ray.Core.Transport.Internet.Xtls"; option go_package = "v2ray.com/core/transport/internet/xtls"; option java_package = "com.v2ray.core.transport.internet.xtls"; option java_multiple_files = true; message Certificate { // XTLS certificate in x509 format. bytes Certificate = 1; // XTLS key in x509 format. bytes Key = 2; enum Usage { ENCIPHERMENT = 0; AUTHORITY_VERIFY = 1; AUTHORITY_ISSUE = 2; } Usage usage = 3; } message Config { // Whether or not to allow self-signed certificates. bool allow_insecure = 1; // Whether or not to allow insecure cipher suites. bool allow_insecure_ciphers = 5; // List of certificates to be served on server. repeated Certificate certificate = 2; // Override server name. string server_name = 3; // Lists of string as ALPN values. repeated string next_protocol = 4; // Whether or not to disable session (ticket) resumption. bool disable_session_resumption = 6; // If true, root certificates on the system will not be loaded for // verification. bool disable_system_root = 7; } ================================================ FILE: transport/internet/xtls/config_other.go ================================================ // +build !windows // +build !confonly package xtls import ( "crypto/x509" "sync" ) type rootCertsCache struct { sync.Mutex pool *x509.CertPool } func (c *rootCertsCache) load() (*x509.CertPool, error) { c.Lock() defer c.Unlock() if c.pool != nil { return c.pool, nil } pool, err := x509.SystemCertPool() if err != nil { return nil, err } c.pool = pool return pool, nil } var rootCerts rootCertsCache func (c *Config) getCertPool() (*x509.CertPool, error) { if c.DisableSystemRoot { return c.loadSelfCertPool() } if len(c.Certificate) == 0 { return rootCerts.load() } pool, err := x509.SystemCertPool() if err != nil { return nil, newError("system root").AtWarning().Base(err) } for _, cert := range c.Certificate { if !pool.AppendCertsFromPEM(cert.Certificate) { return nil, newError("append cert to root").AtWarning().Base(err) } } return pool, err } ================================================ FILE: transport/internet/xtls/config_test.go ================================================ package xtls_test import ( "crypto/x509" "testing" "time" xtls "github.com/xtls/go" "v2ray.com/core/common" "v2ray.com/core/common/protocol/tls/cert" . "v2ray.com/core/transport/internet/xtls" ) func TestCertificateIssuing(t *testing.T) { certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) certificate.Usage = Certificate_AUTHORITY_ISSUE c := &Config{ Certificate: []*Certificate{ certificate, }, } xtlsConfig := c.GetXTLSConfig() v2rayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ ServerName: "www.v2fly.org", }) common.Must(err) x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) common.Must(err) if !x509Cert.NotAfter.After(time.Now()) { t.Error("NotAfter: ", x509Cert.NotAfter) } } func TestExpiredCertificate(t *testing.T) { caCert := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign)) expiredCert := cert.MustGenerate(caCert, cert.NotAfter(time.Now().Add(time.Minute*-2)), cert.CommonName("www.v2fly.org"), cert.DNSNames("www.v2fly.org")) certificate := ParseCertificate(caCert) certificate.Usage = Certificate_AUTHORITY_ISSUE certificate2 := ParseCertificate(expiredCert) c := &Config{ Certificate: []*Certificate{ certificate, certificate2, }, } xtlsConfig := c.GetXTLSConfig() v2rayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ ServerName: "www.v2fly.org", }) common.Must(err) x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) common.Must(err) if !x509Cert.NotAfter.After(time.Now()) { t.Error("NotAfter: ", x509Cert.NotAfter) } } func TestInsecureCertificates(t *testing.T) { c := &Config{ AllowInsecureCiphers: true, } xtlsConfig := c.GetXTLSConfig() if len(xtlsConfig.CipherSuites) > 0 { t.Fatal("Unexpected tls cipher suites list: ", xtlsConfig.CipherSuites) } } func BenchmarkCertificateIssuing(b *testing.B) { certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) certificate.Usage = Certificate_AUTHORITY_ISSUE c := &Config{ Certificate: []*Certificate{ certificate, }, } xtlsConfig := c.GetXTLSConfig() lenCerts := len(xtlsConfig.Certificates) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ ServerName: "www.v2fly.org", }) delete(xtlsConfig.NameToCertificate, "www.v2fly.org") xtlsConfig.Certificates = xtlsConfig.Certificates[:lenCerts] } } ================================================ FILE: transport/internet/xtls/config_windows.go ================================================ // +build windows // +build !confonly package xtls import "crypto/x509" func (c *Config) getCertPool() (*x509.CertPool, error) { if c.DisableSystemRoot { return c.loadSelfCertPool() } return nil, nil } ================================================ FILE: transport/internet/xtls/errors.generated.go ================================================ package xtls import "v2ray.com/core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: transport/internet/xtls/xtls.go ================================================ // +build !confonly package xtls import ( xtls "github.com/xtls/go" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" ) //go:generate go run v2ray.com/core/common/errors/errorgen var ( _ buf.Writer = (*Conn)(nil) ) type Conn struct { *xtls.Conn } func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error { mb = buf.Compact(mb) mb, err := buf.WriteMultiBuffer(c, mb) buf.ReleaseMulti(mb) return err } func (c *Conn) HandshakeAddress() net.Address { if err := c.Handshake(); err != nil { return nil } state := c.ConnectionState() if state.ServerName == "" { return nil } return net.ParseAddress(state.ServerName) } // Client initiates a XTLS client handshake on the given connection. func Client(c net.Conn, config *xtls.Config) net.Conn { xtlsConn := xtls.Client(c, config) return &Conn{Conn: xtlsConn} } // Server initiates a XTLS server handshake on the given connection. func Server(c net.Conn, config *xtls.Config) net.Conn { xtlsConn := xtls.Server(c, config) return &Conn{Conn: xtlsConn} } ================================================ FILE: transport/link.go ================================================ package transport import "v2ray.com/core/common/buf" // Link is a utility for connecting between an inbound and an outbound proxy handler. type Link struct { Reader buf.Reader Writer buf.Writer } ================================================ FILE: transport/pipe/impl.go ================================================ package pipe import ( "errors" "io" "runtime" "sync" "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/signal" "v2ray.com/core/common/signal/done" ) type state byte const ( open state = iota closed errord ) type pipeOption struct { limit int32 // maximum buffer size in bytes discardOverflow bool } func (o *pipeOption) isFull(curSize int32) bool { return o.limit >= 0 && curSize > o.limit } type pipe struct { sync.Mutex data buf.MultiBuffer readSignal *signal.Notifier writeSignal *signal.Notifier done *done.Instance option pipeOption state state } var errBufferFull = errors.New("buffer full") var errSlowDown = errors.New("slow down") func (p *pipe) getState(forRead bool) error { switch p.state { case open: if !forRead && p.option.isFull(p.data.Len()) { return errBufferFull } return nil case closed: if !forRead { return io.ErrClosedPipe } if !p.data.IsEmpty() { return nil } return io.EOF case errord: return io.ErrClosedPipe default: panic("impossible case") } } func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) { p.Lock() defer p.Unlock() if err := p.getState(true); err != nil { return nil, err } data := p.data p.data = nil return data, nil } func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) { for { data, err := p.readMultiBufferInternal() if data != nil || err != nil { p.writeSignal.Signal() return data, err } select { case <-p.readSignal.Wait(): case <-p.done.Wait(): } } } func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { timer := time.NewTimer(d) defer timer.Stop() for { data, err := p.readMultiBufferInternal() if data != nil || err != nil { p.writeSignal.Signal() return data, err } select { case <-p.readSignal.Wait(): case <-p.done.Wait(): case <-timer.C: return nil, buf.ErrReadTimeout } } } func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error { p.Lock() defer p.Unlock() if err := p.getState(false); err != nil { return err } if p.data == nil { p.data = mb return nil } p.data, _ = buf.MergeMulti(p.data, mb) return errSlowDown } func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error { if mb.IsEmpty() { return nil } for { err := p.writeMultiBufferInternal(mb) if err == nil { p.readSignal.Signal() return nil } if err == errSlowDown { p.readSignal.Signal() // Yield current goroutine. Hopefully the reading counterpart can pick up the payload. runtime.Gosched() return nil } if err == errBufferFull && p.option.discardOverflow { buf.ReleaseMulti(mb) return nil } if err != errBufferFull { buf.ReleaseMulti(mb) p.readSignal.Signal() return err } select { case <-p.writeSignal.Wait(): case <-p.done.Wait(): return io.ErrClosedPipe } } } func (p *pipe) Close() error { p.Lock() defer p.Unlock() if p.state == closed || p.state == errord { return nil } p.state = closed common.Must(p.done.Close()) return nil } // Interrupt implements common.Interruptible. func (p *pipe) Interrupt() { p.Lock() defer p.Unlock() if p.state == closed || p.state == errord { return } p.state = errord if !p.data.IsEmpty() { buf.ReleaseMulti(p.data) p.data = nil } common.Must(p.done.Close()) } ================================================ FILE: transport/pipe/pipe.go ================================================ package pipe import ( "context" "v2ray.com/core/common/signal" "v2ray.com/core/common/signal/done" "v2ray.com/core/features/policy" ) // Option for creating new Pipes. type Option func(*pipeOption) // WithoutSizeLimit returns an Option for Pipe to have no size limit. func WithoutSizeLimit() Option { return func(opt *pipeOption) { opt.limit = -1 } } // WithSizeLimit returns an Option for Pipe to have the given size limit. func WithSizeLimit(limit int32) Option { return func(opt *pipeOption) { opt.limit = limit } } // DiscardOverflow returns an Option for Pipe to discard writes if full. func DiscardOverflow() Option { return func(opt *pipeOption) { opt.discardOverflow = true } } // OptionsFromContext returns a list of Options from context. func OptionsFromContext(ctx context.Context) []Option { var opt []Option bp := policy.BufferPolicyFromContext(ctx) if bp.PerConnection >= 0 { opt = append(opt, WithSizeLimit(bp.PerConnection)) } else { opt = append(opt, WithoutSizeLimit()) } return opt } // New creates a new Reader and Writer that connects to each other. func New(opts ...Option) (*Reader, *Writer) { p := &pipe{ readSignal: signal.NewNotifier(), writeSignal: signal.NewNotifier(), done: done.New(), option: pipeOption{ limit: -1, }, } for _, opt := range opts { opt(&(p.option)) } return &Reader{ pipe: p, }, &Writer{ pipe: p, } } ================================================ FILE: transport/pipe/pipe_test.go ================================================ package pipe_test import ( "errors" "io" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" "v2ray.com/core/common" "v2ray.com/core/common/buf" . "v2ray.com/core/transport/pipe" ) func TestPipeReadWrite(t *testing.T) { pReader, pWriter := New(WithSizeLimit(1024)) b := buf.New() b.WriteString("abcd") common.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b})) b2 := buf.New() b2.WriteString("efg") common.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b2})) rb, err := pReader.ReadMultiBuffer() common.Must(err) if r := cmp.Diff(rb.String(), "abcdefg"); r != "" { t.Error(r) } } func TestPipeInterrupt(t *testing.T) { pReader, pWriter := New(WithSizeLimit(1024)) payload := []byte{'a', 'b', 'c', 'd'} b := buf.New() b.Write(payload) common.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b})) pWriter.Interrupt() rb, err := pReader.ReadMultiBuffer() if err != io.ErrClosedPipe { t.Fatal("expect io.ErrClosePipe, but got ", err) } if !rb.IsEmpty() { t.Fatal("expect empty buffer, but got ", rb.Len()) } } func TestPipeClose(t *testing.T) { pReader, pWriter := New(WithSizeLimit(1024)) payload := []byte{'a', 'b', 'c', 'd'} b := buf.New() common.Must2(b.Write(payload)) common.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b})) common.Must(pWriter.Close()) rb, err := pReader.ReadMultiBuffer() common.Must(err) if rb.String() != string(payload) { t.Fatal("expect content ", string(payload), " but actually ", rb.String()) } rb, err = pReader.ReadMultiBuffer() if err != io.EOF { t.Fatal("expected EOF, but got ", err) } if !rb.IsEmpty() { t.Fatal("expect empty buffer, but got ", rb.String()) } } func TestPipeLimitZero(t *testing.T) { pReader, pWriter := New(WithSizeLimit(0)) bb := buf.New() common.Must2(bb.Write([]byte{'a', 'b'})) common.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{bb})) var errg errgroup.Group errg.Go(func() error { b := buf.New() b.Write([]byte{'c', 'd'}) return pWriter.WriteMultiBuffer(buf.MultiBuffer{b}) }) errg.Go(func() error { time.Sleep(time.Second) var container buf.MultiBufferContainer if err := buf.Copy(pReader, &container); err != nil { return err } if r := cmp.Diff(container.String(), "abcd"); r != "" { return errors.New(r) } return nil }) errg.Go(func() error { time.Sleep(time.Second * 2) return pWriter.Close() }) if err := errg.Wait(); err != nil { t.Error(err) } } func TestPipeWriteMultiThread(t *testing.T) { pReader, pWriter := New(WithSizeLimit(0)) var errg errgroup.Group for i := 0; i < 10; i++ { errg.Go(func() error { b := buf.New() b.WriteString("abcd") return pWriter.WriteMultiBuffer(buf.MultiBuffer{b}) }) } time.Sleep(time.Millisecond * 100) pWriter.Close() errg.Wait() b, err := pReader.ReadMultiBuffer() common.Must(err) if r := cmp.Diff(b[0].Bytes(), []byte{'a', 'b', 'c', 'd'}); r != "" { t.Error(r) } } func TestInterfaces(t *testing.T) { _ = (buf.Reader)(new(Reader)) _ = (buf.TimeoutReader)(new(Reader)) _ = (common.Interruptible)(new(Reader)) _ = (common.Interruptible)(new(Writer)) _ = (common.Closable)(new(Writer)) } func BenchmarkPipeReadWrite(b *testing.B) { reader, writer := New(WithoutSizeLimit()) a := buf.New() a.Extend(buf.Size) c := buf.MultiBuffer{a} b.ResetTimer() for i := 0; i < b.N; i++ { common.Must(writer.WriteMultiBuffer(c)) d, err := reader.ReadMultiBuffer() common.Must(err) c = d } } ================================================ FILE: transport/pipe/reader.go ================================================ package pipe import ( "time" "v2ray.com/core/common/buf" ) // Reader is a buf.Reader that reads content from a pipe. type Reader struct { pipe *pipe } // ReadMultiBuffer implements buf.Reader. func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) { return r.pipe.ReadMultiBuffer() } // ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise. func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) { return r.pipe.ReadMultiBufferTimeout(d) } // Interrupt implements common.Interruptible. func (r *Reader) Interrupt() { r.pipe.Interrupt() } ================================================ FILE: transport/pipe/writer.go ================================================ package pipe import ( "v2ray.com/core/common/buf" ) // Writer is a buf.Writer that writes data into a pipe. type Writer struct { pipe *pipe } // WriteMultiBuffer implements buf.Writer. func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error { return w.pipe.WriteMultiBuffer(mb) } // Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF. func (w *Writer) Close() error { return w.pipe.Close() } // Interrupt implements common.Interruptible. func (w *Writer) Interrupt() { w.pipe.Interrupt() } ================================================ FILE: v2ray.go ================================================ // +build !confonly package core import ( "context" "reflect" "sync" "v2ray.com/core/common" "v2ray.com/core/common/serial" "v2ray.com/core/features" "v2ray.com/core/features/dns" "v2ray.com/core/features/dns/localdns" "v2ray.com/core/features/inbound" "v2ray.com/core/features/outbound" "v2ray.com/core/features/policy" "v2ray.com/core/features/routing" "v2ray.com/core/features/stats" ) // Server is an instance of V2Ray. At any time, there must be at most one Server instance running. type Server interface { common.Runnable } // ServerType returns the type of the server. func ServerType() interface{} { return (*Instance)(nil) } type resolution struct { deps []reflect.Type callback interface{} } func getFeature(allFeatures []features.Feature, t reflect.Type) features.Feature { for _, f := range allFeatures { if reflect.TypeOf(f.Type()) == t { return f } } return nil } func (r *resolution) resolve(allFeatures []features.Feature) (bool, error) { var fs []features.Feature for _, d := range r.deps { f := getFeature(allFeatures, d) if f == nil { return false, nil } fs = append(fs, f) } callback := reflect.ValueOf(r.callback) var input []reflect.Value callbackType := callback.Type() for i := 0; i < callbackType.NumIn(); i++ { pt := callbackType.In(i) for _, f := range fs { if reflect.TypeOf(f).AssignableTo(pt) { input = append(input, reflect.ValueOf(f)) break } } } if len(input) != callbackType.NumIn() { panic("Can't get all input parameters") } var err error ret := callback.Call(input) errInterface := reflect.TypeOf((*error)(nil)).Elem() for i := len(ret) - 1; i >= 0; i-- { if ret[i].Type() == errInterface { v := ret[i].Interface() if v != nil { err = v.(error) } break } } return true, err } // Instance combines all functionalities in V2Ray. type Instance struct { access sync.Mutex features []features.Feature featureResolutions []resolution running bool ctx context.Context } func AddInboundHandler(server *Instance, config *InboundHandlerConfig) error { inboundManager := server.GetFeature(inbound.ManagerType()).(inbound.Manager) rawHandler, err := CreateObject(server, config) if err != nil { return err } handler, ok := rawHandler.(inbound.Handler) if !ok { return newError("not an InboundHandler") } if err := inboundManager.AddHandler(server.ctx, handler); err != nil { return err } return nil } func addInboundHandlers(server *Instance, configs []*InboundHandlerConfig) error { for _, inboundConfig := range configs { if err := AddInboundHandler(server, inboundConfig); err != nil { return err } } return nil } func AddOutboundHandler(server *Instance, config *OutboundHandlerConfig) error { outboundManager := server.GetFeature(outbound.ManagerType()).(outbound.Manager) rawHandler, err := CreateObject(server, config) if err != nil { return err } handler, ok := rawHandler.(outbound.Handler) if !ok { return newError("not an OutboundHandler") } if err := outboundManager.AddHandler(server.ctx, handler); err != nil { return err } return nil } func addOutboundHandlers(server *Instance, configs []*OutboundHandlerConfig) error { for _, outboundConfig := range configs { if err := AddOutboundHandler(server, outboundConfig); err != nil { return err } } return nil } // RequireFeatures is a helper function to require features from Instance in context. // See Instance.RequireFeatures for more information. func RequireFeatures(ctx context.Context, callback interface{}) error { v := MustFromContext(ctx) return v.RequireFeatures(callback) } // New returns a new V2Ray instance based on given configuration. // The instance is not started at this point. // To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional. func New(config *Config) (*Instance, error) { var server = &Instance{ctx: context.Background()} err, done := initInstanceWithConfig(config, server) if done { return nil, err } return server, nil } func NewWithContext(config *Config, ctx context.Context) (*Instance, error) { var server = &Instance{ctx: ctx} err, done := initInstanceWithConfig(config, server) if done { return nil, err } return server, nil } func initInstanceWithConfig(config *Config, server *Instance) (error, bool) { if config.Transport != nil { features.PrintDeprecatedFeatureWarning("global transport settings") } if err := config.Transport.Apply(); err != nil { return err, true } for _, appSettings := range config.App { settings, err := appSettings.GetInstance() if err != nil { return err, true } obj, err := CreateObject(server, settings) if err != nil { return err, true } if feature, ok := obj.(features.Feature); ok { if err := server.AddFeature(feature); err != nil { return err, true } } } essentialFeatures := []struct { Type interface{} Instance features.Feature }{ {dns.ClientType(), localdns.New()}, {policy.ManagerType(), policy.DefaultManager{}}, {routing.RouterType(), routing.DefaultRouter{}}, {stats.ManagerType(), stats.NoopManager{}}, } for _, f := range essentialFeatures { if server.GetFeature(f.Type) == nil { if err := server.AddFeature(f.Instance); err != nil { return err, true } } } if server.featureResolutions != nil { return newError("not all dependency are resolved."), true } if err := addInboundHandlers(server, config.Inbound); err != nil { return err, true } if err := addOutboundHandlers(server, config.Outbound); err != nil { return err, true } return nil, false } // Type implements common.HasType. func (s *Instance) Type() interface{} { return ServerType() } // Close shutdown the V2Ray instance. func (s *Instance) Close() error { s.access.Lock() defer s.access.Unlock() s.running = false var errors []interface{} for _, f := range s.features { if err := f.Close(); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return newError("failed to close all features").Base(newError(serial.Concat(errors...))) } return nil } // RequireFeatures registers a callback, which will be called when all dependent features are registered. // The callback must be a func(). All its parameters must be features.Feature. func (s *Instance) RequireFeatures(callback interface{}) error { callbackType := reflect.TypeOf(callback) if callbackType.Kind() != reflect.Func { panic("not a function") } var featureTypes []reflect.Type for i := 0; i < callbackType.NumIn(); i++ { featureTypes = append(featureTypes, reflect.PtrTo(callbackType.In(i))) } r := resolution{ deps: featureTypes, callback: callback, } if finished, err := r.resolve(s.features); finished { return err } s.featureResolutions = append(s.featureResolutions, r) return nil } // AddFeature registers a feature into current Instance. func (s *Instance) AddFeature(feature features.Feature) error { s.features = append(s.features, feature) if s.running { if err := feature.Start(); err != nil { newError("failed to start feature").Base(err).WriteToLog() } return nil } if s.featureResolutions == nil { return nil } var pendingResolutions []resolution for _, r := range s.featureResolutions { finished, err := r.resolve(s.features) if finished && err != nil { return err } if !finished { pendingResolutions = append(pendingResolutions, r) } } if len(pendingResolutions) == 0 { s.featureResolutions = nil } else if len(pendingResolutions) < len(s.featureResolutions) { s.featureResolutions = pendingResolutions } return nil } // GetFeature returns a feature of the given type, or nil if such feature is not registered. func (s *Instance) GetFeature(featureType interface{}) features.Feature { return getFeature(s.features, reflect.TypeOf(featureType)) } // Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown. // A V2Ray instance can be started only once. Upon closing, the instance is not guaranteed to start again. // // v2ray:api:stable func (s *Instance) Start() error { s.access.Lock() defer s.access.Unlock() s.running = true for _, f := range s.features { if err := f.Start(); err != nil { return err } } newError("V2Ray ", Version(), " started").AtWarning().WriteToLog() return nil } ================================================ FILE: v2ray_test.go ================================================ package core_test import ( "testing" proto "github.com/golang/protobuf/proto" . "v2ray.com/core" "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/proxyman" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/common/uuid" "v2ray.com/core/features/dns" "v2ray.com/core/features/dns/localdns" _ "v2ray.com/core/main/distro/all" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" ) func TestV2RayDependency(t *testing.T) { instance := new(Instance) wait := make(chan bool, 1) instance.RequireFeatures(func(d dns.Client) { if d == nil { t.Error("expected dns client fulfilled, but actually nil") } wait <- true }) instance.AddFeature(localdns.New()) <-wait } func TestV2RayClose(t *testing.T) { port := tcp.PickPort() userId := uuid.New() config := &Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, Inbound: []*InboundHandlerConfig{ { ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ PortRange: net.SinglePortRange(port), Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(0), NetworkList: &net.NetworkList{ Network: []net.Network{net.Network_TCP, net.Network_UDP}, }, }), }, }, Outbound: []*OutboundHandlerConfig{ { ProxySettings: serial.ToTypedMessage(&outbound.Config{ Receiver: []*protocol.ServerEndpoint{ { Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(0), User: []*protocol.User{ { Account: serial.ToTypedMessage(&vmess.Account{ Id: userId.String(), }), }, }, }, }, }), }, }, } cfgBytes, err := proto.Marshal(config) common.Must(err) server, err := StartInstance("protobuf", cfgBytes) common.Must(err) server.Close() }