├── 1 ├── .github └── workflows │ ├── Stop All Workflows.yaml │ ├── android-build-all.yaml │ ├── android-build-arm64.yaml │ ├── android-build-armv7.yaml │ ├── android-build-x86_64.yaml │ ├── dart.yml │ ├── ios-build.yaml │ ├── linux-build.yaml │ ├── mac-build copy.yaml │ ├── windows-build-Setup-cmd.yml │ ├── windows-build-Setup.yml │ └── windows-build.yml ├── .gitignore ├── .metadata ├── .sentry-native ├── 96697942-6dc9-4fd5-e899-18c41b0da851.run.lock ├── b221b550-270f-48e3-67d4-65b549f7c944.run.lock └── d5bced59-91f4-4c15-d704-bb7d3ae30d05.run.lock ├── .vscode └── launch.json ├── 1.iss ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle.kts │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── astral │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── launch_background.xml │ │ │ └── splash_logo.png │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle.kts ├── assets └── icon.ico ├── devtools_options.yaml ├── dlls ├── Ak.dll ├── Packet.dll ├── Packet.lib ├── arm64 │ ├── Packet.dll │ ├── Packet.lib │ └── wintun.dll ├── i686 │ ├── Packet.dll │ ├── Packet.lib │ └── wintun.dll └── wintun.dll ├── flutter_rust_bridge.yaml ├── fonts ├── MiSans-Demibold.ttf └── MiSans-Regular.ttf ├── integration_test └── simple_test.dart ├── lib ├── app.dart ├── fun │ ├── e_d_room.dart │ ├── net_astral_udp.dart │ ├── random_name.dart │ ├── reg.dart │ ├── route_fun.dart │ ├── show_add_room_dialog.dart │ ├── show_edit_room_dialog.dart │ ├── show_server_dialog.dart │ └── up.dart ├── k │ ├── app_s │ │ └── aps.dart │ ├── database │ │ └── app_data.dart │ ├── mod │ │ └── window_manager.dart │ ├── models │ │ ├── all_settings.dart │ │ ├── all_settings.g.dart │ │ ├── net_config.dart │ │ ├── net_config.g.dart │ │ ├── room.dart │ │ ├── room.g.dart │ │ ├── rule_group.dart │ │ ├── rule_group.g.dart │ │ ├── server_mod.dart │ │ ├── server_mod.g.dart │ │ ├── theme_settings.dart │ │ └── theme_settings.g.dart │ ├── models_mod │ │ ├── all_settings_cz.dart │ │ ├── net_config_cz.dart │ │ ├── room_cz.dart │ │ ├── rule_group_cz.dart │ │ ├── server_cz.dart │ │ └── theme_settings_cz.dart │ └── navigtion.dart ├── main.dart ├── screens │ ├── home_page.dart │ ├── main_screen.dart │ ├── room_page.dart │ ├── server_page.dart │ ├── settings_page.dart │ └── user_page.dart ├── src │ └── rust │ │ ├── api │ │ ├── firewall.dart │ │ ├── hops.dart │ │ └── simple.dart │ │ ├── frb_generated.dart │ │ ├── frb_generated.io.dart │ │ └── frb_generated.web.dart └── wid │ ├── all_user_card.dart │ ├── bottom_nav.dart │ ├── canvas_jump.dart │ ├── home │ ├── about_home.dart │ ├── bugcs.dart │ ├── connect_button.dart │ ├── contributors.dart │ ├── servers_home.dart │ ├── udp_log.dart │ ├── user_ip.dart │ └── virtual_ip.dart │ ├── home_box.dart │ ├── left_nav.dart │ ├── mini_user_card.dart │ ├── room_card.dart │ ├── server_card.dart │ ├── status_bar.dart │ ├── theme_selector.dart │ └── windows_controls.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake └── runner │ ├── CMakeLists.txt │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── pubspec.lock ├── pubspec.yaml ├── rust ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── easytier │ ├── Cargo.toml │ ├── build.rs │ ├── locales │ │ └── app.yml │ ├── src │ │ ├── arch │ │ │ ├── mod.rs │ │ │ └── windows.rs │ │ ├── common │ │ │ ├── compressor.rs │ │ │ ├── config.rs │ │ │ ├── constants.rs │ │ │ ├── defer.rs │ │ │ ├── dns.rs │ │ │ ├── error.rs │ │ │ ├── global_ctx.rs │ │ │ ├── ifcfg │ │ │ │ ├── darwin.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── netlink.rs │ │ │ │ ├── route.rs │ │ │ │ └── windows.rs │ │ │ ├── mod.rs │ │ │ ├── netns.rs │ │ │ ├── network.rs │ │ │ ├── scoped_task.rs │ │ │ ├── stun.rs │ │ │ └── stun_codec_ext.rs │ │ ├── connector │ │ │ ├── direct.rs │ │ │ ├── dns_connector.rs │ │ │ ├── http_connector.rs │ │ │ ├── manual.rs │ │ │ ├── mod.rs │ │ │ └── udp_hole_punch │ │ │ │ ├── both_easy_sym.rs │ │ │ │ ├── common.rs │ │ │ │ ├── cone.rs │ │ │ │ ├── mod.rs │ │ │ │ └── sym_to_cone.rs │ │ ├── easytier-cli.rs │ │ ├── easytier-core.rs │ │ ├── gateway │ │ │ ├── fast_socks5 │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── server.rs │ │ │ │ └── util │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── stream.rs │ │ │ │ │ └── target_addr.rs │ │ │ ├── icmp_proxy.rs │ │ │ ├── ip_reassembler.rs │ │ │ ├── kcp_proxy.rs │ │ │ ├── mod.rs │ │ │ ├── socks5.rs │ │ │ ├── tcp_proxy.rs │ │ │ ├── tokio_smoltcp │ │ │ │ ├── channel_device.rs │ │ │ │ ├── device.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── reactor.rs │ │ │ │ ├── socket.rs │ │ │ │ └── socket_allocator.rs │ │ │ └── udp_proxy.rs │ │ ├── instance │ │ │ ├── dns_server │ │ │ │ ├── client_instance.rs │ │ │ │ ├── config.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── runner.rs │ │ │ │ ├── server.rs │ │ │ │ ├── server_instance.rs │ │ │ │ ├── system_config │ │ │ │ │ ├── darwin.rs │ │ │ │ │ ├── linux.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── windows.rs │ │ │ │ └── tests.rs │ │ │ ├── instance.rs │ │ │ ├── listeners.rs │ │ │ ├── mod.rs │ │ │ └── virtual_nic.rs │ │ ├── launcher.rs │ │ ├── lib.rs │ │ ├── peer_center │ │ │ ├── instance.rs │ │ │ ├── mod.rs │ │ │ └── server.rs │ │ ├── peers │ │ │ ├── encrypt │ │ │ │ ├── aes_gcm.rs │ │ │ │ ├── mod.rs │ │ │ │ └── ring_aes_gcm.rs │ │ │ ├── foreign_network_client.rs │ │ │ ├── foreign_network_manager.rs │ │ │ ├── mod.rs │ │ │ ├── peer.rs │ │ │ ├── peer_conn.rs │ │ │ ├── peer_conn_ping.rs │ │ │ ├── peer_manager.rs │ │ │ ├── peer_map.rs │ │ │ ├── peer_ospf_route.rs │ │ │ ├── peer_rpc.rs │ │ │ ├── peer_rpc_service.rs │ │ │ ├── peer_task.rs │ │ │ ├── route_trait.rs │ │ │ ├── rpc_service.rs │ │ │ └── tests.rs │ │ ├── proto │ │ │ ├── cli.proto │ │ │ ├── cli.rs │ │ │ ├── common.proto │ │ │ ├── common.rs │ │ │ ├── error.proto │ │ │ ├── error.rs │ │ │ ├── magic_dns.proto │ │ │ ├── magic_dns.rs │ │ │ ├── mod.rs │ │ │ ├── peer_rpc.proto │ │ │ ├── peer_rpc.rs │ │ │ ├── rpc_impl │ │ │ │ ├── bidirect.rs │ │ │ │ ├── client.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── packet.rs │ │ │ │ ├── server.rs │ │ │ │ ├── service_registry.rs │ │ │ │ └── standalone.rs │ │ │ ├── rpc_types │ │ │ │ ├── __rt.rs │ │ │ │ ├── controller.rs │ │ │ │ ├── descriptor.rs │ │ │ │ ├── error.rs │ │ │ │ ├── handler.rs │ │ │ │ └── mod.rs │ │ │ ├── tests.proto │ │ │ ├── tests.rs │ │ │ ├── web.proto │ │ │ └── web.rs │ │ ├── tests │ │ │ ├── mod.rs │ │ │ └── three_node.rs │ │ ├── tunnel │ │ │ ├── buf.rs │ │ │ ├── common.rs │ │ │ ├── filter.rs │ │ │ ├── insecure_tls.rs │ │ │ ├── mod.rs │ │ │ ├── mpsc.rs │ │ │ ├── packet_def.rs │ │ │ ├── quic.rs │ │ │ ├── ring.rs │ │ │ ├── stats.rs │ │ │ ├── tcp.rs │ │ │ ├── udp.rs │ │ │ ├── websocket.rs │ │ │ └── wireguard.rs │ │ ├── utils.rs │ │ ├── vpn_portal │ │ │ ├── mod.rs │ │ │ └── wireguard.rs │ │ └── web_client │ │ │ ├── controller.rs │ │ │ ├── mod.rs │ │ │ └── session.rs │ └── third_party │ │ ├── Packet.dll │ │ ├── Packet.lib │ │ ├── arm64 │ │ ├── Packet.dll │ │ ├── Packet.lib │ │ └── wintun.dll │ │ ├── i686 │ │ ├── Packet.dll │ │ ├── Packet.lib │ │ └── wintun.dll │ │ └── wintun.dll └── src │ ├── api │ ├── firewall.rs │ ├── hops.rs │ ├── mod.rs │ └── simple.rs │ ├── frb_generated.rs │ └── lib.rs ├── rust_builder ├── .gitignore ├── README.md ├── android │ ├── .gitignore │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ └── AndroidManifest.xml ├── cargokit │ ├── .gitignore │ ├── LICENSE │ ├── README │ ├── build_pod.sh │ ├── build_tool │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── bin │ │ │ └── build_tool.dart │ │ ├── lib │ │ │ ├── build_tool.dart │ │ │ └── src │ │ │ │ ├── android_environment.dart │ │ │ │ ├── artifacts_provider.dart │ │ │ │ ├── build_cmake.dart │ │ │ │ ├── build_gradle.dart │ │ │ │ ├── build_pod.dart │ │ │ │ ├── build_tool.dart │ │ │ │ ├── builder.dart │ │ │ │ ├── cargo.dart │ │ │ │ ├── crate_hash.dart │ │ │ │ ├── environment.dart │ │ │ │ ├── logging.dart │ │ │ │ ├── options.dart │ │ │ │ ├── precompile_binaries.dart │ │ │ │ ├── rustup.dart │ │ │ │ ├── target.dart │ │ │ │ ├── util.dart │ │ │ │ └── verify_binaries.dart │ │ ├── pubspec.lock │ │ └── pubspec.yaml │ ├── cmake │ │ ├── cargokit.cmake │ │ └── resolve_symlinks.ps1 │ ├── gradle │ │ └── plugin.gradle │ ├── run_build_tool.cmd │ └── run_build_tool.sh ├── ios │ ├── Classes │ │ └── dummy_file.c │ └── rust_lib_astral.podspec ├── linux │ └── CMakeLists.txt ├── macos │ ├── Classes │ │ └── dummy_file.c │ └── rust_lib_astral.podspec ├── pubspec.yaml └── windows │ ├── .gitignore │ └── CMakeLists.txt ├── test_driver └── integration_test.dart ├── vpn_service_plugin ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── plugin │ │ │ └── vpn_service_plugin │ │ │ ├── TauriVpnService.kt │ │ │ └── VpnServicePlugin.kt │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── example │ │ └── vpn_service_plugin │ │ └── VpnServicePluginTest.kt ├── example │ ├── .gitignore │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle.kts │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── vpn_service_plugin_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values-night │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle.kts │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ └── settings.gradle.kts │ ├── integration_test │ │ └── plugin_integration_test.dart │ ├── lib │ │ └── main.dart │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ └── widget_test.dart ├── lib │ ├── vpn_service_plugin.dart │ ├── vpn_service_plugin_method_channel.dart │ └── vpn_service_plugin_platform_interface.dart └── pubspec.yaml ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /1: -------------------------------------------------------------------------------- 1 | Theme.of(context).colorScheme.primary // 主要颜色(Primary) 2 | Theme.of(context).colorScheme.onPrimary // 在主要颜色上的颜色(On Primary) 3 | Theme.of(context).colorScheme.primaryContainer // 主要容器颜色(Primary Container) 4 | Theme.of(context).colorScheme.onPrimaryContainer // 在主要容器上的颜色(On Primary Container) 5 | 6 | Theme.of(context).colorScheme.secondary // 次要颜色(Secondary) 7 | Theme.of(context).colorScheme.onSecondary // 在次要颜色上的颜色(On Secondary) 8 | Theme.of(context).colorScheme.secondaryContainer // 次要容器颜色(Secondary Container) 9 | Theme.of(context).colorScheme.onSecondaryContainer // 在次要容器上的颜色(On Secondary Container) 10 | 11 | Theme.of(context).colorScheme.tertiary // 第三颜色(Tertiary) 12 | Theme.of(context).colorScheme.onTertiary // 在第三颜色上的颜色(On Tertiary) 13 | Theme.of(context).colorScheme.tertiaryContainer // 第三容器颜色(Tertiary Container) 14 | Theme.of(context).colorScheme.onTertiaryContainer // 在第三容器上的颜色(On Tertiary Container) 15 | 16 | Theme.of(context).colorScheme.error // 错误颜色(Error) 17 | Theme.of(context).colorScheme.onError // 在错误颜色上的颜色(On Error) 18 | Theme.of(context).colorScheme.errorContainer // 错误容器颜色(Error Container) 19 | Theme.of(context).colorScheme.onErrorContainer // 在错误容器上的颜色(On Error Container) 20 | 21 | Theme.of(context).colorScheme.background // 背景颜色(Background) 22 | Theme.of(context).colorScheme.onBackground // 在背景上的颜色(On Background) 23 | 24 | Theme.of(context).colorScheme.surface // 表面颜色(Surface) 25 | Theme.of(context).colorScheme.onSurface // 在表面上的颜色(On Surface) 26 | Theme.of(context).colorScheme.surfaceVariant // 表面变体颜色(Surface Variant) 27 | Theme.of(context).colorScheme.onSurfaceVariant // 在表面变体上的颜色(On Surface Variant) 28 | Theme.of(context).colorScheme.surfaceTint // 表面色调(Surface Tint) 29 | 30 | Theme.of(context).colorScheme.outline // 轮廓颜色(Outline) 31 | Theme.of(context).colorScheme.shadow // 阴影颜色(Shadow) 32 | 33 | Theme.of(context).colorScheme.inverseSurface // 反转表面颜色(Inverse Surface) 34 | Theme.of(context).colorScheme.onInverseSurface // 在反转表面上的颜色(On Inverse Surface) 35 | Theme.of(context).colorScheme.inversePrimary // 反转主要颜色(Inverse Primary) -------------------------------------------------------------------------------- /.github/workflows/Stop All Workflows.yaml: -------------------------------------------------------------------------------- 1 | name: Stop All Workflows 2 | 3 | on: 4 | workflow_dispatch: # 手动触发 5 | 6 | permissions: 7 | actions: write # 需要 actions 的写入权限 8 | contents: read # 需要读取仓库内容的权限 9 | 10 | jobs: 11 | stop-workflows: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Stop all running workflows 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 使用默认的 GITHUB_TOKEN 20 | run: | 21 | # 获取当前仓库的所有正在运行的工作流 22 | RUNNING_WORKFLOWS=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ 23 | -H "Accept: application/vnd.github.v3+json" \ 24 | "https://api.github.com/repos/${{ github.repository }}/actions/runs?status=in_progress" | \ 25 | jq -r '.workflow_runs[] | select(.status == "in_progress") | .id') 26 | 27 | # 逐个取消工作流 28 | for RUN_ID in $RUNNING_WORKFLOWS; do 29 | echo "Cancelling workflow run ID: $RUN_ID" 30 | curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" \ 31 | -H "Accept: application/vnd.github.v3+json" \ 32 | "https://api.github.com/repos/${{ github.repository }}/actions/runs/$RUN_ID/cancel" 33 | done 34 | -------------------------------------------------------------------------------- /.github/workflows/linux-build.yaml: -------------------------------------------------------------------------------- 1 | name: 🐧 构建 Linux 应用 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | # tags: 7 | # - 'v*' # 当推送版本标签时触发 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | packages: write 15 | 16 | steps: 17 | - name: 🛠️ 检出代码仓库 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | lfs: true 22 | 23 | - name: 🦀 设置 Rust 工具链 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | override: true 28 | 29 | - name: 📦 安装系统依赖 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get install -y build-essential ninja-build libgtk-3-dev 33 | sudo apt-get install -y clang cmake pkg-config 34 | sudo apt-get install -y liblzma-dev 35 | sudo apt-get install -y libsecret-1-dev libjsoncpp-dev 36 | sudo apt-get install -y protobuf-compiler 37 | # 只安装 ayatana 版本的依赖 38 | sudo apt-get install -y libayatana-appindicator3-dev 39 | # 移除冲突的包 40 | # sudo apt-get install -y libappindicator3-dev 41 | 42 | - name: 🐦 安装 Flutter 43 | run: | 44 | git clone https://github.com/flutter/flutter.git -b stable $HOME/flutter 45 | echo "$HOME/flutter/bin" >> $GITHUB_PATH 46 | export PATH="$HOME/flutter/bin:$PATH" 47 | chmod +x $HOME/flutter/bin/flutter 48 | flutter --version 49 | 50 | - name: 🎯 启用 Linux 桌面支持 51 | run: | 52 | flutter config --enable-linux-desktop 53 | flutter create --platforms=linux . 54 | 55 | - name: 📚 安装 Flutter 依赖 56 | run: | 57 | flutter pub get 58 | 59 | - name: 🛠️ 构建 Linux 应用 60 | run: | 61 | flutter build linux --release 62 | 63 | - name: 📦 打包应用 64 | run: | 65 | mkdir -p release 66 | cd build/linux/x64/release/bundle 67 | tar -czf $GITHUB_WORKSPACE/release/astral-linux-x64.tar.gz * 68 | echo "打包完成,检查文件大小:" 69 | ls -lh $GITHUB_WORKSPACE/release/ 70 | 71 | - name: 📤 上传构建产物 72 | uses: actions/upload-artifact@v4 73 | with: 74 | name: linux-release 75 | path: release/astral-linux-x64.tar.gz 76 | retention-days: 7 -------------------------------------------------------------------------------- /.github/workflows/mac-build copy.yaml: -------------------------------------------------------------------------------- 1 | name: 🍎 构建 macOS 应用 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | # tags: 7 | # - 'v*' # 当推送版本标签时触发 8 | 9 | jobs: 10 | build: 11 | runs-on: self-hosted # 更改为支持 M1 的运行器 12 | permissions: 13 | contents: write 14 | packages: write 15 | 16 | steps: 17 | - name: 🛠️ 检出代码仓库 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | lfs: true 22 | 23 | - name: 🦀 设置 Rust 工具链 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | override: true 28 | target: aarch64-apple-darwin # 添加 ARM 架构支持 29 | 30 | - name: 📦 安装系统依赖 31 | run: | 32 | brew update 33 | brew install cmake ninja 34 | brew install protobuf 35 | brew install ruby 36 | brew install cocoapods 37 | export LDFLAGS="-L/opt/homebrew/opt/ruby/lib" 38 | export CPPFLAGS="-I/opt/homebrew/opt/ruby/include" 39 | 40 | - name: 🐦 安装 Flutter 41 | run: | 42 | [ ! -d "$HOME/flutter" ] && git clone https://github.com/flutter/flutter.git -b stable $HOME/flutter 43 | echo "$HOME/flutter/bin" >> $GITHUB_PATH 44 | export PATH="$HOME/flutter/bin:$PATH" 45 | chmod +x $HOME/flutter/bin/flutter 46 | flutter --version 47 | 48 | - name: 🎯 启用 macOS 桌面支持 49 | run: | 50 | flutter config --enable-macos-desktop 51 | flutter create --platforms=macos . 52 | 53 | - name: 📚 安装 Flutter 依赖 54 | run: | 55 | flutter pub get 56 | 57 | - name: 🛠️ 构建 macOS 应用 58 | run: | 59 | flutter build macos --release --dart-define=FLUTTER_APP_ARCH=arm64 # 指定 ARM64 平台 60 | 61 | - name: 📦 打包应用 62 | run: | 63 | mkdir -p release 64 | cd build/macos/Build/Products/Release 65 | # 创建 DMG 文件 66 | hdiutil create -volname "Astral" -srcfolder astral.app -ov -format UDZO $GITHUB_WORKSPACE/release/astral-macos-arm64.dmg 67 | echo "打包完成,检查文件大小:" 68 | ls -lh $GITHUB_WORKSPACE/release/ 69 | 70 | - name: 📤 上传构建产物 71 | uses: actions/upload-artifact@v4 72 | with: 73 | name: macos-arm64-release 74 | path: release/astral-macos-arm64.dmg 75 | retention-days: 7 76 | -------------------------------------------------------------------------------- /.github/workflows/windows-build.yml: -------------------------------------------------------------------------------- 1 | name: 🪟 构建 Windows 应用 2 | 3 | on: 4 | workflow_dispatch: 5 | push: # 每次 push 到仓库时触发 6 | tags: 7 | - 'v*' # 指定触发分支 8 | 9 | jobs: 10 | build: 11 | runs-on: windows-latest 12 | 13 | # 调整 GITHUB_TOKEN 的权限 14 | permissions: 15 | contents: write # 允许写入仓库内容(上传文件) 16 | packages: write 17 | 18 | steps: 19 | - name: 🛠️ 检出代码仓库 20 | uses: actions/checkout@v4 21 | 22 | # 安装Flutter 23 | - name: 🐦 安装Flutter 24 | run: | 25 | echo "正在克隆Flutter稳定分支..." 26 | git clone https://github.com/flutter/flutter.git --branch 3.29.3 $env:GITHUB_WORKSPACE/flutter --depth 1 27 | 28 | echo "正在将Flutter添加到PATH..." 29 | echo "$env:GITHUB_WORKSPACE/flutter/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 30 | 31 | echo "Flutter版本验证:" 32 | & "$env:GITHUB_WORKSPACE/flutter/bin/flutter.bat" --version 33 | shell: pwsh 34 | 35 | # 安装 Flutter 依赖 36 | - name: 📦 安装 Flutter 依赖 37 | run: | 38 | echo "正在获取 Flutter 依赖..." 39 | flutter pub get 40 | echo "Flutter 依赖安装完成!" 41 | shell: pwsh 42 | 43 | # 编译 Flutter Windows 应用 44 | - name: 🛠️ 编译 Flutter Windows 应用 45 | run: | 46 | echo "正在编译 Flutter Windows Release 版本..." 47 | flutter build windows --release 48 | echo "Flutter Windows Release 编译完成!" 49 | shell: pwsh 50 | 51 | 52 | # 复制 DLL 文件到输出目录 53 | - name: 📂 复制 DLL 文件到 Release 目录 54 | run: | 55 | echo "正在复制 DLL 文件到 Release 目录..." 56 | $dllSourceDir = "$env:GITHUB_WORKSPACE\dlls" 57 | $releaseDir = "$env:GITHUB_WORKSPACE\build\windows\x64\runner\Release" 58 | Copy-Item -Path "$dllSourceDir\*" -Destination $releaseDir -Recurse -Force 59 | echo "DLL 文件已复制到 $releaseDir!" 60 | 61 | # 压缩 Release 目录 62 | - name: 📦 压缩 Release 目录 63 | run: | 64 | echo "正在压缩 Release 目录..." 65 | $releaseDir = "$env:GITHUB_WORKSPACE\build\windows\x64\runner\Release" 66 | $zipFile = "$env:GITHUB_WORKSPACE\astral-windows.zip" 67 | Compress-Archive -Path "$releaseDir\*" -DestinationPath $zipFile -Force 68 | echo "Release 目录已压缩为 $zipFile!" 69 | 70 | # 上传压缩后的 Release 目录作为 Artifact 71 | - name: 📤 上传压缩后的 Release 目录 72 | uses: actions/upload-artifact@v4 73 | with: 74 | name: release 75 | path: ${{ github.workspace }}/astral-windows.zip 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | 47 | # 添加至.gitignore 48 | .env 49 | *.env 50 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 17 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 18 | - platform: android 19 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 20 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 21 | - platform: linux 22 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 23 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 24 | - platform: web 25 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 26 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 27 | - platform: windows 28 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 29 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 30 | 31 | # User provided section 32 | 33 | # List of Local paths (relative to this file) that should be 34 | # ignored by the migrate tool. 35 | # 36 | # Files that are not part of the templates will be ignored by default. 37 | unmanaged_files: 38 | - 'lib/main.dart' 39 | - 'ios/Runner.xcodeproj/project.pbxproj' 40 | -------------------------------------------------------------------------------- /.sentry-native/96697942-6dc9-4fd5-e899-18c41b0da851.run.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/.sentry-native/96697942-6dc9-4fd5-e899-18c41b0da851.run.lock -------------------------------------------------------------------------------- /.sentry-native/b221b550-270f-48e3-67d4-65b549f7c944.run.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/.sentry-native/b221b550-270f-48e3-67d4-65b549f7c944.run.lock -------------------------------------------------------------------------------- /.sentry-native/d5bced59-91f4-4c15-d704-bb7d3ae30d05.run.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/.sentry-native/d5bced59-91f4-4c15-d704-bb7d3ae30d05.run.lock -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter (x86)", 9 | "request": "launch", 10 | "type": "dart", 11 | "flutterMode": "debug", 12 | "args": [ 13 | "--no-build", 14 | "--use-application-binary=build/app/outputs/flutter-apk/app-debug.apk" 15 | ] 16 | }, 17 | { 18 | "name": "Flutter", 19 | "request": "launch", 20 | "type": "dart", 21 | "flutterMode": "profile" 22 | }, 23 | { 24 | "name": "Flutter Run", 25 | "request": "launch", 26 | "type": "dart", 27 | "program": "lib/main.dart", 28 | "args": ["run"] 29 | }, 30 | { 31 | "name": "fln2n", 32 | "request": "launch", 33 | "type": "dart" 34 | }, 35 | { 36 | "name": "fln2n (profile mode)", 37 | "request": "launch", 38 | "type": "dart", 39 | "flutterMode": "profile" 40 | }, 41 | { 42 | "name": "fln2n (release mode)", 43 | "request": "launch", 44 | "type": "dart", 45 | "flutterMode": "release" 46 | }, 47 | { 48 | "name": "rust_builder", 49 | "cwd": "rust_builder", 50 | "request": "launch", 51 | "type": "dart" 52 | }, 53 | { 54 | "name": "rust_builder (profile mode)", 55 | "cwd": "rust_builder", 56 | "request": "launch", 57 | "type": "dart", 58 | "flutterMode": "profile" 59 | }, 60 | { 61 | "name": "rust_builder (release mode)", 62 | "cwd": "rust_builder", 63 | "request": "launch", 64 | "type": "dart", 65 | "flutterMode": "release" 66 | }, 67 | { 68 | "name": "build_tool", 69 | "cwd": "rust_builder\\cargokit\\build_tool", 70 | "request": "launch", 71 | "type": "dart" 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astral [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ldoubil/astral) 2 | 3 | ### 重要声明 4 | 5 | ⚠️ 本软件仅供学习和非商业用途使用 6 | - ❌ 禁止商业使用 - 本软件不得用于任何商业目的 7 | - ❌ 禁止修改软件名称 - 不得更改 "Astral" 软件名称试图混淆以及二次售卖 8 | - ✅ 保留原作者信息 - 必须保留所有原作者版权信息 9 | - ✅ 开源协议 - 遵循开源许可证条款 10 | 11 | ## 项目简介 12 | 13 | Astral 是一个基于 EasyTier 的跨平台网络应用,提供简单易用的 P2P 网络连接和 VPN 服务。通过 Flutter 构建的现代化界面,让用户能够轻松创建和管理虚拟网络。 14 | 15 | ## 主要特性 16 | 17 | - 🌐 **P2P 网络连接** - 基于 EasyTier 的去中心化网络架构 18 | - 🔒 **VPN 服务** - 安全的虚拟专用网络连接 19 | - 🖥️ **跨平台支持** - 支持 Windows、macOS、Linux、Android 和 iOS 20 | - 🎨 **现代化界面** - 基于 Flutter 的美观用户界面 21 | - ⚡ **高性能** - Rust 后端确保高效的网络处理 22 | - 🔧 **易于配置** - 简单的房间和服务器管理 23 | 24 | ## 技术栈 25 | 26 | - **前端**: Flutter (Dart) 27 | - **后端**: Rust (EasyTier) 28 | - **网络**: P2P、VPN、WireGuard 29 | - **平台**: Windows、macOS、Linux、Android、iOS 30 | 31 | ## 功能说明 32 | 33 | - 房间管理 - 创建和加入网络房间 34 | - 服务器配置 - 配置和管理网络服务器 35 | - 用户管理 - 查看和管理网络用户 36 | - 网络设置 - 自定义网络参数和配置 37 | 38 | ## 联系方式 39 | 40 | - QQ群:808169040 41 | - 项目文档: 42 | 43 | 44 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | analyzer: 11 | errors: 12 | collection_methods_unrelated_type: ignore 13 | file_names: ignore 14 | non_constant_identifier_names: ignore 15 | include: package:flutter_lints/flutter.yaml 16 | 17 | linter: 18 | # The lint rules applied to this project can be customized in the 19 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 20 | # included above or to enable additional rules. A list of all available lints 21 | # and their documentation is published at https://dart.dev/lints. 22 | # 23 | # Instead of disabling a lint rule for the entire project in the 24 | # section below, it can also be suppressed for a single line of code 25 | # or a specific dart file by using the `// ignore: name_of_lint` and 26 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 27 | # producing the lint. 28 | rules: 29 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 30 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 31 | 32 | # Additional information about this file can be found at 33 | # https://dart.dev/guides/language/analysis-options 34 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | import java.io.FileInputStream 3 | 4 | plugins { 5 | id("com.android.application") 6 | id("kotlin-android") 7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 8 | id("dev.flutter.flutter-gradle-plugin") 9 | } 10 | 11 | val keystoreProperties = Properties() 12 | val keystorePropertiesFile = rootProject.file("key.properties") 13 | if (keystorePropertiesFile.exists()) { 14 | keystoreProperties.load(FileInputStream(keystorePropertiesFile)) 15 | } 16 | 17 | android { 18 | namespace = "com.kevin.astral" 19 | compileSdk = flutter.compileSdkVersion 20 | // ndkVersion = flutter.ndkVersion 21 | ndkVersion = "27.2.12479018" 22 | 23 | compileOptions { 24 | sourceCompatibility = JavaVersion.VERSION_11 25 | targetCompatibility = JavaVersion.VERSION_11 26 | // 启用 core library desugaring 27 | isCoreLibraryDesugaringEnabled = true 28 | } 29 | 30 | kotlinOptions { 31 | jvmTarget = JavaVersion.VERSION_11.toString() 32 | } 33 | // 添加 buildFeatures 配置 34 | buildFeatures { 35 | buildConfig = true 36 | } 37 | 38 | defaultConfig { 39 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 40 | applicationId = "com.kevin.astral" 41 | // You can update the following values to match your application needs. 42 | // For more information, see: https://flutter.dev/to/review-gradle-config. 43 | minSdk = flutter.minSdkVersion 44 | targetSdk = flutter.targetSdkVersion 45 | versionCode = flutter.versionCode 46 | versionName = flutter.versionName 47 | } 48 | 49 | signingConfigs { 50 | create("release") { 51 | keyAlias = keystoreProperties["keyAlias"] as String 52 | keyPassword = keystoreProperties["keyPassword"] as String 53 | storeFile = keystoreProperties["storeFile"]?.let { file(it) } 54 | storePassword = keystoreProperties["storePassword"] as String 55 | } 56 | } 57 | 58 | buildTypes { 59 | release { 60 | signingConfig = signingConfigs.getByName("release") 61 | // signingConfig = signingConfigs.getByName("debug") 62 | 63 | } 64 | } 65 | 66 | } 67 | 68 | // 添加 dependencies 块 69 | dependencies { 70 | coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") 71 | } 72 | 73 | flutter { 74 | source = "../.." 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/astral/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kevin.astral 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/drawable/splash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/assets/icon.ico -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /dlls/Ak.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/Ak.dll -------------------------------------------------------------------------------- /dlls/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/Packet.dll -------------------------------------------------------------------------------- /dlls/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/Packet.lib -------------------------------------------------------------------------------- /dlls/arm64/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/arm64/Packet.dll -------------------------------------------------------------------------------- /dlls/arm64/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/arm64/Packet.lib -------------------------------------------------------------------------------- /dlls/arm64/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/arm64/wintun.dll -------------------------------------------------------------------------------- /dlls/i686/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/i686/Packet.dll -------------------------------------------------------------------------------- /dlls/i686/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/i686/Packet.lib -------------------------------------------------------------------------------- /dlls/i686/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/i686/wintun.dll -------------------------------------------------------------------------------- /dlls/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/dlls/wintun.dll -------------------------------------------------------------------------------- /flutter_rust_bridge.yaml: -------------------------------------------------------------------------------- 1 | rust_input: crate::api 2 | rust_root: rust/ 3 | dart_output: lib/src/rust -------------------------------------------------------------------------------- /fonts/MiSans-Demibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/fonts/MiSans-Demibold.ttf -------------------------------------------------------------------------------- /fonts/MiSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/fonts/MiSans-Regular.ttf -------------------------------------------------------------------------------- /integration_test/simple_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/app.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:astral/src/rust/frb_generated.dart'; 4 | import 'package:integration_test/integration_test.dart'; 5 | 6 | void main() { 7 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 8 | setUpAll(() async => await RustLib.init()); 9 | testWidgets('Can call rust function', (WidgetTester tester) async { 10 | await tester.pumpWidget(const KevinApp()); 11 | expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/fun/net_astral_udp.dart'; 2 | import 'package:astral/screens/main_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:astral/k/app_s/aps.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | // 仅在桌面平台导入系统托盘 7 | 8 | class KevinApp extends StatefulWidget { 9 | const KevinApp({super.key}); 10 | @override 11 | State createState() => _KevinAppState(); 12 | } 13 | 14 | class _KevinAppState extends State { 15 | final _aps = Aps(); 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | getIpv4AndIpV6Addresses(); 21 | } 22 | 23 | @override 24 | void dispose() { 25 | super.dispose(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return MaterialApp( 31 | localizationsDelegates: const [ 32 | // 添加国际化支持 33 | GlobalMaterialLocalizations.delegate, 34 | GlobalWidgetsLocalizations.delegate, 35 | GlobalCupertinoLocalizations.delegate, 36 | ], 37 | // Insert this line 38 | supportedLocales: const [Locale("zh", "CN"), Locale("en", "US")], 39 | theme: ThemeData( 40 | useMaterial3: true, 41 | colorSchemeSeed: _aps.themeColor.watch(context), // 设置当前主题颜色, 42 | brightness: Brightness.light, 43 | ).copyWith( 44 | textTheme: Typography.material2021().black.apply(fontFamily: 'MiSans'), 45 | primaryTextTheme: Typography.material2021().black.apply( 46 | fontFamily: 'MiSans', 47 | ), 48 | ), 49 | darkTheme: ThemeData( 50 | useMaterial3: true, 51 | colorSchemeSeed: _aps.themeColor.watch(context), 52 | brightness: Brightness.dark, 53 | ).copyWith( 54 | textTheme: Typography.material2021().white.apply(fontFamily: 'MiSans'), 55 | primaryTextTheme: Typography.material2021().white.apply( 56 | fontFamily: 'MiSans', 57 | ), 58 | ), 59 | themeMode: _aps.themeMode.watch(context), // 设置当前主题模式 60 | home: MainScreen(), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/fun/net_astral_udp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:astral/k/app_s/aps.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | 6 | Future getIpv4AndIpV6Addresses() async { 7 | try { 8 | final client = HttpClient(); 9 | final request = await client.getUrl(Uri.parse('https://ipw.cn/')); 10 | final response = await request.close(); 11 | if (response.statusCode == HttpStatus.ok) { 12 | print('Failed to get public IPv6: HTTP ${response.statusCode}'); 13 | final publicIPv6 = await response.transform(utf8.decoder).join(); 14 | if (publicIPv6.isNotEmpty) { 15 | Aps().ipv6.value = response.statusCode.toString(); 16 | } 17 | } 18 | } catch (e) { 19 | print('Error fetching public IPv6: $e'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/fun/random_name.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | const List prefixes = [ 4 | '月光', 5 | '星云', 6 | '量子', 7 | '琉璃', 8 | '蒸汽', 9 | '镜像', 10 | '蜂巢', 11 | '极光', 12 | '云端', 13 | '琥珀', 14 | '棱镜', 15 | '螺旋', 16 | '黄昏', 17 | '黎光', 18 | '数据', 19 | '像素', 20 | '立方', 21 | '穹顶', 22 | '虫洞', 23 | ' fractal', 24 | '相位', 25 | '回声', 26 | '虚像', 27 | '纳米', 28 | '拓扑', 29 | '弦月', 30 | '翡翠', 31 | '静滞', 32 | '折叠', 33 | '观星', 34 | '粒子', 35 | '相位', 36 | '虹膜', 37 | '熵减', 38 | '拓扑', 39 | '全息', 40 | '磁浮', 41 | '反物质', 42 | '曲速', 43 | '克莱因', 44 | ]; 45 | 46 | const List middles = [ 47 | '会议室', 48 | '作战室', 49 | '温室', 50 | '档案馆', 51 | '核心', 52 | '回廊', 53 | '枢纽', 54 | '大厅', 55 | '观测台', 56 | '实验室', 57 | '咖啡角', 58 | '舰桥', 59 | '冥想间', 60 | '工坊', 61 | '沙盘', 62 | '剧场', 63 | '花房', 64 | '终端', 65 | '矩阵', 66 | '蜂巢', 67 | '图书馆', 68 | '发射井', 69 | '熔炉', 70 | '停机坪', 71 | '禁闭室', 72 | '培养舱', 73 | '画廊', 74 | '反应堆', 75 | '棱镜', 76 | '暗房', 77 | '跃迁舱', 78 | '服务器', 79 | '投影间', 80 | '解压舱', 81 | '温室', 82 | '指挥台', 83 | '孵化器', 84 | '天井', 85 | '祭坛', 86 | '回响室', 87 | ]; 88 | // 创建一个随机数生成器实例 89 | final Random _random = Random(); 90 | 91 | /// 生成一个随机名称,由一个前缀和一个中间部分组成 92 | String RandomName() { 93 | // 随机选择一个前缀 94 | final String prefix = prefixes[_random.nextInt(prefixes.length)]; 95 | // 随机选择一个中间部分 96 | final String middle = middles[_random.nextInt(middles.length)]; 97 | 98 | // 组合并返回名称 99 | return '$prefix$middle'; 100 | } 101 | -------------------------------------------------------------------------------- /lib/fun/show_add_room_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/fun/random_name.dart'; 2 | import 'package:astral/screens/room_page.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | Future showAddRoomDialog(BuildContext context) async { 6 | bool isEncrypted = true; 7 | String? name = RandomName(); 8 | String? roomName; 9 | String? roomPassword; 10 | 11 | await showDialog( 12 | context: context, 13 | builder: (context) { 14 | return StatefulBuilder( 15 | builder: (context, setState) { 16 | return AlertDialog( 17 | title: const Text('添加房间'), 18 | content: Column( 19 | mainAxisSize: MainAxisSize.min, 20 | children: [ 21 | TextField( 22 | controller: TextEditingController(text: name), 23 | decoration: const InputDecoration(labelText: '房间名称'), 24 | // 当文本字段内容改变时,同步更新外部 'name' 变量 25 | onChanged: (value) => name = value, 26 | ), 27 | const SizedBox(height: 8), 28 | SwitchListTile( 29 | title: const Text('是否保护'), 30 | value: isEncrypted, 31 | onChanged: (value) { 32 | setState(() { 33 | isEncrypted = value; 34 | }); 35 | }, 36 | ), 37 | 38 | if (!isEncrypted) ...[ 39 | TextField( 40 | decoration: const InputDecoration(labelText: '房间号'), 41 | onChanged: (value) => roomName = value, 42 | ), 43 | const SizedBox(height: 8), 44 | TextField( 45 | decoration: const InputDecoration(labelText: '房间密码'), 46 | onChanged: (value) => roomPassword = value, 47 | ), 48 | ], 49 | ], 50 | ), 51 | actions: [ 52 | TextButton( 53 | onPressed: () => Navigator.of(context).pop(), 54 | child: const Text('取消'), 55 | ), 56 | TextButton( 57 | onPressed: () { 58 | addEncryptedRoom( 59 | isEncrypted, 60 | name ?? RandomName(), 61 | roomName ?? "", 62 | roomPassword ?? "", 63 | ); 64 | Navigator.of(context).pop(); 65 | }, 66 | child: const Text('确定'), 67 | ), 68 | ], 69 | ); 70 | }, 71 | ); 72 | }, 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /lib/fun/show_edit_room_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/k/app_s/aps.dart'; 2 | import 'package:astral/k/models/room.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | Future showEditRoomDialog( 6 | BuildContext context, { 7 | required Room room, 8 | }) async { 9 | String? name = room.name; 10 | 11 | await showDialog( 12 | context: context, 13 | builder: (context) { 14 | return AlertDialog( 15 | title: const Text('编辑房间'), 16 | content: Column( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | TextField( 20 | controller: TextEditingController(text: name), 21 | decoration: const InputDecoration(labelText: '房间名称'), 22 | onChanged: (value) => room.name = value, 23 | ), 24 | const SizedBox(height: 8), 25 | // 显示房间类型(只读) 26 | ListTile( 27 | title: const Text('房间类型'), 28 | subtitle: Text(room.encrypted ? '加密房间' : '普通房间'), 29 | ), 30 | if (!room.encrypted) ...[ 31 | TextField( 32 | controller: TextEditingController(text: room.roomName), 33 | decoration: const InputDecoration(labelText: '房间号'), 34 | onChanged: (value) => room.roomName = value, 35 | ), 36 | const SizedBox(height: 8), 37 | TextField( 38 | controller: TextEditingController(text: room.password), 39 | decoration: const InputDecoration(labelText: '房间密码'), 40 | onChanged: (value) => room.password = value, 41 | ), 42 | ], 43 | ], 44 | ), 45 | actions: [ 46 | TextButton( 47 | onPressed: () => Navigator.of(context).pop(), 48 | child: const Text('取消'), 49 | ), 50 | TextButton( 51 | onPressed: () { 52 | Aps().updateRoom(room); 53 | Navigator.of(context).pop(); 54 | }, 55 | child: const Text('确定'), 56 | ), 57 | ], 58 | ); 59 | }, 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /lib/k/database/app_data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:astral/k/models/all_settings.dart'; 3 | import 'package:astral/k/models/net_config.dart'; 4 | import 'package:astral/k/models/room.dart'; 5 | import 'package:astral/k/models/rule_group.dart'; 6 | import 'package:astral/k/models/server_mod.dart'; 7 | import 'package:astral/k/models_mod/all_settings_cz.dart'; 8 | import 'package:astral/k/models_mod/net_config_cz.dart'; 9 | import 'package:astral/k/models_mod/room_cz.dart'; 10 | import 'package:astral/k/models_mod/server_cz.dart'; 11 | import 'package:isar/isar.dart'; 12 | import 'package:astral/k/models/theme_settings.dart'; 13 | import 'package:astral/k/models_mod/theme_settings_cz.dart'; 14 | import 'package:path_provider/path_provider.dart'; 15 | import 'package:path/path.dart' as path; 16 | 17 | class AppDatabase { 18 | static final AppDatabase _instance = AppDatabase._internal(); 19 | factory AppDatabase() => _instance; 20 | AppDatabase._internal(); 21 | 22 | late final Isar isar; 23 | late final ThemeSettingsRepository themeSettings; 24 | late final NetConfigRepository netConfigSetting; 25 | late final RoomCz RoomSetting; 26 | late final AllSettingsCz AllSettings; 27 | late final ServerCz ServerSetting; 28 | 29 | /// 初始化数据库 30 | Future init([String? customDbDir]) async { 31 | late final String dbDir; 32 | 33 | if (customDbDir != null) { 34 | // 使用自定义数据库目录 35 | dbDir = customDbDir; 36 | } else if (Platform.isAndroid) { 37 | // Android平台使用应用专属目录 38 | final appDocDir = await getApplicationDocumentsDirectory(); 39 | dbDir = Directory(path.join(appDocDir.path, 'db')).path; 40 | } else { 41 | // 其他平台使用可执行文件所在目录 42 | final executablePath = Platform.resolvedExecutable; 43 | final executableDir = Directory(executablePath).parent.path; 44 | dbDir = Directory(path.join(executableDir, 'data', 'db')).path; 45 | } 46 | 47 | // 确保数据库目录存在 48 | await Directory(dbDir).create(recursive: true); 49 | isar = await Isar.open([ 50 | ThemeSettingsSchema, 51 | NetConfigSchema, 52 | RoomSchema, 53 | AllSettingsSchema, 54 | ServerModSchema, 55 | ], directory: dbDir); 56 | themeSettings = ThemeSettingsRepository(isar); 57 | netConfigSetting = NetConfigRepository(isar); 58 | RoomSetting = RoomCz(isar); 59 | AllSettings = AllSettingsCz(isar); 60 | ServerSetting = ServerCz(isar); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/k/mod/window_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/fun/reg.dart'; 2 | import 'package:window_manager/window_manager.dart'; 3 | import 'dart:io'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:astral/k/app_s/aps.dart'; 6 | 7 | class WindowManagerUtils { 8 | static Future initializeWindow() async { 9 | // 检查当前平台是否为 Windows、MacOS 或 Linux 10 | if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) { 11 | // 确保窗口管理器已初始化 12 | await windowManager.ensureInitialized(); 13 | //添加信号监听 14 | // 创建响应式效果,用于监听和更新窗口标题 15 | effect(() { 16 | // 设置窗口标题为当前应用名称 17 | windowManager.setTitle(Aps().appName.value); 18 | }); 19 | // 定义窗口选项配置 20 | final windowOptions = WindowOptions( 21 | size: Size(960, 540), 22 | // 设置窗口最小大小为 300x300 23 | minimumSize: Size(200, 300), 24 | // 设置窗口居中显示 25 | center: true, 26 | // 设置窗口标题 27 | title: Aps().appName.value, 28 | // 设置标题栏样式为隐藏 29 | titleBarStyle: TitleBarStyle.hidden, 30 | // 设置窗口背景为透明 31 | backgroundColor: Colors.transparent, 32 | // 设置是否在任务栏显示 33 | skipTaskbar: false, 34 | ); 35 | 36 | // 等待窗口准备就绪并显示 37 | await windowManager.waitUntilReadyToShow(windowOptions, () async { 38 | // 如果 startupMinimize 为 true,则最小化窗口 39 | if (Aps().startupMinimize.value) { 40 | await windowManager.hide(); 41 | } else { 42 | await windowManager.show(); 43 | await windowManager.focus(); 44 | } 45 | }); 46 | 47 | if (Aps().startup.value) { 48 | handleStartupSetting(true); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/k/models/all_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | part 'all_settings.g.dart'; 3 | 4 | @collection 5 | class AllSettings { 6 | /// 主键ID,固定为1因为只需要一个实例 7 | Id id = 1; 8 | 9 | /// 当前启用的房间 10 | int? room; 11 | 12 | /// 玩家名称 13 | String? playerName; 14 | 15 | /// 监听列表 16 | List? listenList; 17 | 18 | /// 自定义vpn网段 19 | List customVpn = []; 20 | 21 | ///用户列表简约模式 22 | bool userListSimple = true; 23 | 24 | /// 关闭最小化到托盘 25 | bool closeMinimize = true; 26 | 27 | /// 开机自启 28 | bool startup = false; 29 | 30 | /// 启动后最小化 31 | bool startupMinimize = false; 32 | 33 | /// 启动后自动连接 34 | bool startupAutoConnect = false; 35 | 36 | /// 自动设置网卡跃点 37 | bool autoSetMTU = true; 38 | 39 | /// 参与测试版 40 | bool beta = false; 41 | 42 | /// 自动检查更新 43 | bool autoCheckUpdate = true; 44 | 45 | /// 下载加速 46 | String downloadAccelerate = 'https://gh.xmly.dev/'; 47 | } 48 | -------------------------------------------------------------------------------- /lib/k/models/net_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:isar/isar.dart'; 3 | part 'net_config.g.dart'; 4 | 5 | @embedded 6 | class ConnectionInfo { 7 | late String bindAddr; 8 | late String dstAddr; 9 | late String proto; 10 | ConnectionInfo() { 11 | bindAddr = ''; 12 | dstAddr = ''; 13 | proto = ''; 14 | } 15 | } 16 | 17 | @embedded 18 | class ConnectionManager { 19 | late String name; // 分组名称 20 | late List connections; 21 | late bool enabled; 22 | ConnectionManager() { 23 | name = ''; 24 | connections = []; 25 | enabled = false; 26 | } 27 | } 28 | 29 | @collection 30 | class NetConfig { 31 | /// 主键ID,固定为1因为只需要一个实例 32 | Id id = 1; 33 | 34 | String netns = ''; // 网络命名空间 35 | 36 | String hostname = Platform.localHostname; // 主机名 37 | 38 | String instance_name = 'default'; // 实例名称 39 | 40 | String ipv4 = ''; // IPv4地址 41 | 42 | bool dhcp = true; // 是否使用DHCP 43 | String network_name = ''; // 网络名称 44 | String network_secret = ''; // 网络密钥 45 | 46 | List listeners = []; // 监听端口 47 | 48 | List peer = []; // 服务器节点地址 49 | 50 | // 子网代理 51 | List cidrproxy = []; // 代理地址 52 | 53 | // 转发配置 54 | List connectionManagers = []; 55 | /// 默认协议 56 | String default_protocol = 'tcp'; //x 57 | 58 | /// 设备名称 59 | String dev_name = ''; 60 | 61 | /// 是否启用加密 62 | bool enable_encryption = true; //x 63 | 64 | /// 是否启用IPv6 65 | bool enable_ipv6 = true; 66 | 67 | /// 最大传输单元 68 | int mtu = 1360; //x 69 | 70 | /// 是否优先考虑延迟 71 | bool latency_first = false; //x 72 | 73 | /// 是否启用出口节点 74 | bool enable_exit_node = false; //x 75 | 76 | /// 是否禁用TUN设备 77 | bool no_tun = false; //x 78 | 79 | /// 是否使用smoltcp网络栈 80 | bool use_smoltcp = false; //x 81 | 82 | /// 中继网络白名单 83 | String relay_network_whitelist = '*'; 84 | 85 | /// 是否禁用P2P 86 | bool disable_p2p = false; //x 87 | 88 | /// 是否中继所有对等RPC 89 | bool relay_all_peer_rpc = false; //x 90 | 91 | /// 是否禁用UDP打洞 92 | bool disable_udp_hole_punching = false; //x 93 | 94 | /// 是否启用多线程 95 | bool multi_thread = true; //x 96 | 97 | /// 数据压缩算法 98 | int data_compress_algo = 1; //x 99 | 100 | /// 是否绑定设备 101 | bool bind_device = true; //x 102 | 103 | /// 是否启用KCP代理 104 | bool enable_kcp_proxy = true; //x 105 | 106 | /// 是否禁用KCP输入 107 | bool disable_kcp_input = false; //x 108 | 109 | /// 是否禁用中继KCP 110 | bool disable_relay_kcp = true; //x 111 | 112 | /// 是否使用系统代理转发 113 | bool proxy_forward_by_system = false; //x 114 | 115 | /// accept_dns 魔术DNS 116 | bool accept_dns = false; //x 117 | } 118 | -------------------------------------------------------------------------------- /lib/k/models/room.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | part 'room.g.dart'; 3 | 4 | @collection 5 | class Room { 6 | /// 主键自增 7 | Id id = Isar.autoIncrement; 8 | String name = ""; // 房间别名 9 | // 是否加密 10 | bool encrypted = false; 11 | //房间名称 12 | String roomName = ""; 13 | // 房间密码 14 | String password = ""; 15 | // 房间标签 16 | List tags = []; 17 | 18 | //构造 19 | Room({ 20 | this.id = Isar.autoIncrement, 21 | this.name = "", 22 | this.encrypted = false, 23 | this.roomName = "", 24 | this.password = "", 25 | this.tags = const [], 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/k/models/rule_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | part 'rule_group.g.dart'; 3 | 4 | // 操作类型枚举 5 | enum OperationType { connect, bind, sendto, recvfrom, all } 6 | 7 | // 规则类 8 | @embedded 9 | class Rule { 10 | // 操作类型 11 | @enumerated 12 | OperationType op = OperationType.all; // Default value instead of nullable 13 | 14 | // 匹配条件 15 | String? matchIp; 16 | int? matchPort; 17 | 18 | // 替换目标 19 | String? replaceIp; 20 | int? replacePort; 21 | 22 | // 网卡绑定相关 23 | String? bindNic; 24 | bool? enableAutoNic; 25 | String? nicNameFilter; 26 | // 构造函数 27 | Rule({ 28 | this.op = OperationType.all, // Default value instead of nullable 29 | this.matchIp, 30 | this.matchPort, 31 | this.replaceIp, 32 | this.replacePort, 33 | this.bindNic, 34 | this.enableAutoNic, 35 | this.nicNameFilter, 36 | }); 37 | } 38 | 39 | // 规则组类 40 | @collection 41 | class RuleGroup { 42 | Id id = Isar.autoIncrement; 43 | 44 | // 规则组名称 45 | String? name; 46 | 47 | // 匹配窗口标题的正则表达式 48 | String? regex; 49 | 50 | // 规则列表 51 | List rules = []; 52 | 53 | // 实现实例化 54 | RuleGroup({this.name, this.regex = '', this.rules = const []}); 55 | } 56 | -------------------------------------------------------------------------------- /lib/k/models/server_mod.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | part 'server_mod.g.dart'; 3 | 4 | @collection 5 | class ServerMod { 6 | /// 主键自增 7 | Id id = Isar.autoIncrement; 8 | String name = ""; // 服务器名 9 | String url = ""; // 服务器地址 10 | // 是否启用 11 | bool enable = true; 12 | // tcp 开启 13 | bool tcp = true; 14 | // udp 开启 15 | bool udp = false; 16 | // ws 开启 17 | bool ws = false; 18 | // wss 开启 19 | bool wss = false; 20 | // quic 开启 21 | bool quic = false; 22 | // wg 开启 23 | bool wg = false; 24 | // txt 开启 25 | bool txt = false; 26 | // srv 开启 27 | bool srv = false; 28 | // http 开启 29 | bool http = false; 30 | // https 开启 31 | bool https = false; 32 | 33 | //构造 34 | ServerMod({ 35 | this.id = Isar.autoIncrement, 36 | this.enable = false, 37 | this.name = "", 38 | this.url = "", 39 | this.tcp = true, 40 | this.udp = false, 41 | this.ws = false, 42 | this.wss = false, 43 | this.quic = false, 44 | this.wg = false, 45 | this.txt = false, 46 | this.srv = false, 47 | this.http = false, 48 | this.https = false, 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /lib/k/models/theme_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:isar/isar.dart'; 3 | part 'theme_settings.g.dart'; 4 | 5 | /// 主题设置类 6 | @collection 7 | class ThemeSettings { 8 | /// 主键ID,固定为1因为只需要一个实例 9 | Id id = 1; 10 | 11 | /// 主题颜色值,默认为蓝色 12 | int colorValue = Colors.blue.toARGB32(); 13 | 14 | /// 主题模式枚举值,默认跟随系统 15 | @enumerated 16 | ThemeMode themeModeValue = ThemeMode.system; 17 | 18 | /// 构造函数,用于初始化主题设置 19 | ThemeSettings({ 20 | this.colorValue = 0xFFFF5722, 21 | this.themeModeValue = ThemeMode.system, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/k/models_mod/room_cz.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | import 'package:astral/k/models/room.dart'; // 添加 Room 模型的导入 3 | 4 | class RoomCz { 5 | final Isar _isar; 6 | 7 | RoomCz(this._isar) { 8 | init(); 9 | } 10 | 11 | Future init() async {} 12 | 13 | // 添加房间 14 | Future addRoom(Room room) async { 15 | return await _isar.writeTxn(() async { 16 | return await _isar.rooms.put(room); 17 | }); 18 | } 19 | 20 | // 根据ID获取房间 21 | Future getRoomById(int id) async { 22 | return await _isar.rooms.get(id); 23 | } 24 | 25 | // 获取所有房间 26 | Future> getAllRooms() async { 27 | return await _isar.rooms.where().findAll(); 28 | } 29 | 30 | // 更新房间 31 | Future updateRoom(Room room) async { 32 | return await _isar.writeTxn(() async { 33 | return await _isar.rooms.put(room); // Isar 的 put 方法会自动处理更新 34 | }); 35 | } 36 | 37 | // 删除房间 38 | Future deleteRoom(int id) async { 39 | return await _isar.writeTxn(() async { 40 | return await _isar.rooms.delete(id); 41 | }); 42 | } 43 | 44 | // 根据标签查询房间 45 | Future> getRoomsByTag(String tag) async { 46 | return await _isar.rooms.filter().tagsElementEqualTo(tag).findAll(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/k/models_mod/server_cz.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/k/models/server_mod.dart'; 2 | import 'package:isar/isar.dart'; 3 | 4 | class ServerCz { 5 | final Isar _isar; 6 | 7 | ServerCz(this._isar) { 8 | init(); 9 | } 10 | 11 | Future init() async { 12 | } 13 | 14 | // 添加服务器 15 | Future addServer(ServerMod server) async { 16 | return await _isar.writeTxn(() async { 17 | return await _isar.serverMods.put(server); 18 | }); 19 | } 20 | 21 | // 设置是否启用 22 | Future setServerEnable(ServerMod server, bool enable) async { 23 | server.enable = enable; 24 | return await _isar.writeTxn(() async { 25 | return await _isar.serverMods.put(server); 26 | }); 27 | } 28 | 29 | // 根据ID获取服务器 30 | Future getServerById(int id) async { 31 | return await _isar.serverMods.get(id); 32 | } 33 | 34 | // 获取所有服务器 35 | Future> getAllServers() async { 36 | return await _isar.serverMods.where().findAll(); 37 | } 38 | 39 | // 更新服务器 40 | Future updateServer(ServerMod server) async { 41 | return await _isar.writeTxn(() async { 42 | return await _isar.serverMods.put(server); 43 | }); 44 | } 45 | 46 | // 删除服务器 by id 47 | Future deleteServerid(int id) async { 48 | return await _isar.writeTxn(() async { 49 | return await _isar.serverMods.delete(id); 50 | }); 51 | } 52 | 53 | // 删除服务器 by object 54 | Future deleteServer(ServerMod server) async { 55 | return await _isar.writeTxn(() async { 56 | return await _isar.serverMods.delete(server.id); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/k/models_mod/theme_settings_cz.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:isar/isar.dart'; 3 | import '../models/theme_settings.dart'; 4 | 5 | /// 主题设置仓库类 6 | /// 负责管理和持久化主题相关的设置 7 | class ThemeSettingsRepository { 8 | /// Isar数据库实例 9 | final Isar _isar; 10 | 11 | /// 构造函数 12 | /// @param _isar Isar数据库实例 13 | /// 创建实例时自动初始化数据 14 | ThemeSettingsRepository(this._isar) { 15 | init(); 16 | } 17 | 18 | /// 初始化主题设置 19 | /// 如果数据库中没有主题设置记录,则创建一个默认的设置记录 20 | Future init() async { 21 | if (await _isar.themeSettings.count() == 0) { 22 | await _isar.writeTxn(() async { 23 | await _isar.themeSettings.put(ThemeSettings()..id = 1); 24 | }); 25 | } 26 | } 27 | 28 | /// 更新主题颜色 29 | /// @param colorValue 新的颜色值 30 | /// 将新的颜色值保存到数据库中 31 | Future updateThemeColor(int colorValue) async { 32 | ThemeSettings? settings = await _isar.themeSettings.get(1); 33 | if (settings != null) { 34 | settings.colorValue = colorValue; 35 | await _isar.writeTxn(() async { 36 | await _isar.themeSettings.put(settings); 37 | }); 38 | } 39 | } 40 | 41 | /// 获取当前主题颜色 42 | /// @return 返回当前的主题颜色值,如果未设置则返回0 43 | Future getThemeColor() async { 44 | ThemeSettings? settings = await _isar.themeSettings.get(1); 45 | return settings?.colorValue ?? 0xFFFF5722; 46 | } 47 | 48 | /// 更新主题模式 49 | /// @param themeMode 新的主题模式(light/dark/system) 50 | /// 将新的主题模式保存到数据库中 51 | Future updateThemeMode(ThemeMode themeMode) async { 52 | ThemeSettings? settings = await _isar.themeSettings.get(1); 53 | if (settings != null) { 54 | settings.themeModeValue = themeMode; 55 | await _isar.writeTxn(() async { 56 | await _isar.themeSettings.put(settings); 57 | }); 58 | } 59 | } 60 | 61 | /// 获取当前主题模式 62 | /// @return 返回当前的主题模式,如果未设置则返回系统默认模式 63 | Future getThemeMode() async { 64 | ThemeSettings? settings = await _isar.themeSettings.get(1); 65 | return settings?.themeModeValue ?? ThemeMode.system; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/k/navigtion.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NavigationItem { 4 | final IconData icon; 5 | final IconData activeIcon; 6 | final String label; 7 | final Widget page; 8 | 9 | const NavigationItem({ 10 | required this.icon, 11 | IconData? activeIcon, 12 | required this.label, 13 | required this.page, 14 | }) : activeIcon = activeIcon ?? icon; 15 | } 16 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:astral/fun/up.dart'; 5 | import 'package:astral/k/database/app_data.dart'; 6 | import 'package:astral/k/mod/window_manager.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:astral/src/rust/frb_generated.dart'; 10 | import 'package:astral/app.dart'; 11 | import 'package:sentry_flutter/sentry_flutter.dart'; 12 | 13 | // 修改后的main.dart文件内容 14 | void main() async { 15 | // 添加Zone错误致命检测(必须放在最顶部) 16 | BindingBase.debugZoneErrorsAreFatal = true; 17 | 18 | runZonedGuarded( 19 | () async { 20 | WidgetsFlutterBinding.ensureInitialized(); 21 | await AppDatabase().init(); 22 | AppInfoUtil.init(); 23 | await RustLib.init(); 24 | if (!kIsWeb && 25 | (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { 26 | await WindowManagerUtils.initializeWindow(); 27 | } 28 | await SentryFlutter.init((options) { 29 | options.dsn = 30 | 'https://8ddef9dc25ba468431473fc15187df30@o4509285217402880.ingest.de.sentry.io/4509285224087632'; 31 | }); 32 | runApp(const KevinApp()); 33 | }, 34 | (exception, stackTrace) async { 35 | print(exception); 36 | print(stackTrace); 37 | await Sentry.captureException(exception, stackTrace: stackTrace); 38 | }, 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /lib/screens/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/wid/home/about_home.dart'; 2 | import 'package:astral/wid/home/contributors.dart'; // 添加这行 3 | import 'package:astral/wid/home/servers_home.dart'; 4 | import 'package:astral/wid/home/user_ip.dart'; 5 | import 'package:astral/wid/home/virtual_ip.dart'; 6 | import 'package:astral/wid/home/connect_button.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 9 | 10 | class HomePage extends StatefulWidget { 11 | const HomePage({super.key}); 12 | 13 | @override 14 | State createState() => _HomePageState(); 15 | } 16 | 17 | class _HomePageState extends State { 18 | // 根据宽度计算列数 19 | int _getColumnCount(double width) { 20 | if (width >= 1200) { 21 | return 5; 22 | } else if (width >= 900) { 23 | return 4; 24 | } else if (width >= 600) { 25 | return 3; 26 | } 27 | return 2; 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final width = MediaQuery.of(context).size.width; 33 | final columnCount = _getColumnCount(width); 34 | return Scaffold( 35 | body: Stack( 36 | children: [ 37 | Column( 38 | children: [ 39 | Expanded( 40 | child: SingleChildScrollView( 41 | child: Padding( 42 | padding: const EdgeInsets.all(14), 43 | child: StaggeredGrid.count( 44 | crossAxisCount: columnCount, 45 | mainAxisSpacing: 8, 46 | crossAxisSpacing: 8, 47 | children: [ 48 | VirtualIpBox(), 49 | UserIpBox(), 50 | ServersHome(), 51 | // UdpLog(), 52 | AboutHome(), 53 | ], 54 | ), 55 | ), 56 | ), 57 | ), 58 | ], 59 | ), 60 | ], 61 | ), 62 | floatingActionButton: const ConnectButton(), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/rust/api/firewall.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.10.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | Future getFirewallStatus({required int profileIndex}) => RustLib 10 | .instance 11 | .api 12 | .crateApiFirewallGetFirewallStatus(profileIndex: profileIndex); 13 | 14 | Future setFirewallStatus({ 15 | required int profileIndex, 16 | required bool enable, 17 | }) => RustLib.instance.api.crateApiFirewallSetFirewallStatus( 18 | profileIndex: profileIndex, 19 | enable: enable, 20 | ); 21 | -------------------------------------------------------------------------------- /lib/src/rust/api/hops.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.10.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | Future> getAllInterfacesMetrics() => 10 | RustLib.instance.api.crateApiHopsGetAllInterfacesMetrics(); 11 | 12 | Future setInterfaceMetric({ 13 | required String interfaceName, 14 | required int metric, 15 | }) => RustLib.instance.api.crateApiHopsSetInterfaceMetric( 16 | interfaceName: interfaceName, 17 | metric: metric, 18 | ); 19 | -------------------------------------------------------------------------------- /lib/wid/bottom_nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/k/app_s/aps.dart'; 2 | import 'package:astral/k/navigtion.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class BottomNav extends StatelessWidget { 6 | final List navigationItems; 7 | final ColorScheme colorScheme; 8 | 9 | const BottomNav({ 10 | super.key, 11 | required this.navigationItems, 12 | required this.colorScheme, 13 | }); 14 | 15 | @override 16 | BottomNavigationBar build(BuildContext context) { 17 | return BottomNavigationBar( 18 | backgroundColor: colorScheme.surfaceContainerLow, 19 | elevation: 8, 20 | type: BottomNavigationBarType.fixed, 21 | selectedItemColor: colorScheme.primary, 22 | unselectedItemColor: colorScheme.onSurfaceVariant, 23 | showUnselectedLabels: true, 24 | items: 25 | navigationItems 26 | .map( 27 | (item) => BottomNavigationBarItem( 28 | icon: Icon(item.icon), 29 | activeIcon: Icon(item.activeIcon), 30 | label: item.label, 31 | ), 32 | ) 33 | .toList(), 34 | currentIndex: Aps().selectedIndex.watch(context), 35 | onTap: (index) { 36 | Aps().selectedIndex.set(index); 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/wid/home/about_home.dart: -------------------------------------------------------------------------------- 1 | import 'package:astral/fun/up.dart'; 2 | import 'package:astral/k/app_s/aps.dart'; 3 | import 'package:astral/src/rust/api/simple.dart'; 4 | import 'package:astral/wid/home_box.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:graphview/GraphView.dart'; 7 | 8 | class AboutHome extends StatefulWidget { 9 | const AboutHome({super.key}); 10 | 11 | @override 12 | State createState() => _AboutHomeState(); 13 | } 14 | 15 | class _AboutHomeState extends State { 16 | String version = ''; 17 | @override 18 | void initState() { 19 | // TODO: implement initState 20 | super.initState(); 21 | easytierVersion().then((value) { 22 | setState(() { 23 | version = value; // 异步完成后更新状态 24 | }); 25 | }); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | var colorScheme = Theme.of(context).colorScheme; 31 | return HomeBox( 32 | widthSpan: 2, 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | Row( 37 | children: [ 38 | Icon( 39 | Icons.info_outline, 40 | color: colorScheme.primary, 41 | size: 22, 42 | ), // 修改标题图标 43 | const SizedBox(width: 8), 44 | const Text( 45 | '关于', 46 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w400), 47 | ), 48 | ], 49 | ), 50 | const SizedBox(height: 16), 51 | Wrap( 52 | spacing: 8, 53 | children: [ 54 | Icon( 55 | Icons.smartphone, 56 | size: 20, 57 | color: colorScheme.primary, 58 | ), // 软件版本图标 59 | const Text( 60 | '软件版本: ', 61 | style: TextStyle(fontWeight: FontWeight.w700), 62 | ), 63 | Text( 64 | AppInfoUtil.getVersion(), 65 | style: TextStyle(color: colorScheme.secondary), 66 | ), 67 | ], 68 | ), 69 | const SizedBox(height: 4), 70 | Wrap( 71 | spacing: 8, 72 | children: [ 73 | Icon( 74 | Icons.memory, 75 | size: 20, 76 | color: colorScheme.primary, 77 | ), // 内核版本图标 78 | const Text( 79 | '内核版本: ', 80 | style: TextStyle(fontWeight: FontWeight.w700), 81 | ), 82 | Text(version, style: TextStyle(color: colorScheme.secondary)), 83 | ], 84 | ), 85 | ], 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/wid/home_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 3 | 4 | class HomeBox extends StatefulWidget { 5 | final int widthSpan; 6 | final Widget? child; 7 | final double? fixedCellHeight; // 改为可空类型 8 | //是否开启边框 9 | final bool? isBorder; 10 | 11 | const HomeBox({ 12 | super.key, 13 | required this.widthSpan, 14 | this.child, 15 | this.fixedCellHeight, // 移除默认值 16 | this.isBorder = true, 17 | }); 18 | 19 | @override 20 | State createState() => _HomeBoxState(); 21 | } 22 | 23 | class _HomeBoxState extends State { 24 | bool isHovered = false; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final theme = Theme.of(context); 29 | 30 | return widget.fixedCellHeight != null 31 | ? StaggeredGridTile.extent( 32 | crossAxisCellCount: widget.widthSpan, 33 | mainAxisExtent: widget.fixedCellHeight!, 34 | child: _buildContent(theme), 35 | ) 36 | : StaggeredGridTile.fit( 37 | crossAxisCellCount: widget.widthSpan, 38 | child: _buildContent(theme), 39 | ); 40 | } 41 | 42 | Widget _buildContent(ThemeData theme) { 43 | return MouseRegion( 44 | onEnter: (_) => setState(() => isHovered = true), 45 | onExit: (_) => setState(() => isHovered = false), 46 | child: Card( 47 | elevation: isHovered ? 8 : 4, 48 | shape: RoundedRectangleBorder( 49 | borderRadius: BorderRadius.circular(widget.isBorder ?? true ? 8 : 1), 50 | side: BorderSide( 51 | color: isHovered ? theme.colorScheme.primary : Colors.transparent, 52 | width: 1, 53 | ), 54 | ), 55 | child: InkWell( 56 | onTap: () {}, 57 | splashColor: theme.colorScheme.primary.withValues(alpha: 0.3), 58 | highlightColor: theme.colorScheme.primary.withValues(alpha: 0.1), 59 | borderRadius: BorderRadius.circular(8), 60 | child: Container( 61 | padding: EdgeInsets.all(widget.isBorder ?? true ? 12 : 1.0), 62 | height: widget.fixedCellHeight, // height 会自动适应内容 63 | width: double.infinity, 64 | child: widget.child, 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_localization 7 | gtk 8 | isar_flutter_libs 9 | open_file_linux 10 | screen_retriever_linux 11 | sentry_flutter 12 | system_tray 13 | url_launcher_linux 14 | window_manager 15 | ) 16 | 17 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 18 | rust_lib_astral 19 | ) 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) 22 | 23 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 25 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 28 | endforeach(plugin) 29 | 30 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 31 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 32 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 33 | endforeach(ffi_plugin) 34 | -------------------------------------------------------------------------------- /linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: astral 2 | description: "astral" 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | 5 | 6 | version: 2.0.0-beta.94 7 | 8 | environment: 9 | sdk: ^3.7.2 10 | 11 | dependencies: 12 | open_file: ^3.0.0 13 | vpn_service_plugin: 14 | path: vpn_service_plugin 15 | flutter: 16 | sdk: flutter 17 | path: ^1.8.3 # Add this line 18 | cupertino_icons: ^1.0.8 19 | rust_lib_astral: 20 | path: rust_builder 21 | flutter_rust_bridge: 2.10.0 22 | signals_flutter: ^6.0.2 23 | isar_flutter_libs: 24 | hosted: https://pub.isar-community.dev 25 | version: 3.1.8 26 | window_manager: ^0.4.3 27 | flutter_colorpicker: ^1.1.0 28 | flutter_staggered_grid_view: ^0.7.0 29 | uuid: ^3.0.7 30 | jwt_decoder: ^2.0.1 31 | dart_jsonwebtoken: ^3.2.0 32 | flutter_localization: ^0.3.2 33 | flutter_localizations: 34 | sdk: flutter 35 | graphview: ^1.2.0 36 | device_info_plus: ^11.4.0 37 | toml: ^0.16.0 38 | isar: 39 | hosted: https://pub.isar-community.dev 40 | version: 3.1.8 41 | package_info_plus: ^8.3.0 42 | url_launcher: ^6.3.1 43 | path_provider: ^2.1.5 44 | system_tray: ^2.0.3 45 | app_links: ^6.4.0 46 | sentry_flutter: ^8.14.2 47 | flutter_dotenv: ^5.2.1 48 | win32: ^5.12.0 49 | json_annotation: ^4.8.1 50 | permission_handler: ^11.0.1 51 | 52 | dev_dependencies: 53 | flutter_test: 54 | sdk: flutter 55 | flutter_lints: ^5.0.0 56 | build_runner: ^2.4.4 57 | json_serializable: ^6.7.1 58 | integration_test: 59 | sdk: flutter 60 | isar_generator: 61 | hosted: https://pub.isar-community.dev 62 | version: 3.1.8 63 | 64 | flutter: 65 | uses-material-design: true 66 | assets: 67 | # - assets/dlls/ 68 | - assets/icon.ico 69 | 70 | fonts: 71 | - family: MiSans 72 | fonts: 73 | - asset: fonts/MiSans-Demibold.ttf 74 | - asset: fonts/MiSans-Regular.ttf 75 | weight: 800 76 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_lib_astral" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "staticlib"] 8 | 9 | [dependencies] 10 | flutter_rust_bridge = "=2.10.0" 11 | lazy_static = "1.4" 12 | serde_json = "1.0" 13 | serde = { version = "1.0", features = ["derive"] } 14 | once_cell = "1.19.0" 15 | dashmap = "6.1.0" 16 | chrono = "0.4.38" 17 | humansize = "1.1.1" 18 | tokio = "1.39.2" 19 | anyhow = "1.0.95" 20 | surge-ping = "0.8" 21 | easytier = { path = "./easytier" } 22 | windows = { version = "0.48.0", features = ["Win32_NetworkManagement_WindowsFirewall", 23 | "Win32_System_Com", 24 | "Win32_NetworkManagement_IpHelper", 25 | "Win32_Networking_WinSock", 26 | "Win32_System_Memory", 27 | "Win32_System_WindowsProgramming", 28 | "Win32_Foundation"] } 29 | winapi = { version = "0.3", features = ["iphlpapi", "iptypes", "netioapi", "winerror", "ws2def"] } 30 | rand = "0.9.1" 31 | 32 | [lints.rust] 33 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } 34 | -------------------------------------------------------------------------------- /rust/easytier/src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | pub mod windows; 3 | -------------------------------------------------------------------------------- /rust/easytier/src/common/constants.rs: -------------------------------------------------------------------------------- 1 | macro_rules! define_global_var { 2 | ($name:ident, $type:ty, $init:expr) => { 3 | pub static $name: once_cell::sync::Lazy> = 4 | once_cell::sync::Lazy::new(|| tokio::sync::Mutex::new($init)); 5 | }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! use_global_var { 10 | ($name:ident) => { 11 | crate::common::constants::$name.lock().await.to_owned() 12 | }; 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! set_global_var { 17 | ($name:ident, $val:expr) => { 18 | *crate::common::constants::$name.lock().await = $val 19 | }; 20 | } 21 | 22 | define_global_var!(MANUAL_CONNECTOR_RECONNECT_INTERVAL_MS, u64, 1000); 23 | 24 | define_global_var!(OSPF_UPDATE_MY_GLOBAL_FOREIGN_NETWORK_INTERVAL_SEC, u64, 10); 25 | 26 | pub const UDP_HOLE_PUNCH_CONNECTOR_SERVICE_ID: u32 = 2; 27 | 28 | pub const WIN_SERVICE_WORK_DIR_REG_KEY: &str = "SOFTWARE\\EasyTier\\Service\\WorkDir"; 29 | 30 | pub const EASYTIER_VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-Astral"); 31 | -------------------------------------------------------------------------------- /rust/easytier/src/common/defer.rs: -------------------------------------------------------------------------------- 1 | #[doc(hidden)] 2 | pub struct Defer { 3 | // internal struct used by defer! macro 4 | func: Option, 5 | } 6 | 7 | impl Defer { 8 | pub fn new(func: F) -> Self { 9 | Self { func: Some(func) } 10 | } 11 | } 12 | 13 | impl Drop for Defer { 14 | fn drop(&mut self) { 15 | self.func.take().map(|f| f()); 16 | } 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! defer { 21 | ( $($tt:tt)* ) => { 22 | let _deferred = $crate::common::defer::Defer::new(|| { $($tt)* }); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /rust/easytier/src/common/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, result}; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::tunnel; 6 | 7 | use super::PeerId; 8 | 9 | #[derive(Error, Debug)] 10 | pub enum Error { 11 | #[error("io error")] 12 | IOError(#[from] io::Error), 13 | 14 | #[cfg(feature = "tun")] 15 | #[error("rust tun error {0}")] 16 | TunError(#[from] tun::Error), 17 | 18 | #[error("tunnel error {0}")] 19 | TunnelError(#[from] tunnel::TunnelError), 20 | #[error("Peer has no conn, PeerId: {0}")] 21 | PeerNoConnectionError(PeerId), 22 | #[error("RouteError: {0:?}")] 23 | RouteError(Option), 24 | #[error("Not found")] 25 | NotFound, 26 | #[error("Invalid Url: {0}")] 27 | InvalidUrl(String), 28 | #[error("Shell Command error: {0}")] 29 | ShellCommandError(String), 30 | // #[error("Rpc listen error: {0}")] 31 | // RpcListenError(String), 32 | #[error("Rpc connect error: {0}")] 33 | RpcConnectError(String), 34 | #[error("Timeout error: {0}")] 35 | Timeout(#[from] tokio::time::error::Elapsed), 36 | #[error("url in blacklist")] 37 | UrlInBlacklist, 38 | #[error("unknown data store error")] 39 | Unknown, 40 | #[error("anyhow error: {0}")] 41 | AnyhowError(#[from] anyhow::Error), 42 | 43 | #[error("wait resp error: {0}")] 44 | WaitRespError(String), 45 | 46 | #[error("message decode error: {0}")] 47 | MessageDecodeError(String), 48 | 49 | #[error("secret key error: {0}")] 50 | SecretKeyError(String), 51 | } 52 | 53 | pub type Result = result::Result; 54 | 55 | // impl From for std:: 56 | -------------------------------------------------------------------------------- /rust/easytier/src/common/ifcfg/darwin.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use async_trait::async_trait; 4 | 5 | use super::{cidr_to_subnet_mask, run_shell_cmd, Error, IfConfiguerTrait}; 6 | 7 | pub struct MacIfConfiger {} 8 | #[async_trait] 9 | impl IfConfiguerTrait for MacIfConfiger { 10 | async fn add_ipv4_route( 11 | &self, 12 | name: &str, 13 | address: Ipv4Addr, 14 | cidr_prefix: u8, 15 | cost: Option, 16 | ) -> Result<(), Error> { 17 | run_shell_cmd( 18 | format!( 19 | "route -n add {} -netmask {} -interface {} -hopcount {}", 20 | address, 21 | cidr_to_subnet_mask(cidr_prefix), 22 | name, 23 | cost.unwrap_or(7) 24 | ) 25 | .as_str(), 26 | ) 27 | .await 28 | } 29 | 30 | async fn remove_ipv4_route( 31 | &self, 32 | name: &str, 33 | address: Ipv4Addr, 34 | cidr_prefix: u8, 35 | ) -> Result<(), Error> { 36 | run_shell_cmd( 37 | format!( 38 | "route -n delete {} -netmask {} -interface {}", 39 | address, 40 | cidr_to_subnet_mask(cidr_prefix), 41 | name 42 | ) 43 | .as_str(), 44 | ) 45 | .await 46 | } 47 | 48 | async fn add_ipv4_ip( 49 | &self, 50 | name: &str, 51 | address: Ipv4Addr, 52 | cidr_prefix: u8, 53 | ) -> Result<(), Error> { 54 | run_shell_cmd( 55 | format!( 56 | "ifconfig {} {:?}/{:?} 10.8.8.8 up", 57 | name, address, cidr_prefix, 58 | ) 59 | .as_str(), 60 | ) 61 | .await 62 | } 63 | 64 | async fn set_link_status(&self, name: &str, up: bool) -> Result<(), Error> { 65 | run_shell_cmd(format!("ifconfig {} {}", name, if up { "up" } else { "down" }).as_str()) 66 | .await 67 | } 68 | 69 | async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { 70 | if ip.is_none() { 71 | run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await 72 | } else { 73 | run_shell_cmd( 74 | format!("ifconfig {} inet {} delete", name, ip.unwrap().to_string()).as_str(), 75 | ) 76 | .await 77 | } 78 | } 79 | 80 | async fn set_mtu(&self, name: &str, mtu: u32) -> Result<(), Error> { 81 | run_shell_cmd(format!("ifconfig {} mtu {}", name, mtu).as_str()).await 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rust/easytier/src/gateway/fast_socks5/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jonathan Dizdarevic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /rust/easytier/src/gateway/fast_socks5/README.md: -------------------------------------------------------------------------------- 1 | Code is modified from https://github.com/dizda/fast-socks5 2 | -------------------------------------------------------------------------------- /rust/easytier/src/gateway/fast_socks5/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stream; 2 | pub mod target_addr; 3 | -------------------------------------------------------------------------------- /rust/easytier/src/gateway/fast_socks5/util/stream.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use tokio::io::ErrorKind as IOErrorKind; 3 | use tokio::net::{TcpStream, ToSocketAddrs}; 4 | use tokio::time::timeout; 5 | 6 | use crate::gateway::fast_socks5::{ReplyError, Result}; 7 | 8 | /// Easy to destructure bytes buffers by naming each fields: 9 | /// 10 | /// # Examples (before) 11 | /// 12 | /// ```ignore 13 | /// let mut buf = [0u8; 2]; 14 | /// stream.read_exact(&mut buf).await?; 15 | /// let [version, method_len] = buf; 16 | /// 17 | /// assert_eq!(version, 0x05); 18 | /// ``` 19 | /// 20 | /// # Examples (after) 21 | /// 22 | /// ```ignore 23 | /// let [version, method_len] = read_exact!(stream, [0u8; 2]); 24 | /// 25 | /// assert_eq!(version, 0x05); 26 | /// ``` 27 | #[macro_export] 28 | macro_rules! read_exact { 29 | ($stream: expr, $array: expr) => {{ 30 | let mut x = $array; 31 | // $stream 32 | // .read_exact(&mut x) 33 | // .await 34 | // .map_err(|_| io_err("lol"))?; 35 | $stream.read_exact(&mut x).await.map(|_| x) 36 | }}; 37 | } 38 | 39 | pub async fn tcp_connect_with_timeout(addr: T, request_timeout_s: u64) -> Result 40 | where 41 | T: ToSocketAddrs, 42 | { 43 | let fut = tcp_connect(addr); 44 | match timeout(Duration::from_secs(request_timeout_s), fut).await { 45 | Ok(result) => result, 46 | Err(_) => Err(ReplyError::ConnectionTimeout.into()), 47 | } 48 | } 49 | 50 | pub async fn tcp_connect(addr: T) -> Result 51 | where 52 | T: ToSocketAddrs, 53 | { 54 | match TcpStream::connect(addr).await { 55 | Ok(o) => Ok(o), 56 | Err(e) => match e.kind() { 57 | // Match other TCP errors with ReplyError 58 | IOErrorKind::ConnectionRefused => Err(ReplyError::ConnectionRefused.into()), 59 | IOErrorKind::ConnectionAborted => Err(ReplyError::ConnectionNotAllowed.into()), 60 | IOErrorKind::ConnectionReset => Err(ReplyError::ConnectionNotAllowed.into()), 61 | IOErrorKind::NotConnected => Err(ReplyError::NetworkUnreachable.into()), 62 | _ => Err(e.into()), // #[error("General failure")] ? 63 | }, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rust/easytier/src/gateway/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use tokio::task::JoinSet; 3 | 4 | use crate::common::global_ctx::ArcGlobalCtx; 5 | 6 | pub mod icmp_proxy; 7 | pub mod ip_reassembler; 8 | pub mod tcp_proxy; 9 | #[cfg(feature = "smoltcp")] 10 | pub mod tokio_smoltcp; 11 | pub mod udp_proxy; 12 | 13 | #[cfg(feature = "socks5")] 14 | pub mod fast_socks5; 15 | #[cfg(feature = "socks5")] 16 | pub mod socks5; 17 | 18 | pub mod kcp_proxy; 19 | 20 | #[derive(Debug)] 21 | pub(crate) struct CidrSet { 22 | global_ctx: ArcGlobalCtx, 23 | cidr_set: Arc>>, 24 | tasks: JoinSet<()>, 25 | } 26 | 27 | impl CidrSet { 28 | pub fn new(global_ctx: ArcGlobalCtx) -> Self { 29 | let mut ret = Self { 30 | global_ctx, 31 | cidr_set: Arc::new(Mutex::new(vec![])), 32 | tasks: JoinSet::new(), 33 | }; 34 | ret.run_cidr_updater(); 35 | ret 36 | } 37 | 38 | fn run_cidr_updater(&mut self) { 39 | let global_ctx = self.global_ctx.clone(); 40 | let cidr_set = self.cidr_set.clone(); 41 | self.tasks.spawn(async move { 42 | let mut last_cidrs = vec![]; 43 | loop { 44 | let cidrs = global_ctx.get_proxy_cidrs(); 45 | if cidrs != last_cidrs { 46 | last_cidrs = cidrs.clone(); 47 | cidr_set.lock().unwrap().clear(); 48 | for cidr in cidrs.iter() { 49 | cidr_set.lock().unwrap().push(cidr.clone()); 50 | } 51 | } 52 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 53 | } 54 | }); 55 | } 56 | 57 | pub fn contains_v4(&self, ip: std::net::Ipv4Addr) -> bool { 58 | let ip = ip.into(); 59 | let s = self.cidr_set.lock().unwrap(); 60 | for cidr in s.iter() { 61 | if cidr.contains(&ip) { 62 | return true; 63 | } 64 | } 65 | false 66 | } 67 | 68 | pub fn is_empty(&self) -> bool { 69 | self.cidr_set.lock().unwrap().is_empty() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rust/easytier/src/gateway/tokio_smoltcp/channel_device.rs: -------------------------------------------------------------------------------- 1 | use futures::{Sink, Stream}; 2 | use smoltcp::phy::DeviceCapabilities; 3 | use std::{ 4 | io, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | use tokio::sync::mpsc::{channel, Receiver, Sender}; 9 | use tokio_util::sync::{PollSendError, PollSender}; 10 | 11 | use super::device::AsyncDevice; 12 | 13 | /// A device that send and receive packets using a channel. 14 | pub struct ChannelDevice { 15 | recv: Receiver>>, 16 | send: PollSender>, 17 | caps: DeviceCapabilities, 18 | } 19 | 20 | impl ChannelDevice { 21 | /// Make a new `ChannelDevice` with the given `recv` and `send` channels. 22 | /// 23 | /// The `caps` is used to determine the device capabilities. `DeviceCapabilities::max_transmission_unit` must be set. 24 | pub fn new(caps: DeviceCapabilities) -> (Self, Sender>>, Receiver>) { 25 | let (tx1, rx1) = channel(1000); 26 | let (tx2, rx2) = channel(1000); 27 | ( 28 | ChannelDevice { 29 | send: PollSender::new(tx1), 30 | recv: rx2, 31 | caps, 32 | }, 33 | tx2, 34 | rx1, 35 | ) 36 | } 37 | } 38 | 39 | impl Stream for ChannelDevice { 40 | type Item = io::Result>; 41 | 42 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 43 | self.recv.poll_recv(cx) 44 | } 45 | } 46 | 47 | fn map_err(e: PollSendError>) -> io::Error { 48 | io::Error::new(io::ErrorKind::Other, e) 49 | } 50 | 51 | impl Sink> for ChannelDevice { 52 | type Error = io::Error; 53 | 54 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 55 | self.send.poll_reserve(cx).map_err(map_err) 56 | } 57 | 58 | fn start_send(mut self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { 59 | self.send.send_item(item).map_err(map_err) 60 | } 61 | 62 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 63 | self.send.poll_reserve(cx).map_err(map_err) 64 | } 65 | 66 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 67 | Poll::Ready(Ok(())) 68 | } 69 | } 70 | 71 | impl AsyncDevice for ChannelDevice { 72 | fn capabilities(&self) -> &DeviceCapabilities { 73 | &self.caps 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rust/easytier/src/instance/dns_server/mod.rs: -------------------------------------------------------------------------------- 1 | // This module is copy and modified from https://github.com/fanyang89/libdns 2 | pub(crate) mod config; 3 | pub(crate) mod server; 4 | 5 | pub mod client_instance; 6 | pub mod runner; 7 | pub mod server_instance; 8 | pub mod system_config; 9 | 10 | #[cfg(test)] 11 | mod tests; 12 | 13 | pub static MAGIC_DNS_INSTANCE_ADDR: &str = "tcp://127.0.0.1:49813"; 14 | pub static MAGIC_DNS_FAKE_IP: &str = "100.100.100.101"; 15 | pub static DEFAULT_ET_DNS_ZONE: &str = "et.net."; 16 | -------------------------------------------------------------------------------- /rust/easytier/src/instance/dns_server/system_config/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | pub mod linux; 3 | 4 | #[cfg(target_os = "windows")] 5 | pub mod windows; 6 | 7 | #[cfg(target_os = "macos")] 8 | pub mod darwin; 9 | 10 | #[derive(Default, Debug)] 11 | pub struct OSConfig { 12 | pub nameservers: Vec, 13 | pub search_domains: Vec, 14 | pub match_domains: Vec, 15 | } 16 | 17 | pub trait SystemConfig: Send + Sync { 18 | fn set_dns(&self, cfg: &OSConfig) -> std::io::Result<()>; 19 | fn close(&self) -> std::io::Result<()>; 20 | } 21 | -------------------------------------------------------------------------------- /rust/easytier/src/instance/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dns_server; 2 | pub mod instance; 3 | pub mod listeners; 4 | 5 | #[cfg(feature = "tun")] 6 | pub mod virtual_nic; 7 | -------------------------------------------------------------------------------- /rust/easytier/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | mod arch; 4 | mod gateway; 5 | mod instance; 6 | mod peer_center; 7 | mod vpn_portal; 8 | 9 | pub mod common; 10 | pub mod connector; 11 | pub mod launcher; 12 | pub mod peers; 13 | pub mod proto; 14 | pub mod tunnel; 15 | pub mod utils; 16 | pub mod web_client; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | 21 | pub const VERSION: &str = common::constants::EASYTIER_VERSION; 22 | rust_i18n::i18n!("locales", fallback = "en"); 23 | -------------------------------------------------------------------------------- /rust/easytier/src/peer_center/mod.rs: -------------------------------------------------------------------------------- 1 | // peer_center is used to collect peer info into one peer node. 2 | // the center node is selected with the following rules: 3 | // 1. has smallest peer id 4 | // 2. TODO: has allow_to_be_center peer feature 5 | // peer center is not guaranteed to be stable and can be changed when peer enter or leave. 6 | // it's used to reduce the cost to exchange infos between peers. 7 | 8 | use std::collections::BTreeMap; 9 | 10 | use crate::proto::cli::PeerInfo; 11 | use crate::proto::peer_rpc::{DirectConnectedPeerInfo, PeerInfoForGlobalMap}; 12 | 13 | pub mod instance; 14 | mod server; 15 | 16 | #[derive(thiserror::Error, Debug, serde::Deserialize, serde::Serialize)] 17 | pub enum Error { 18 | #[error("Digest not match, need provide full peer info to center server.")] 19 | DigestMismatch, 20 | #[error("Not center server")] 21 | NotCenterServer, 22 | } 23 | 24 | pub type Digest = u64; 25 | 26 | impl From> for PeerInfoForGlobalMap { 27 | fn from(peers: Vec) -> Self { 28 | let mut peer_map = BTreeMap::new(); 29 | for peer in peers { 30 | let Some(min_lat) = peer 31 | .conns 32 | .iter() 33 | .map(|conn| conn.stats.as_ref().unwrap().latency_us) 34 | .min() 35 | else { 36 | continue; 37 | }; 38 | 39 | let dp_info = DirectConnectedPeerInfo { 40 | latency_ms: std::cmp::max(1, (min_lat as u32 / 1000) as i32), 41 | }; 42 | 43 | // sort conn info so hash result is stable 44 | peer_map.insert(peer.peer_id, dp_info); 45 | } 46 | PeerInfoForGlobalMap { 47 | direct_peers: peer_map, 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rust/easytier/src/peers/encrypt/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::tunnel::packet_def::ZCPacket; 2 | 3 | #[cfg(feature = "wireguard")] 4 | pub mod ring_aes_gcm; 5 | 6 | #[cfg(feature = "aes-gcm")] 7 | pub mod aes_gcm; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("packet is too short. len: {0}")] 12 | PacketTooShort(usize), 13 | #[error("decryption failed")] 14 | DecryptionFailed, 15 | #[error("encryption failed")] 16 | EncryptionFailed, 17 | #[error("invalid tag. tag: {0:?}")] 18 | InvalidTag(Vec), 19 | } 20 | 21 | pub trait Encryptor: Send + Sync + 'static { 22 | fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>; 23 | fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>; 24 | } 25 | 26 | pub struct NullCipher; 27 | 28 | impl Encryptor for NullCipher { 29 | fn encrypt(&self, _zc_packet: &mut ZCPacket) -> Result<(), Error> { 30 | Ok(()) 31 | } 32 | 33 | fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { 34 | let pm_header = zc_packet.peer_manager_header().unwrap(); 35 | if pm_header.is_encrypted() { 36 | return Err(Error::DecryptionFailed); 37 | } else { 38 | Ok(()) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rust/easytier/src/peers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod peer; 2 | // pub mod peer_conn; 3 | pub mod peer_conn; 4 | pub mod peer_conn_ping; 5 | pub mod peer_manager; 6 | pub mod peer_map; 7 | pub mod peer_ospf_route; 8 | pub mod peer_rpc; 9 | pub mod peer_rpc_service; 10 | pub mod route_trait; 11 | pub mod rpc_service; 12 | 13 | pub mod foreign_network_client; 14 | pub mod foreign_network_manager; 15 | 16 | pub mod encrypt; 17 | 18 | pub mod peer_task; 19 | 20 | #[cfg(test)] 21 | pub mod tests; 22 | 23 | use crate::tunnel::packet_def::ZCPacket; 24 | 25 | #[async_trait::async_trait] 26 | #[auto_impl::auto_impl(Arc)] 27 | pub trait PeerPacketFilter { 28 | async fn try_process_packet_from_peer(&self, _zc_packet: ZCPacket) -> Option { 29 | Some(_zc_packet) 30 | } 31 | } 32 | 33 | #[async_trait::async_trait] 34 | #[auto_impl::auto_impl(Arc)] 35 | pub trait NicPacketFilter { 36 | async fn try_process_packet_from_nic(&self, data: &mut ZCPacket) -> bool; 37 | 38 | fn id(&self) -> String { 39 | format!("{:p}", self) 40 | } 41 | } 42 | 43 | type BoxPeerPacketFilter = Box; 44 | type BoxNicPacketFilter = Box; 45 | 46 | // pub type PacketRecvChan = tachyonix::Sender; 47 | // pub type PacketRecvChanReceiver = tachyonix::Receiver; 48 | // pub fn create_packet_recv_chan() -> (PacketRecvChan, PacketRecvChanReceiver) { 49 | // tachyonix::channel(128) 50 | // } 51 | pub type PacketRecvChan = tokio::sync::mpsc::Sender; 52 | pub type PacketRecvChanReceiver = tokio::sync::mpsc::Receiver; 53 | pub fn create_packet_recv_chan() -> (PacketRecvChan, PacketRecvChanReceiver) { 54 | tokio::sync::mpsc::channel(128) 55 | } 56 | pub async fn recv_packet_from_chan( 57 | packet_recv_chan_receiver: &mut PacketRecvChanReceiver, 58 | ) -> Result { 59 | packet_recv_chan_receiver 60 | .recv() 61 | .await 62 | .ok_or(anyhow::anyhow!("recv_packet_from_chan failed")) 63 | } 64 | -------------------------------------------------------------------------------- /rust/easytier/src/peers/peer_rpc_service.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use crate::{ 4 | common::global_ctx::ArcGlobalCtx, 5 | proto::{ 6 | common::Void, 7 | peer_rpc::{ 8 | DirectConnectorRpc, GetIpListRequest, GetIpListResponse, SendV6HolePunchPacketRequest, 9 | }, 10 | rpc_types::{self, controller::BaseController}, 11 | }, 12 | tunnel::udp, 13 | }; 14 | 15 | #[derive(Clone)] 16 | pub struct DirectConnectorManagerRpcServer { 17 | // TODO: this only cache for one src peer, should make it global 18 | global_ctx: ArcGlobalCtx, 19 | } 20 | 21 | #[async_trait::async_trait] 22 | impl DirectConnectorRpc for DirectConnectorManagerRpcServer { 23 | type Controller = BaseController; 24 | 25 | async fn get_ip_list( 26 | &self, 27 | _: BaseController, 28 | _: GetIpListRequest, 29 | ) -> rpc_types::error::Result { 30 | let mut ret = self.global_ctx.get_ip_collector().collect_ip_addrs().await; 31 | ret.listeners = self 32 | .global_ctx 33 | .config 34 | .get_mapped_listeners() 35 | .into_iter() 36 | .chain(self.global_ctx.get_running_listeners().into_iter()) 37 | .map(Into::into) 38 | .collect(); 39 | tracing::trace!( 40 | "get_ip_list: public_ipv4: {:?}, public_ipv6: {:?}, listeners: {:?}", 41 | ret.public_ipv4, 42 | ret.public_ipv6, 43 | ret.listeners 44 | ); 45 | Ok(ret) 46 | } 47 | 48 | async fn send_v6_hole_punch_packet( 49 | &self, 50 | _: BaseController, 51 | req: SendV6HolePunchPacketRequest, 52 | ) -> rpc_types::error::Result { 53 | let listener_port = req.listener_port as u16; 54 | let SocketAddr::V6(connector_addr) = req 55 | .connector_addr 56 | .ok_or(anyhow::anyhow!("connector_addr is required"))? 57 | .into() 58 | else { 59 | return Err(anyhow::anyhow!("connector_addr is not a v6 address").into()); 60 | }; 61 | 62 | tracing::info!( 63 | "Sending v6 hole punch packet to {} from listener port {}", 64 | connector_addr, 65 | listener_port 66 | ); 67 | 68 | // send 3 packets to the connector 69 | for _ in 0..3 { 70 | udp::send_v6_hole_punch_packet(listener_port, connector_addr).await?; 71 | tokio::time::sleep(std::time::Duration::from_millis(30)).await; 72 | } 73 | Ok(Default::default()) 74 | } 75 | } 76 | 77 | impl DirectConnectorManagerRpcServer { 78 | pub fn new(global_ctx: ArcGlobalCtx) -> Self { 79 | Self { global_ctx } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package error; 3 | 4 | message OtherError { string error_message = 1; } 5 | 6 | message InvalidMethodIndex { 7 | string service_name = 1; 8 | uint32 method_index = 2; 9 | } 10 | 11 | message InvalidService { string service_name = 1; } 12 | 13 | message ProstDecodeError {} 14 | 15 | message ProstEncodeError {} 16 | 17 | message ExecuteError { string error_message = 1; } 18 | 19 | message MalformatRpcPacket { string error_message = 1; } 20 | 21 | message Timeout { string error_message = 1; } 22 | 23 | message Error { 24 | oneof error_kind { 25 | OtherError other_error = 1; 26 | InvalidMethodIndex invalid_method_index = 2; 27 | InvalidService invalid_service = 3; 28 | ProstDecodeError prost_decode_error = 4; 29 | ProstEncodeError prost_encode_error = 5; 30 | ExecuteError execute_error = 6; 31 | MalformatRpcPacket malformat_rpc_packet = 7; 32 | Timeout timeout = 8; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/magic_dns.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | import "common.proto"; 5 | import "cli.proto"; 6 | 7 | package magic_dns; 8 | 9 | message DnsRecordA { 10 | string name = 1; 11 | common.Ipv4Addr value = 2; 12 | int32 ttl = 3; 13 | } 14 | 15 | message DnsRecordSOA { 16 | string name = 1; 17 | string value = 2; 18 | } 19 | 20 | message DnsRecord { 21 | oneof record { 22 | DnsRecordA a = 1; 23 | DnsRecordSOA soa = 2; 24 | } 25 | } 26 | 27 | message DnsRecordList { 28 | repeated DnsRecord records = 1; 29 | } 30 | 31 | message UpdateDnsRecordRequest { 32 | string zone = 1; 33 | repeated cli.Route routes = 2; 34 | } 35 | 36 | message GetDnsRecordResponse { 37 | map records = 1; 38 | } 39 | 40 | message HandshakeRequest {} 41 | 42 | message HandshakeResponse {} 43 | 44 | service MagicDnsServerRpc { 45 | rpc Handshake(HandshakeRequest) returns (HandshakeResponse) {} 46 | rpc Heartbeat(common.Void) returns (common.Void) {} 47 | rpc UpdateDnsRecord(UpdateDnsRecordRequest) returns (common.Void) {} 48 | rpc GetDnsRecord(common.Void) returns (GetDnsRecordResponse) {} 49 | } 50 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/magic_dns.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/magic_dns.rs")); 2 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rpc_impl; 2 | pub mod rpc_types; 3 | 4 | pub mod cli; 5 | pub mod common; 6 | pub mod error; 7 | pub mod magic_dns; 8 | pub mod peer_rpc; 9 | pub mod web; 10 | 11 | #[cfg(test)] 12 | pub mod tests; 13 | 14 | const DESCRIPTOR_POOL_BYTES: &[u8] = 15 | include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")); 16 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/peer_rpc.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/peer_rpc.rs")); 2 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_impl/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::tunnel::{mpsc::MpscTunnel, Tunnel}; 2 | 3 | pub type RpcController = super::rpc_types::controller::BaseController; 4 | 5 | pub mod bidirect; 6 | pub mod client; 7 | pub mod packet; 8 | pub mod server; 9 | pub mod service_registry; 10 | pub mod standalone; 11 | 12 | pub type Transport = MpscTunnel>; 13 | pub type RpcTransactId = i64; 14 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_types/__rt.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions used by generated code; this is *not* part of the crate's public API! 2 | use bytes; 3 | use prost; 4 | 5 | use super::controller; 6 | use super::descriptor; 7 | use super::descriptor::ServiceDescriptor; 8 | use super::error; 9 | use super::handler; 10 | use super::handler::Handler; 11 | 12 | /// Efficiently decode a particular message type from a byte buffer. 13 | pub fn decode(buf: bytes::Bytes) -> error::Result 14 | where 15 | M: prost::Message + Default, 16 | { 17 | let message = prost::Message::decode(buf)?; 18 | Ok(message) 19 | } 20 | 21 | /// Efficiently encode a particular message into a byte buffer. 22 | pub fn encode(message: M) -> error::Result 23 | where 24 | M: prost::Message, 25 | { 26 | let len = prost::Message::encoded_len(&message); 27 | let mut buf = ::bytes::BytesMut::with_capacity(len); 28 | prost::Message::encode(&message, &mut buf)?; 29 | Ok(buf.freeze()) 30 | } 31 | 32 | pub async fn call_method( 33 | handler: H, 34 | ctrl: H::Controller, 35 | method: ::Method, 36 | input: I, 37 | ) -> super::error::Result 38 | where 39 | H: handler::Handler, 40 | I: prost::Message, 41 | O: prost::Message + Default, 42 | { 43 | type Error = super::error::Error; 44 | let input_bytes = encode(input)?; 45 | let ret_msg = handler.call(ctrl, method, input_bytes).await?; 46 | decode(ret_msg) 47 | } 48 | 49 | pub trait RpcClientFactory: Clone + Send + Sync + 'static { 50 | type Descriptor: ServiceDescriptor + Default; 51 | type ClientImpl; 52 | type Controller: controller::Controller; 53 | 54 | fn new( 55 | handler: impl Handler, 56 | ) -> Self::ClientImpl; 57 | } 58 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_types/descriptor.rs: -------------------------------------------------------------------------------- 1 | //! Traits for defining generic service descriptor definitions. 2 | //! 3 | //! These traits are built on the assumption that some form of code generation is being used (e.g. 4 | //! using only `&'static str`s) but it's of course possible to implement these traits manually. 5 | use std::any; 6 | use std::fmt; 7 | 8 | /// A descriptor for an available RPC service. 9 | pub trait ServiceDescriptor: Clone + fmt::Debug + Send + Sync { 10 | /// The associated type of method descriptors. 11 | type Method: MethodDescriptor + fmt::Debug + TryFrom; 12 | 13 | /// The name of the service, used in Rust code and perhaps for human readability. 14 | fn name(&self) -> &'static str; 15 | 16 | /// The raw protobuf name of the service. 17 | fn proto_name(&self) -> &'static str; 18 | 19 | /// The package name of the service. 20 | fn package(&self) -> &'static str { 21 | "" 22 | } 23 | 24 | /// All of the available methods on the service. 25 | fn methods(&self) -> &'static [Self::Method]; 26 | } 27 | 28 | /// A descriptor for a method available on an RPC service. 29 | pub trait MethodDescriptor: Clone + Copy + fmt::Debug + Send + Sync { 30 | /// The name of the service, used in Rust code and perhaps for human readability. 31 | fn name(&self) -> &'static str; 32 | 33 | /// The raw protobuf name of the service. 34 | fn proto_name(&self) -> &'static str; 35 | 36 | /// The Rust `TypeId` for the input that this method accepts. 37 | fn input_type(&self) -> any::TypeId; 38 | 39 | /// The raw protobuf name for the input type that this method accepts. 40 | fn input_proto_type(&self) -> &'static str; 41 | 42 | /// The Rust `TypeId` for the output that this method produces. 43 | fn output_type(&self) -> any::TypeId; 44 | 45 | /// The raw protobuf name for the output type that this method produces. 46 | fn output_proto_type(&self) -> &'static str; 47 | 48 | /// The index of the method in the service descriptor. 49 | fn index(&self) -> u8; 50 | } 51 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_types/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type definitions for errors that can occur during RPC interactions. 2 | use std::result; 3 | 4 | use prost; 5 | use thiserror; 6 | 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum Error { 9 | #[error("rust tun error {0}")] 10 | ExecutionError(#[from] anyhow::Error), 11 | 12 | #[error("Decode error: {0}")] 13 | DecodeError(#[from] prost::DecodeError), 14 | 15 | #[error("Encode error: {0}")] 16 | EncodeError(#[from] prost::EncodeError), 17 | 18 | #[error("Invalid method index: {0}, service: {1}")] 19 | InvalidMethodIndex(u8, String), 20 | 21 | #[error("Invalid service name: {0}, proto name: {1}")] 22 | InvalidServiceKey(String, String), 23 | 24 | #[error("Invalid packet: {0}")] 25 | MalformatRpcPacket(String), 26 | 27 | #[error("Timeout: {0}")] 28 | Timeout(#[from] tokio::time::error::Elapsed), 29 | 30 | #[error("Tunnel error: {0}")] 31 | TunnelError(#[from] crate::tunnel::TunnelError), 32 | 33 | #[error("Shutdown")] 34 | Shutdown, 35 | } 36 | 37 | pub type Result = result::Result; 38 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_types/handler.rs: -------------------------------------------------------------------------------- 1 | //! Traits for defining generic RPC handlers. 2 | use super::{ 3 | controller::Controller, 4 | descriptor::{self, ServiceDescriptor}, 5 | }; 6 | use bytes; 7 | 8 | /// An implementation of a specific RPC handler. 9 | /// 10 | /// This can be an actual implementation of a service, or something that will send a request over 11 | /// a network to fulfill a request. 12 | #[async_trait::async_trait] 13 | pub trait Handler: Clone + Send + Sync + 'static { 14 | /// The service descriptor for the service whose requests this handler can handle. 15 | type Descriptor: descriptor::ServiceDescriptor + Default; 16 | 17 | type Controller: super::controller::Controller; 18 | /// 19 | 20 | /// Perform a raw call to the specified service and method. 21 | async fn call( 22 | &self, 23 | ctrl: Self::Controller, 24 | method: ::Method, 25 | input: bytes::Bytes, 26 | ) -> super::error::Result; 27 | 28 | fn service_descriptor(&self) -> Self::Descriptor { 29 | Self::Descriptor::default() 30 | } 31 | 32 | fn get_method_from_index( 33 | &self, 34 | index: u8, 35 | ) -> super::error::Result<::Method> { 36 | let desc = self.service_descriptor(); 37 | ::Method::try_from(index) 38 | .map_err(|_| super::error::Error::InvalidMethodIndex(index, desc.name().to_string())) 39 | } 40 | } 41 | 42 | #[async_trait::async_trait] 43 | pub trait HandlerExt: Send + Sync + 'static { 44 | type Controller; 45 | 46 | async fn call_method( 47 | &self, 48 | ctrl: Self::Controller, 49 | method_index: u8, 50 | input: bytes::Bytes, 51 | ) -> super::error::Result; 52 | } 53 | 54 | #[async_trait::async_trait] 55 | impl> HandlerExt for T { 56 | type Controller = C; 57 | 58 | async fn call_method( 59 | &self, 60 | ctrl: Self::Controller, 61 | method_index: u8, 62 | input: bytes::Bytes, 63 | ) -> super::error::Result { 64 | let method = self.get_method_from_index(method_index)?; 65 | self.call(ctrl, method, input).await 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/rpc_types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod __rt; 2 | pub mod controller; 3 | pub mod descriptor; 4 | pub mod error; 5 | pub mod handler; 6 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/tests.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package tests; 4 | 5 | /// The Greeting service. This service is used to generate greetings for various 6 | /// use-cases. 7 | service Greeting { 8 | // Generates a "hello" greeting based on the supplied info. 9 | rpc SayHello(SayHelloRequest) returns (SayHelloResponse); 10 | // Generates a "goodbye" greeting based on the supplied info. 11 | rpc SayGoodbye(SayGoodbyeRequest) returns (SayGoodbyeResponse); 12 | } 13 | 14 | // The request for an `Greeting.SayHello` call. 15 | message SayHelloRequest { string name = 1; } 16 | 17 | // The response for an `Greeting.SayHello` call. 18 | message SayHelloResponse { string greeting = 1; } 19 | 20 | // The request for an `Greeting.SayGoodbye` call. 21 | message SayGoodbyeRequest { string name = 1; } 22 | 23 | // The response for an `Greeting.SayGoodbye` call. 24 | message SayGoodbyeResponse { string greeting = 1; } 25 | -------------------------------------------------------------------------------- /rust/easytier/src/proto/web.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/web.rs")); 2 | -------------------------------------------------------------------------------- /rust/easytier/src/vpn_portal/mod.rs: -------------------------------------------------------------------------------- 1 | // with vpn portal, user can use other vpn client to connect to easytier servers 2 | // without installing easytier. 3 | // these vpn client include: 4 | // 1. wireguard 5 | // 2. openvpn (TODO) 6 | // 3. shadowsocks (TODO) 7 | 8 | use std::sync::Arc; 9 | 10 | use crate::{common::global_ctx::ArcGlobalCtx, peers::peer_manager::PeerManager}; 11 | 12 | #[cfg(feature = "wireguard")] 13 | pub mod wireguard; 14 | 15 | #[async_trait::async_trait] 16 | pub trait VpnPortal: Send + Sync { 17 | async fn start( 18 | &mut self, 19 | global_ctx: ArcGlobalCtx, 20 | peer_mgr: Arc, 21 | ) -> anyhow::Result<()>; 22 | async fn dump_client_config(&self, peer_mgr: Arc) -> String; 23 | fn name(&self) -> String; 24 | async fn list_clients(&self) -> Vec; 25 | } 26 | 27 | pub struct NullVpnPortal; 28 | 29 | #[async_trait::async_trait] 30 | impl VpnPortal for NullVpnPortal { 31 | async fn start( 32 | &mut self, 33 | _global_ctx: ArcGlobalCtx, 34 | _peer_mgr: Arc, 35 | ) -> anyhow::Result<()> { 36 | Ok(()) 37 | } 38 | 39 | async fn dump_client_config(&self, _peer_mgr: Arc) -> String { 40 | "".to_string() 41 | } 42 | 43 | fn name(&self) -> String { 44 | "null".to_string() 45 | } 46 | 47 | async fn list_clients(&self) -> Vec { 48 | vec![] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rust/easytier/src/web_client/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{common::scoped_task::ScopedTask, tunnel::TunnelConnector}; 4 | 5 | pub mod controller; 6 | pub mod session; 7 | 8 | pub struct WebClient { 9 | controller: Arc, 10 | tasks: ScopedTask<()>, 11 | } 12 | 13 | impl WebClient { 14 | pub fn new(connector: T, token: S, hostname: H) -> Self { 15 | let controller = Arc::new(controller::Controller::new(token.to_string(), 16 | hostname.to_string())); 17 | 18 | let controller_clone = controller.clone(); 19 | let tasks = ScopedTask::from(tokio::spawn(async move { 20 | Self::routine(controller_clone, Box::new(connector)).await; 21 | })); 22 | 23 | WebClient { controller, tasks } 24 | } 25 | 26 | async fn routine( 27 | controller: Arc, 28 | mut connector: Box, 29 | ) { 30 | loop { 31 | let conn = match connector.connect().await { 32 | Ok(conn) => conn, 33 | Err(e) => { 34 | println!( 35 | "Failed to connect to the server ({}), retrying in 5 seconds...", 36 | e 37 | ); 38 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 39 | continue; 40 | } 41 | }; 42 | 43 | println!("Successfully connected to {:?}", conn.info()); 44 | 45 | let mut session = session::Session::new(conn, controller.clone()); 46 | session.wait().await; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rust/easytier/third_party/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/Packet.dll -------------------------------------------------------------------------------- /rust/easytier/third_party/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/Packet.lib -------------------------------------------------------------------------------- /rust/easytier/third_party/arm64/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/arm64/Packet.dll -------------------------------------------------------------------------------- /rust/easytier/third_party/arm64/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/arm64/Packet.lib -------------------------------------------------------------------------------- /rust/easytier/third_party/arm64/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/arm64/wintun.dll -------------------------------------------------------------------------------- /rust/easytier/third_party/i686/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/i686/Packet.dll -------------------------------------------------------------------------------- /rust/easytier/third_party/i686/Packet.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/i686/Packet.lib -------------------------------------------------------------------------------- /rust/easytier/third_party/i686/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/i686/wintun.dll -------------------------------------------------------------------------------- /rust/easytier/third_party/wintun.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/rust/easytier/third_party/wintun.dll -------------------------------------------------------------------------------- /rust/src/api/firewall.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | pub use windows::{ 3 | core::{Result, Interface, HRESULT, Error}, 4 | Win32::Foundation::VARIANT_BOOL, 5 | Win32::{ 6 | NetworkManagement::WindowsFirewall::{ 7 | INetFwPolicy2, NetFwPolicy2, NET_FW_PROFILE2_DOMAIN, NET_FW_PROFILE2_PRIVATE, 8 | NET_FW_PROFILE2_PUBLIC, 9 | }, 10 | System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED}, 11 | }, 12 | }; 13 | #[cfg(target_os = "windows")] 14 | pub fn get_firewall_status(profile_index: u32) -> Result { 15 | unsafe { 16 | CoInitializeEx(None, COINIT_APARTMENTTHREADED)?; 17 | 18 | let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)?; 19 | 20 | let profile_type = match profile_index { 21 | 1 => NET_FW_PROFILE2_DOMAIN, 22 | 2 => NET_FW_PROFILE2_PRIVATE, 23 | 3 => NET_FW_PROFILE2_PUBLIC, 24 | _ => return Err(Error::new(HRESULT(-1), "Invalid profile index".into())), 25 | }; 26 | 27 | // 正确调用 COM 接口方法 28 | let enabled = policy.get_FirewallEnabled(profile_type)?; 29 | Ok(enabled.as_bool()) 30 | } 31 | } 32 | /// 不是window就返回false 33 | #[cfg(not(target_os = "windows"))] 34 | pub fn get_firewall_status(profile_index: u32) -> Result { 35 | Ok(false) 36 | } 37 | #[cfg(target_os = "windows")] 38 | pub fn set_firewall_status(profile_index: u32, enable: bool) -> Result<()> { 39 | // 不是window就返回false 40 | if!cfg!(windows) { 41 | return Ok(()); 42 | } 43 | unsafe { 44 | CoInitializeEx(None, COINIT_APARTMENTTHREADED)?; 45 | 46 | let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)?; 47 | 48 | let profile_type = match profile_index { 49 | 1 => NET_FW_PROFILE2_DOMAIN, 50 | 2 => NET_FW_PROFILE2_PRIVATE, 51 | 3 => NET_FW_PROFILE2_PUBLIC, 52 | _ => return Err(Error::new(HRESULT(-1), "Invalid profile index".into())), 53 | }; 54 | 55 | policy.put_FirewallEnabled(profile_type, VARIANT_BOOL::from(enable))?; 56 | Ok(()) 57 | } 58 | } 59 | 60 | /// 不是window就返回false 61 | #[cfg(not(target_os = "windows"))] 62 | pub fn set_firewall_status(profile_index: u32, enable: bool) -> Result<(), std::io::Error> { 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /rust/src/api/hops.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::process::Command; 3 | 4 | #[cfg(target_os = "windows")] 5 | use std::os::windows::process::CommandExt; 6 | 7 | #[cfg(target_os = "windows")] 8 | pub fn get_all_interfaces_metrics() -> io::Result> { 9 | 10 | let output = Command::new("netsh") 11 | .args(&["interface", "ipv4", "show", "interfaces"]) 12 | .creation_flags(0x08000000) // CREATE_NO_WINDOW 13 | .output()?; 14 | 15 | if !output.status.success() { 16 | return Err(io::Error::new( 17 | io::ErrorKind::Other, 18 | String::from_utf8_lossy(&output.stderr).to_string(), 19 | )); 20 | } 21 | 22 | let output_str = String::from_utf8_lossy(&output.stdout); 23 | let mut interfaces = Vec::new(); 24 | 25 | for line in output_str.lines().skip(3) { // 跳过前两行表头 26 | if line.trim().is_empty() { 27 | continue; 28 | } 29 | 30 | let parts: Vec<&str> = line.split_whitespace().collect(); 31 | if parts.len() >= 5 { 32 | let name = parts[4..].join(" "); 33 | let metric = parts[1].parse::().unwrap_or(0); 34 | interfaces.push((name, metric)); 35 | } 36 | } 37 | 38 | Ok(interfaces) 39 | } 40 | #[cfg(not(target_os = "windows"))] 41 | pub fn get_all_interfaces_metrics() -> io::Result> { 42 | Ok(Vec::new()) 43 | } 44 | 45 | #[cfg(target_os = "windows")] 46 | pub fn set_interface_metric(interface_name: &str, metric: u32) -> io::Result<()> { 47 | let output = Command::new("netsh") 48 | .args(&["interface", "ipv4", "set", "interface", interface_name, "metric=", &metric.to_string()]) 49 | .creation_flags(0x08000000) // CREATE_NO_WINDOW 50 | .output()?; 51 | 52 | if output.status.success() { 53 | Ok(()) 54 | } else { 55 | Err(io::Error::new( 56 | io::ErrorKind::Other, 57 | String::from_utf8_lossy(&output.stderr).to_string(), 58 | )) 59 | } 60 | } 61 | 62 | #[cfg(not(target_os = "windows"))] 63 | pub fn set_interface_metric(_interface_name: &str, _metric: u32) -> io::Result<()> { 64 | Ok(()) 65 | } -------------------------------------------------------------------------------- /rust/src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod simple; 2 | pub mod firewall; 3 | pub mod hops; -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | mod frb_generated; 3 | -------------------------------------------------------------------------------- /rust_builder/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /rust_builder/README.md: -------------------------------------------------------------------------------- 1 | Please ignore this folder, which is just glue to build Rust with Flutter. -------------------------------------------------------------------------------- /rust_builder/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /rust_builder/android/build.gradle: -------------------------------------------------------------------------------- 1 | // The Android Gradle Plugin builds the native code with the Android NDK. 2 | 3 | group 'com.flutter_rust_bridge.rust_lib_astral' 4 | version '1.0' 5 | 6 | buildscript { 7 | repositories { 8 | google() 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | // The Android Gradle Plugin knows how to build native code with the NDK. 14 | classpath 'com.android.tools.build:gradle:7.3.0' 15 | } 16 | } 17 | 18 | rootProject.allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | 27 | android { 28 | if (project.android.hasProperty("namespace")) { 29 | namespace 'com.flutter_rust_bridge.rust_lib_astral' 30 | } 31 | 32 | // Bumping the plugin compileSdkVersion requires all clients of this plugin 33 | // to bump the version in their app. 34 | compileSdkVersion 33 35 | 36 | // Use the NDK version 37 | // declared in /android/app/build.gradle file of the Flutter project. 38 | // Replace it with a version number if this plugin requires a specfic NDK version. 39 | // (e.g. ndkVersion "23.1.7779620") 40 | ndkVersion android.ndkVersion 41 | 42 | compileOptions { 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | 47 | defaultConfig { 48 | minSdkVersion 19 49 | } 50 | } 51 | 52 | apply from: "../cargokit/gradle/plugin.gradle" 53 | cargokit { 54 | manifestDir = "../../rust" 55 | libname = "rust_lib_astral" 56 | } 57 | -------------------------------------------------------------------------------- /rust_builder/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rust_lib_astral' 2 | -------------------------------------------------------------------------------- /rust_builder/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /rust_builder/cargokit/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .dart_tool 3 | *.iml 4 | !pubspec.lock 5 | -------------------------------------------------------------------------------- /rust_builder/cargokit/LICENSE: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | Copyright 2022 Matej Knopp 5 | 6 | ================================================================================ 7 | 8 | MIT LICENSE 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | of the Software, and to permit persons to whom the Software is furnished to do 15 | so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 23 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | ================================================================================ 28 | 29 | APACHE LICENSE, VERSION 2.0 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. 42 | 43 | -------------------------------------------------------------------------------- /rust_builder/cargokit/README: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | Experimental repository to provide glue for seamlessly integrating cargo build 5 | with flutter plugins and packages. 6 | 7 | See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ 8 | for a tutorial on how to use Cargokit. 9 | 10 | Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. 11 | 12 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASEDIR=$(dirname "$0") 5 | 6 | # Workaround for https://github.com/dart-lang/pub/issues/4010 7 | BASEDIR=$(cd "$BASEDIR" ; pwd -P) 8 | 9 | # Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project 10 | NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` 11 | 12 | export PATH=${NEW_PATH%?} # remove trailing : 13 | 14 | env 15 | 16 | # Platform name (macosx, iphoneos, iphonesimulator) 17 | export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME 18 | 19 | # Arctive architectures (arm64, armv7, x86_64), space separated. 20 | export CARGOKIT_DARWIN_ARCHS=$ARCHS 21 | 22 | # Current build configuration (Debug, Release) 23 | export CARGOKIT_CONFIGURATION=$CONFIGURATION 24 | 25 | # Path to directory containing Cargo.toml. 26 | export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 27 | 28 | # Temporary directory for build artifacts. 29 | export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR 30 | 31 | # Output directory for final artifacts. 32 | export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME 33 | 34 | # Directory to store built tool artifacts. 35 | export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool 36 | 37 | # Directory inside root project. Not necessarily the top level directory of root project. 38 | export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT 39 | 40 | FLUTTER_EXPORT_BUILD_ENVIRONMENT=( 41 | "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS 42 | "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS 43 | ) 44 | 45 | for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" 46 | do 47 | if [[ -f "$path" ]]; then 48 | source "$path" 49 | fi 50 | done 51 | 52 | sh "$BASEDIR/run_build_tool.sh" build-pod "$@" 53 | 54 | # Make a symlink from built framework to phony file, which will be used as input to 55 | # build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate 56 | # attribute on custom build phase) 57 | ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" 58 | ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" 59 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/README.md: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | A sample command-line application with an entrypoint in `bin/`, library code 5 | in `lib/`, and example unit test in `test/`. 6 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This is copied from Cargokit (which is the official way to use it currently) 2 | # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | # This file configures the static analysis results for your project (errors, 5 | # warnings, and lints). 6 | # 7 | # This enables the 'recommended' set of lints from `package:lints`. 8 | # This set helps identify many issues that may lead to problems when running 9 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 10 | # style and format. 11 | # 12 | # If you want a smaller set of lints you can change this to specify 13 | # 'package:lints/core.yaml'. These are just the most critical lints 14 | # (the recommended set includes the core lints). 15 | # The core lints are also what is used by pub.dev for scoring packages. 16 | 17 | include: package:lints/recommended.yaml 18 | 19 | # Uncomment the following section to specify additional rules. 20 | 21 | linter: 22 | rules: 23 | - prefer_relative_imports 24 | - directives_ordering 25 | 26 | # analyzer: 27 | # exclude: 28 | # - path/to/excluded/files/** 29 | 30 | # For more information about the core and recommended set of lints, see 31 | # https://dart.dev/go/core-lints 32 | 33 | # For additional information about configuring this file, see 34 | # https://dart.dev/guides/language/analysis-options 35 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/bin/build_tool.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'package:build_tool/build_tool.dart' as build_tool; 5 | 6 | void main(List arguments) { 7 | build_tool.runMain(arguments); 8 | } 9 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/build_tool.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'src/build_tool.dart' as build_tool; 5 | 6 | Future runMain(List args) async { 7 | return build_tool.runMain(args); 8 | } 9 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/build_cmake.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:path/path.dart' as path; 7 | 8 | import 'artifacts_provider.dart'; 9 | import 'builder.dart'; 10 | import 'environment.dart'; 11 | import 'options.dart'; 12 | import 'target.dart'; 13 | 14 | class BuildCMake { 15 | final CargokitUserOptions userOptions; 16 | 17 | BuildCMake({required this.userOptions}); 18 | 19 | Future build() async { 20 | final targetPlatform = Environment.targetPlatform; 21 | final target = Target.forFlutterName(Environment.targetPlatform); 22 | if (target == null) { 23 | throw Exception("Unknown target platform: $targetPlatform"); 24 | } 25 | 26 | final environment = BuildEnvironment.fromEnvironment(isAndroid: false); 27 | final provider = 28 | ArtifactProvider(environment: environment, userOptions: userOptions); 29 | final artifacts = await provider.getArtifacts([target]); 30 | 31 | final libs = artifacts[target]!; 32 | 33 | for (final lib in libs) { 34 | if (lib.type == AritifactType.dylib) { 35 | File(lib.path) 36 | .copySync(path.join(Environment.outputDir, lib.finalFileName)); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/build_gradle.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:logging/logging.dart'; 7 | import 'package:path/path.dart' as path; 8 | 9 | import 'artifacts_provider.dart'; 10 | import 'builder.dart'; 11 | import 'environment.dart'; 12 | import 'options.dart'; 13 | import 'target.dart'; 14 | 15 | final log = Logger('build_gradle'); 16 | 17 | class BuildGradle { 18 | BuildGradle({required this.userOptions}); 19 | 20 | final CargokitUserOptions userOptions; 21 | 22 | Future build() async { 23 | final targets = Environment.targetPlatforms.map((arch) { 24 | final target = Target.forFlutterName(arch); 25 | if (target == null) { 26 | throw Exception( 27 | "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); 28 | } 29 | return target; 30 | }).toList(); 31 | 32 | final environment = BuildEnvironment.fromEnvironment(isAndroid: true); 33 | final provider = 34 | ArtifactProvider(environment: environment, userOptions: userOptions); 35 | final artifacts = await provider.getArtifacts(targets); 36 | 37 | for (final target in targets) { 38 | final libs = artifacts[target]!; 39 | final outputDir = path.join(Environment.outputDir, target.android!); 40 | Directory(outputDir).createSync(recursive: true); 41 | 42 | for (final lib in libs) { 43 | if (lib.type == AritifactType.dylib) { 44 | File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/cargo.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:path/path.dart' as path; 7 | import 'package:toml/toml.dart'; 8 | 9 | class ManifestException { 10 | ManifestException(this.message, {required this.fileName}); 11 | 12 | final String? fileName; 13 | final String message; 14 | 15 | @override 16 | String toString() { 17 | if (fileName != null) { 18 | return 'Failed to parse package manifest at $fileName: $message'; 19 | } else { 20 | return 'Failed to parse package manifest: $message'; 21 | } 22 | } 23 | } 24 | 25 | class CrateInfo { 26 | CrateInfo({required this.packageName}); 27 | 28 | final String packageName; 29 | 30 | static CrateInfo parseManifest(String manifest, {final String? fileName}) { 31 | final toml = TomlDocument.parse(manifest); 32 | final package = toml.toMap()['package']; 33 | if (package == null) { 34 | throw ManifestException('Missing package section', fileName: fileName); 35 | } 36 | final name = package['name']; 37 | if (name == null) { 38 | throw ManifestException('Missing package name', fileName: fileName); 39 | } 40 | return CrateInfo(packageName: name); 41 | } 42 | 43 | static CrateInfo load(String manifestDir) { 44 | final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); 45 | final manifest = manifestFile.readAsStringSync(); 46 | return parseManifest(manifest, fileName: manifestFile.path); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/environment.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | extension on String { 7 | String resolveSymlink() => File(this).resolveSymbolicLinksSync(); 8 | } 9 | 10 | class Environment { 11 | /// Current build configuration (debug or release). 12 | static String get configuration => 13 | _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); 14 | 15 | static bool get isDebug => configuration == 'debug'; 16 | static bool get isRelease => configuration == 'release'; 17 | 18 | /// Temporary directory where Rust build artifacts are placed. 19 | static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); 20 | 21 | /// Final output directory where the build artifacts are placed. 22 | static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); 23 | 24 | /// Path to the crate manifest (containing Cargo.toml). 25 | static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); 26 | 27 | /// Directory inside root project. Not necessarily root folder. Symlinks are 28 | /// not resolved on purpose. 29 | static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); 30 | 31 | // Pod 32 | 33 | /// Platform name (macosx, iphoneos, iphonesimulator). 34 | static String get darwinPlatformName => 35 | _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); 36 | 37 | /// List of architectures to build for (arm64, armv7, x86_64). 38 | static List get darwinArchs => 39 | _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); 40 | 41 | // Gradle 42 | static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); 43 | static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); 44 | static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); 45 | static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); 46 | static List get targetPlatforms => 47 | _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); 48 | 49 | // CMAKE 50 | static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); 51 | 52 | static String _getEnv(String key) { 53 | final res = Platform.environment[key]; 54 | if (res == null) { 55 | throw Exception("Missing environment variable $key"); 56 | } 57 | return res; 58 | } 59 | 60 | static String _getEnvPath(String key) { 61 | final res = _getEnv(key); 62 | if (Directory(res).existsSync()) { 63 | return res.resolveSymlink(); 64 | } else { 65 | return res; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/logging.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:logging/logging.dart'; 7 | 8 | const String kSeparator = "--"; 9 | const String kDoubleSeparator = "=="; 10 | 11 | bool _lastMessageWasSeparator = false; 12 | 13 | void _log(LogRecord rec) { 14 | final prefix = '${rec.level.name}: '; 15 | final out = rec.level == Level.SEVERE ? stderr : stdout; 16 | if (rec.message == kSeparator) { 17 | if (!_lastMessageWasSeparator) { 18 | out.write(prefix); 19 | out.writeln('-' * 80); 20 | _lastMessageWasSeparator = true; 21 | } 22 | return; 23 | } else if (rec.message == kDoubleSeparator) { 24 | out.write(prefix); 25 | out.writeln('=' * 80); 26 | _lastMessageWasSeparator = true; 27 | return; 28 | } 29 | out.write(prefix); 30 | out.writeln(rec.message); 31 | _lastMessageWasSeparator = false; 32 | } 33 | 34 | void initLogging() { 35 | Logger.root.level = Level.INFO; 36 | Logger.root.onRecord.listen((LogRecord rec) { 37 | final lines = rec.message.split('\n'); 38 | for (final line in lines) { 39 | if (line.isNotEmpty || lines.length == 1 || line != lines.last) { 40 | _log(LogRecord( 41 | rec.level, 42 | line, 43 | rec.loggerName, 44 | )); 45 | } 46 | } 47 | }); 48 | } 49 | 50 | void enableVerboseLogging() { 51 | Logger.root.level = Level.ALL; 52 | } 53 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # This is copied from Cargokit (which is the official way to use it currently) 2 | # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | name: build_tool 5 | description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. 6 | publish_to: none 7 | version: 1.0.0 8 | 9 | environment: 10 | sdk: ">=3.0.0 <4.0.0" 11 | 12 | # Add regular dependencies here. 13 | dependencies: 14 | # these are pinned on purpose because the bundle_tool_runner doesn't have 15 | # pubspec.lock. See run_build_tool.sh 16 | logging: 1.2.0 17 | path: 1.8.0 18 | version: 3.0.0 19 | collection: 1.18.0 20 | ed25519_edwards: 0.3.1 21 | hex: 0.2.0 22 | yaml: 3.1.2 23 | source_span: 1.10.0 24 | github: 9.17.0 25 | args: 2.4.2 26 | crypto: 3.0.3 27 | convert: 3.1.1 28 | http: 1.1.0 29 | toml: 0.14.0 30 | 31 | dev_dependencies: 32 | lints: ^2.1.0 33 | test: ^1.24.0 34 | -------------------------------------------------------------------------------- /rust_builder/cargokit/cmake/resolve_symlinks.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-Symlinks { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | param( 5 | [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 6 | [string] $Path 7 | ) 8 | 9 | [string] $separator = '/' 10 | [string[]] $parts = $Path.Split($separator) 11 | 12 | [string] $realPath = '' 13 | foreach ($part in $parts) { 14 | if ($realPath -and !$realPath.EndsWith($separator)) { 15 | $realPath += $separator 16 | } 17 | $realPath += $part 18 | $item = Get-Item $realPath 19 | if ($item.Target) { 20 | $realPath = $item.Target.Replace('\', '/') 21 | } 22 | } 23 | $realPath 24 | } 25 | 26 | $path=Resolve-Symlinks -Path $args[0] 27 | Write-Host $path 28 | -------------------------------------------------------------------------------- /rust_builder/cargokit/run_build_tool.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | BASEDIR=$(dirname "$0") 6 | 7 | mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" 8 | 9 | cd "$CARGOKIT_TOOL_TEMP_DIR" 10 | 11 | # Write a very simple bin package in temp folder that depends on build_tool package 12 | # from Cargokit. This is done to ensure that we don't pollute Cargokit folder 13 | # with .dart_tool contents. 14 | 15 | BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" 16 | 17 | if [[ -z $FLUTTER_ROOT ]]; then # not defined 18 | DART=dart 19 | else 20 | DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" 21 | fi 22 | 23 | cat << EOF > "pubspec.yaml" 24 | name: build_tool_runner 25 | version: 1.0.0 26 | publish_to: none 27 | 28 | environment: 29 | sdk: '>=3.0.0 <4.0.0' 30 | 31 | dependencies: 32 | build_tool: 33 | path: "$BUILD_TOOL_PKG_DIR" 34 | EOF 35 | 36 | mkdir -p "bin" 37 | 38 | cat << EOF > "bin/build_tool_runner.dart" 39 | import 'package:build_tool/build_tool.dart' as build_tool; 40 | void main(List args) { 41 | build_tool.runMain(args); 42 | } 43 | EOF 44 | 45 | # Create alias for `shasum` if it does not exist and `sha1sum` exists 46 | if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then 47 | shopt -s expand_aliases 48 | alias shasum="sha1sum" 49 | fi 50 | 51 | # Dart run will not cache any package that has a path dependency, which 52 | # is the case for our build_tool_runner. So instead we precompile the package 53 | # ourselves. 54 | # To invalidate the cached kernel we use the hash of ls -LR of the build_tool 55 | # package directory. This should be good enough, as the build_tool package 56 | # itself is not meant to have any path dependencies. 57 | 58 | if [[ "$OSTYPE" == "darwin"* ]]; then 59 | PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) 60 | else 61 | PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) 62 | fi 63 | 64 | PACKAGE_HASH_FILE=".package_hash" 65 | 66 | if [ -f "$PACKAGE_HASH_FILE" ]; then 67 | EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") 68 | if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then 69 | rm "$PACKAGE_HASH_FILE" 70 | fi 71 | fi 72 | 73 | # Run pub get if needed. 74 | if [ ! -f "$PACKAGE_HASH_FILE" ]; then 75 | "$DART" pub get --no-precompile 76 | "$DART" compile kernel bin/build_tool_runner.dart 77 | echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" 78 | fi 79 | 80 | set +e 81 | 82 | "$DART" bin/build_tool_runner.dill "$@" 83 | 84 | exit_code=$? 85 | 86 | # 253 means invalid snapshot version. 87 | if [ $exit_code == 253 ]; then 88 | "$DART" pub get --no-precompile 89 | "$DART" compile kernel bin/build_tool_runner.dart 90 | "$DART" bin/build_tool_runner.dill "$@" 91 | exit_code=$? 92 | fi 93 | 94 | exit $exit_code 95 | -------------------------------------------------------------------------------- /rust_builder/ios/Classes/dummy_file.c: -------------------------------------------------------------------------------- 1 | // This is an empty file to force CocoaPods to create a framework. 2 | -------------------------------------------------------------------------------- /rust_builder/ios/rust_lib_astral.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint rust_lib_astral.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'rust_lib_astral' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter FFI plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter FFI plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | 16 | # This will ensure the source files in Classes/ are included in the native 17 | # builds of apps using this FFI plugin. Podspec does not support relative 18 | # paths, so Classes contains a forwarder C file that relatively imports 19 | # `../src/*` so that the C sources can be shared among all target platforms. 20 | s.source = { :path => '.' } 21 | s.source_files = 'Classes/**/*' 22 | s.dependency 'Flutter' 23 | s.platform = :ios, '11.0' 24 | 25 | # Flutter.framework does not contain a i386 slice. 26 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 27 | s.swift_version = '5.0' 28 | 29 | s.script_phase = { 30 | :name => 'Build Rust library', 31 | # First argument is relative path to the `rust` folder, second is name of rust library 32 | :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_astral', 33 | :execution_position => :before_compile, 34 | :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], 35 | # Let XCode know that the static library referenced in -force_load below is 36 | # created by this build step. 37 | :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_astral.a"], 38 | } 39 | s.pod_target_xcconfig = { 40 | 'DEFINES_MODULE' => 'YES', 41 | # Flutter.framework does not contain a i386 slice. 42 | 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 43 | 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_astral.a', 44 | } 45 | end -------------------------------------------------------------------------------- /rust_builder/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have CMake 3.10 or later 2 | # installed. You should not increase this version, as doing so will cause 3 | # the plugin to fail to compile for some customers of the plugin. 4 | cmake_minimum_required(VERSION 3.10) 5 | 6 | # Project-level configuration. 7 | set(PROJECT_NAME "rust_lib_astral") 8 | project(${PROJECT_NAME} LANGUAGES CXX) 9 | 10 | include("../cargokit/cmake/cargokit.cmake") 11 | apply_cargokit(${PROJECT_NAME} ../../rust rust_lib_astral "") 12 | 13 | # List of absolute paths to libraries that should be bundled with the plugin. 14 | # This list could contain prebuilt libraries, or libraries created by an 15 | # external build triggered from this build file. 16 | set(rust_lib_astral_bundled_libraries 17 | "${${PROJECT_NAME}_cargokit_lib}" 18 | PARENT_SCOPE 19 | ) 20 | -------------------------------------------------------------------------------- /rust_builder/macos/Classes/dummy_file.c: -------------------------------------------------------------------------------- 1 | // This is an empty file to force CocoaPods to create a framework. 2 | -------------------------------------------------------------------------------- /rust_builder/macos/rust_lib_astral.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint rust_lib_astral.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'rust_lib_astral' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter FFI plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter FFI plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | 16 | # This will ensure the source files in Classes/ are included in the native 17 | # builds of apps using this FFI plugin. Podspec does not support relative 18 | # paths, so Classes contains a forwarder C file that relatively imports 19 | # `../src/*` so that the C sources can be shared among all target platforms. 20 | s.source = { :path => '.' } 21 | s.source_files = 'Classes/**/*' 22 | s.dependency 'FlutterMacOS' 23 | 24 | s.platform = :osx, '10.11' 25 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 26 | s.swift_version = '5.0' 27 | 28 | s.script_phase = { 29 | :name => 'Build Rust library', 30 | # First argument is relative path to the `rust` folder, second is name of rust library 31 | :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_astral', 32 | :execution_position => :before_compile, 33 | :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], 34 | # Let XCode know that the static library referenced in -force_load below is 35 | # created by this build step. 36 | :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_astral.a"], 37 | } 38 | s.pod_target_xcconfig = { 39 | 'DEFINES_MODULE' => 'YES', 40 | # Flutter.framework does not contain a i386 slice. 41 | 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 42 | 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_astral.a', 43 | } 44 | end -------------------------------------------------------------------------------- /rust_builder/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rust_lib_astral 2 | description: "Utility to build Rust code" 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: '>=3.3.0 <4.0.0' 8 | flutter: '>=3.3.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | 15 | dev_dependencies: 16 | ffi: ^2.0.2 17 | ffigen: ^11.0.0 18 | flutter_test: 19 | sdk: flutter 20 | flutter_lints: ^2.0.0 21 | 22 | flutter: 23 | plugin: 24 | platforms: 25 | android: 26 | ffiPlugin: true 27 | ios: 28 | ffiPlugin: true 29 | linux: 30 | ffiPlugin: true 31 | macos: 32 | ffiPlugin: true 33 | windows: 34 | ffiPlugin: true 35 | -------------------------------------------------------------------------------- /rust_builder/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /rust_builder/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have a version of Visual Studio 2 | # installed that includes CMake 3.14 or later. You should not increase this 3 | # version, as doing so will cause the plugin to fail to compile for some 4 | # customers of the plugin. 5 | cmake_minimum_required(VERSION 3.14) 6 | 7 | # Project-level configuration. 8 | set(PROJECT_NAME "rust_lib_astral") 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | include("../cargokit/cmake/cargokit.cmake") 12 | apply_cargokit(${PROJECT_NAME} ../../../../../../rust rust_lib_astral "") 13 | 14 | # List of absolute paths to libraries that should be bundled with the plugin. 15 | # This list could contain prebuilt libraries, or libraries created by an 16 | # external build triggered from this build file. 17 | set(rust_lib_astral_bundled_libraries 18 | "${${PROJECT_NAME}_cargokit_lib}" 19 | PARENT_SCOPE 20 | ) 21 | -------------------------------------------------------------------------------- /test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /vpn_service_plugin/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 28 | /pubspec.lock 29 | **/doc/api/ 30 | .dart_tool/ 31 | .flutter-plugins 32 | .flutter-plugins-dependencies 33 | build/ 34 | -------------------------------------------------------------------------------- /vpn_service_plugin/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 17 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 18 | - platform: android 19 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 20 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /vpn_service_plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /vpn_service_plugin/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /vpn_service_plugin/README.md: -------------------------------------------------------------------------------- 1 | # vpn_service_plugin 2 | 3 | A new Flutter plugin project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter 8 | [plug-in package](https://flutter.dev/to/develop-plugins), 9 | a specialized package that includes platform-specific implementation code for 10 | Android and/or iOS. 11 | 12 | For help getting started with Flutter development, view the 13 | [online documentation](https://docs.flutter.dev), which offers tutorials, 14 | samples, guidance on mobile development, and a full API reference. 15 | 16 | The plugin project was generated without specifying the `--platforms` flag, no platforms are currently supported. 17 | To add platforms, run `flutter create -t plugin --platforms .` in this directory. 18 | You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/to/pubspec-plugin-platforms. 19 | -------------------------------------------------------------------------------- /vpn_service_plugin/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /vpn_service_plugin/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /vpn_service_plugin/android/build.gradle: -------------------------------------------------------------------------------- 1 | group = "com.example.vpn_service_plugin" 2 | version = "1.0-SNAPSHOT" 3 | 4 | buildscript { 5 | ext.kotlin_version = "1.8.22" 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath("com.android.tools.build:gradle:8.7.0") 13 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: "com.android.library" 25 | apply plugin: "kotlin-android" 26 | 27 | android { 28 | namespace = "com.plugin.vpn_service_plugin" 29 | 30 | compileSdk = 35 31 | 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_11 34 | targetCompatibility = JavaVersion.VERSION_11 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = JavaVersion.VERSION_11 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += "src/main/kotlin" 43 | test.java.srcDirs += "src/test/kotlin" 44 | } 45 | 46 | defaultConfig { 47 | minSdk = 21 48 | } 49 | 50 | dependencies { 51 | testImplementation("org.jetbrains.kotlin:kotlin-test") 52 | testImplementation("org.mockito:mockito-core:5.0.0") 53 | } 54 | 55 | testOptions { 56 | unitTests.all { 57 | useJUnitPlatform() 58 | 59 | testLogging { 60 | events "passed", "skipped", "failed", "standardOut", "standardError" 61 | outputs.upToDateWhen {false} 62 | showStandardStreams = true 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /vpn_service_plugin/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'vpn_service_plugin' 2 | -------------------------------------------------------------------------------- /vpn_service_plugin/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vpn_service_plugin/android/src/test/kotlin/com/example/vpn_service_plugin/VpnServicePluginTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.vpn_service_plugin 2 | 3 | import io.flutter.plugin.common.MethodCall 4 | import io.flutter.plugin.common.MethodChannel 5 | import kotlin.test.Test 6 | import org.mockito.Mockito 7 | 8 | /* 9 | * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. 10 | * 11 | * Once you have built the plugin's example app, you can run these tests from the command 12 | * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or 13 | * you can run them directly from IDEs that support JUnit such as Android Studio. 14 | */ 15 | 16 | internal class VpnServicePluginTest { 17 | @Test 18 | fun onMethodCall_getPlatformVersion_returnsExpectedValue() { 19 | val plugin = VpnServicePlugin() 20 | 21 | val call = MethodCall("getPlatformVersion", null) 22 | val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) 23 | plugin.onMethodCall(call, mockResult) 24 | 25 | Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/README.md: -------------------------------------------------------------------------------- 1 | # vpn_service_plugin_example 2 | 3 | Demonstrates how to use the vpn_service_plugin plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "com.example.vpn_service_plugin_example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.example.vpn_service_plugin_example" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.getByName("debug") 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/kotlin/com/example/vpn_service_plugin_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.vpn_service_plugin_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/vpn_service_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/vpn_service_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/vpn_service_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/vpn_service_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/vpn_service_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/integration_test/plugin_integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:vpn_service_plugin/vpn_service_plugin.dart'; 4 | 5 | void main() { 6 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 7 | final VpnServicePlugin plugin = VpnServicePlugin(); 8 | 9 | testWidgets('VPN Service basic flow test', (WidgetTester tester) async { 10 | // Test VPN preparation 11 | final prepareResult = await plugin.prepareVpn(); 12 | expect(prepareResult, isA>()); 13 | 14 | // Test VPN start 15 | final startResult = await plugin.startVpn( 16 | ipv4Addr: "10.126.126.1/24", 17 | dns: "114.114.114.114", 18 | routes: ["0.0.0.0/0"], 19 | mtu: 1500, 20 | ); 21 | expect(startResult, isA>()); 22 | 23 | // Give some time for VPN to establish 24 | await Future.delayed(const Duration(seconds: 2)); 25 | 26 | // Test VPN stop 27 | await plugin.stopVpn(); 28 | }); 29 | 30 | testWidgets('VPN Service error handling test', (WidgetTester tester) async { 31 | // Test invalid IP address format 32 | expect( 33 | () => plugin.startVpn(ipv4Addr: "invalid_ip"), 34 | throwsA(isA()), 35 | ); 36 | 37 | // Test invalid route format 38 | expect( 39 | () => plugin.startVpn( 40 | ipv4Addr: "10.126.126.1/24", 41 | routes: ["invalid_route"], 42 | ), 43 | throwsA(isA()), 44 | ); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:vpn_service_plugin/vpn_service_plugin.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatefulWidget { 9 | const MyApp({super.key}); 10 | 11 | @override 12 | State createState() => _MyAppState(); 13 | } 14 | 15 | class _MyAppState extends State { 16 | final _vpnPlugin = VpnServicePlugin(); 17 | bool _isVpnActive = false; 18 | 19 | Future _toggleVpn() async { 20 | if (!_isVpnActive) { 21 | // 准备 VPN 22 | final prepareResult = await _vpnPlugin.prepareVpn(); 23 | if (prepareResult.containsKey('errorMsg')) { 24 | print('VPN 准备失败: ${prepareResult['errorMsg']}'); 25 | return; 26 | } 27 | 28 | // 启动 VPN 29 | final startResult = await _vpnPlugin.startVpn( 30 | ipv4Addr: "10.126.126.1/24", 31 | dns: "114.114.114.114", 32 | routes: ["0.0.0.0/0"], 33 | ); 34 | 35 | if (startResult.containsKey('errorMsg')) { 36 | print('VPN 启动失败: ${startResult['errorMsg']}'); 37 | return; 38 | } 39 | } else { 40 | // 停止 VPN 41 | await _vpnPlugin.stopVpn(); 42 | } 43 | 44 | setState(() { 45 | _isVpnActive = !_isVpnActive; 46 | }); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return MaterialApp( 52 | home: Scaffold( 53 | appBar: AppBar(title: const Text('VPN 服务示例')), 54 | body: Center( 55 | child: Column( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | Text('VPN 状态: ${_isVpnActive ? "已连接" : "未连接"}'), 59 | const SizedBox(height: 20), 60 | ElevatedButton( 61 | onPressed: _toggleVpn, 62 | child: Text(_isVpnActive ? '断开 VPN' : '连接 VPN'), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /vpn_service_plugin/example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:vpn_service_plugin_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /vpn_service_plugin/lib/vpn_service_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/services.dart'; 3 | import 'vpn_service_plugin_platform_interface.dart'; 4 | 5 | class VpnServicePlugin { 6 | static const MethodChannel _channel = MethodChannel('vpn_service'); 7 | static const EventChannel _eventChannel = EventChannel('vpn_service_events'); 8 | 9 | // VPN服务事件流 10 | static Stream>? _vpnServiceEvents; 11 | 12 | /// 获取VPN服务事件流 13 | Stream> get onVpnStatusChanged { 14 | _vpnServiceEvents ??= _eventChannel 15 | .receiveBroadcastStream() 16 | .map>( 17 | (event) => Map.from(event as Map), 18 | ); 19 | return _vpnServiceEvents!; 20 | } 21 | 22 | /// 准备 VPN 服务,获取必要的权限 23 | Future> prepareVpn() { 24 | return VpnServicePluginPlatform.instance.prepareVpn(); 25 | } 26 | 27 | /// 启动 VPN 服务 28 | /// 29 | /// [ipv4Addr] - VPN 的 IPv4 地址,格式如 "10.126.126.1/24" 30 | /// [routes] - VPN 路由表 31 | /// [dns] - DNS 服务器地址 32 | /// [disallowedApplications] - 不允许使用 VPN 的应用包名列表 33 | /// [mtu] - 最大传输单元 34 | Future> startVpn({ 35 | String? ipv4Addr, 36 | List? routes, 37 | String? dns, 38 | List? disallowedApplications, 39 | int? mtu, 40 | }) { 41 | return VpnServicePluginPlatform.instance.startVpn( 42 | ipv4Addr: ipv4Addr, 43 | routes: routes, 44 | dns: dns, 45 | disallowedApplications: disallowedApplications, 46 | mtu: mtu, 47 | ); 48 | } 49 | 50 | /// 停止 VPN 服务 51 | Future stopVpn() { 52 | return VpnServicePluginPlatform.instance.stopVpn(); 53 | } 54 | 55 | /// 监听VPN服务启动事件 56 | Stream> get onVpnServiceStarted { 57 | return onVpnStatusChanged 58 | .where((event) => event['event'] == 'vpn_service_start') 59 | .map((event) => Map.from(event['data'] as Map)); 60 | } 61 | 62 | /// 监听VPN服务停止事件 63 | Stream> get onVpnServiceStopped { 64 | return onVpnStatusChanged 65 | .where((event) => event['event'] == 'vpn_service_stop') 66 | .map((event) => Map.from(event['data'] as Map)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /vpn_service_plugin/lib/vpn_service_plugin_method_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'vpn_service_plugin_platform_interface.dart'; 4 | 5 | class MethodChannelVpnServicePlugin extends VpnServicePluginPlatform { 6 | @visibleForTesting 7 | final methodChannel = const MethodChannel('vpn_service'); 8 | 9 | @override 10 | Future> prepareVpn() async { 11 | final result = await methodChannel.invokeMethod>( 12 | 'prepareVpn', 13 | ); 14 | return Map.from(result ?? {}); 15 | } 16 | 17 | @override 18 | Future> startVpn({ 19 | String? ipv4Addr, 20 | List? routes, 21 | String? dns, 22 | List? disallowedApplications, 23 | int? mtu, 24 | }) async { 25 | final result = await methodChannel 26 | .invokeMethod>('startVpn', { 27 | 'ipv4Addr': ipv4Addr, 28 | 'routes': routes, 29 | 'dns': dns, 30 | 'disallowedApplications': disallowedApplications, 31 | 'mtu': mtu, 32 | }); 33 | return Map.from(result ?? {}); 34 | } 35 | 36 | @override 37 | Future> stopVpn() async { 38 | await methodChannel.invokeMethod('stopVpn'); 39 | return {}; // Return an empty map to match the required return type 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vpn_service_plugin/lib/vpn_service_plugin_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 2 | import 'vpn_service_plugin_method_channel.dart'; 3 | 4 | abstract class VpnServicePluginPlatform extends PlatformInterface { 5 | VpnServicePluginPlatform() : super(token: _token); 6 | 7 | static final Object _token = Object(); 8 | static VpnServicePluginPlatform _instance = MethodChannelVpnServicePlugin(); 9 | 10 | static VpnServicePluginPlatform get instance => _instance; 11 | 12 | static set instance(VpnServicePluginPlatform instance) { 13 | PlatformInterface.verifyToken(instance, _token); 14 | _instance = instance; 15 | } 16 | 17 | Future> prepareVpn() { 18 | throw UnimplementedError('prepareVpn() 未实现.'); 19 | } 20 | 21 | Future> startVpn({ 22 | String? ipv4Addr, 23 | List? routes, 24 | String? dns, 25 | List? disallowedApplications, 26 | int? mtu, 27 | }) { 28 | throw UnimplementedError('startVpn() 未实现.'); 29 | } 30 | 31 | Future stopVpn() { 32 | throw UnimplementedError('stopVpn() 未实现.'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | astral 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astral", 3 | "short_name": "astral", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void RegisterPlugins(flutter::PluginRegistry* registry) { 20 | AppLinksPluginCApiRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("AppLinksPluginCApi")); 22 | FlutterLocalizationPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); 24 | IsarFlutterLibsPluginRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); 26 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 27 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 28 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 29 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 30 | SentryFlutterPluginRegisterWithRegistrar( 31 | registry->GetRegistrarForPlugin("SentryFlutterPlugin")); 32 | SystemTrayPluginRegisterWithRegistrar( 33 | registry->GetRegistrarForPlugin("SystemTrayPlugin")); 34 | UrlLauncherWindowsRegisterWithRegistrar( 35 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 36 | WindowManagerPluginRegisterWithRegistrar( 37 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 38 | } 39 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | app_links 7 | flutter_localization 8 | isar_flutter_libs 9 | permission_handler_windows 10 | screen_retriever_windows 11 | sentry_flutter 12 | system_tray 13 | url_launcher_windows 14 | window_manager 15 | ) 16 | 17 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 18 | rust_lib_astral 19 | ) 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) 22 | 23 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 25 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 28 | endforeach(plugin) 29 | 30 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 31 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 32 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 33 | endforeach(ffi_plugin) 34 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | SET_TARGET_PROPERTIES(${BINARY_NAME} PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:WINDOWS") 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | # 添加资源复制指令到可执行目标所在目录 37 | add_custom_command(TARGET ${BINARY_NAME} POST_BUILD 38 | COMMAND ${CMAKE_COMMAND} -E copy_directory 39 | "${CMAKE_CURRENT_SOURCE_DIR}/../../dlls" 40 | "${CMAKE_INSTALL_PREFIX}/" 41 | COMMENT "Copying DLL files to output directory" 42 | VERBATIM 43 | ) 44 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 45 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 46 | 47 | # Run the Flutter tool portions of the build. This must not be removed. 48 | add_dependencies(${BINARY_NAME} flutter_assemble) 49 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "flutter_window.h" 8 | #include "utils.h" 9 | 10 | BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { 11 | const int bufferSize = 256; 12 | wchar_t windowTitle[bufferSize]; 13 | 14 | if (GetWindowTextW(hwnd, windowTitle, bufferSize)) { 15 | if (_wcsicmp(windowTitle, L"Astral") == 0) { 16 | *((HWND*)lParam) = hwnd; 17 | return FALSE; 18 | } 19 | } 20 | return TRUE; 21 | } 22 | 23 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 24 | _In_ wchar_t *command_line, _In_ int show_command) { 25 | HANDLE hMutex = CreateMutexW(NULL, TRUE, L"AstralAppSingleInstanceMutex"); 26 | if (GetLastError() == ERROR_ALREADY_EXISTS) { 27 | HWND hWnd = NULL; 28 | EnumWindows(EnumWindowsProc, (LPARAM)&hWnd); 29 | 30 | if (hWnd != NULL) { 31 | if (IsIconic(hWnd)) { 32 | ShowWindow(hWnd, SW_RESTORE); 33 | } 34 | SetForegroundWindow(hWnd); 35 | } 36 | CloseHandle(hMutex); 37 | return 0; 38 | } 39 | 40 | // Attach to console when present (e.g., 'flutter run') or create a 41 | // new console when running with a debugger. 42 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 43 | CreateAndAttachConsole(); 44 | } 45 | 46 | // Initialize COM, so that it is available for use in the library and/or 47 | // plugins. 48 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 49 | 50 | flutter::DartProject project(L"data"); 51 | 52 | std::vector command_line_arguments = 53 | GetCommandLineArguments(); 54 | 55 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 56 | 57 | FlutterWindow window(project); 58 | Win32Window::Point origin(10, 10); 59 | Win32Window::Size size(1280, 720); 60 | if (!window.Create(L"Astral", origin, size)) { 61 | return EXIT_FAILURE; 62 | } 63 | window.SetQuitOnClose(true); 64 | 65 | ::MSG msg; 66 | while (::GetMessage(&msg, nullptr, 0, 0)) { 67 | ::TranslateMessage(&msg); 68 | ::DispatchMessage(&msg); 69 | } 70 | 71 | CloseHandle(hMutex); 72 | return EXIT_SUCCESS; 73 | } -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldoubil/astral/22171ad191cdb0648683de7a019bb7aba085a54c/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PerMonitorV2 7 | UTF-8 8 | true 9 | SegmentHeap 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------