diff --git a/.github/actions/archive/action.yml b/.github/actions/archive/action.yml
index c34bd3c..f33717e 100644
--- a/.github/actions/archive/action.yml
+++ b/.github/actions/archive/action.yml
@@ -29,6 +29,9 @@ runs:
xcodebuild archive \
-allowProvisioningUpdates \
-allowProvisioningDeviceRegistration \
+ -skipPackagePluginValidation \
+ -skipMacroValidation \
+ -onlyUsePackageVersionsFromResolvedFile \
-authenticationKeyID ${{ inputs.app-store-key-id }} \
-authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
-authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
diff --git a/.github/actions/build-for-testing/action.yml b/.github/actions/build-for-testing/action.yml
index ce91b43..2c66963 100644
--- a/.github/actions/build-for-testing/action.yml
+++ b/.github/actions/build-for-testing/action.yml
@@ -24,6 +24,7 @@ runs:
path: |
Apple/PackageCache
Apple/SourcePackages
+ Apple/DerivedData
key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-${{ inputs.scheme }}-
@@ -33,17 +34,18 @@ runs:
run: |
echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
- xcodebuild clean build-for-testing \
+ xcodebuild build-for-testing \
-allowProvisioningUpdates \
-allowProvisioningDeviceRegistration \
+ -skipPackagePluginValidation \
+ -skipMacroValidation \
+ -onlyUsePackageVersionsFromResolvedFile \
-authenticationKeyID ${{ inputs.app-store-key-id }} \
-authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
-authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
- -onlyUsePackageVersionsFromResolvedFile \
-clonedSourcePackagesDirPath SourcePackages \
-packageCachePath $PWD/PackageCache \
- -skipPackagePluginValidation \
- -skipMacroValidation \
+ -derivedDataPath $PWD/DerivedData \
-scheme '${{ inputs.scheme }}' \
-destination '${{ inputs.destination }}' \
-resultBundlePath BuildResults.xcresult
diff --git a/.github/actions/export/action.yml b/.github/actions/export/action.yml
index bf007a7..635732c 100644
--- a/.github/actions/export/action.yml
+++ b/.github/actions/export/action.yml
@@ -37,6 +37,9 @@ runs:
-exportArchive \
-allowProvisioningUpdates \
-allowProvisioningDeviceRegistration \
+ -skipPackagePluginValidation \
+ -skipMacroValidation \
+ -onlyUsePackageVersionsFromResolvedFile \
-authenticationKeyID ${{ inputs.app-store-key-id }} \
-authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
-authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
diff --git a/.github/actions/notarize/action.yml b/.github/actions/notarize/action.yml
new file mode 100644
index 0000000..739e9ea
--- /dev/null
+++ b/.github/actions/notarize/action.yml
@@ -0,0 +1,57 @@
+name: Notarize
+inputs:
+ app-store-key:
+ description: App Store key in PEM PKCS#8 format
+ required: true
+ app-store-key-id:
+ description: App Store key ID
+ required: true
+ app-store-key-issuer-id:
+ description: App Store key issuer ID
+ required: true
+ archive-path:
+ description: Xcode archive path
+ required: true
+outputs:
+ notarized-app:
+ description: The compressed and notarized app
+ value: ${{ steps.notarize.outputs.notarized-app }}
+runs:
+ using: composite
+ steps:
+ - id: notarize
+ shell: bash
+ run: |
+ echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
+
+ echo '{"destination":"upload","method":"developer-id"}' \
+ | plutil -convert xml1 -o ExportOptions.plist -
+
+ xcodebuild \
+ -exportArchive \
+ -allowProvisioningUpdates \
+ -allowProvisioningDeviceRegistration \
+ -authenticationKeyID ${{ inputs.app-store-key-id }} \
+ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
+ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
+ -archivePath '${{ inputs.archive-path }}' \
+ -exportOptionsPlist ExportOptions.plist
+
+ until xcodebuild \
+ -exportNotarizedApp \
+ -allowProvisioningUpdates \
+ -allowProvisioningDeviceRegistration \
+ -authenticationKeyID ${{ inputs.app-store-key-id }} \
+ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
+ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
+ -archivePath '${{ inputs.archive-path }}' \
+ -exportPath Release
+ do
+ echo "Failed to export app, trying again in 10s..."
+ sleep 10
+ done
+
+ tar --options xz:compression-level=9 -C Release -cJvf Wallet.txz ./
+ echo "notarized-app=Wallet.txz" >> $GITHUB_OUTPUT
+
+ rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 Release ExportOptions.plist
diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml
new file mode 100644
index 0000000..ef5c525
--- /dev/null
+++ b/.github/workflows/build-appimage.yml
@@ -0,0 +1,23 @@
+name: Build AppImage
+on:
+ push:
+ branches: [main]
+ pull_request:
+jobs:
+ appimage:
+ name: Build AppImage
+ runs-on: ubuntu-latest
+ container: docker
+ steps:
+ - uses: actions/checkout@v4
+ - name: Build AppImage
+ run: |
+ docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile
+ docker create --name temp appimage-builder
+ docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage .
+ docker rm temp
+ - uses: actions/upload-artifact@v4
+ with:
+ name: AppImage
+ path: Burrow-x86_64.AppImage
+
diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml
index da0f56a..19ef417 100644
--- a/.github/workflows/build-apple.yml
+++ b/.github/workflows/build-apple.yml
@@ -12,7 +12,7 @@ concurrency:
jobs:
build:
name: Build App (${{ matrix.platform }})
- runs-on: macos-13
+ runs-on: macos-14
strategy:
fail-fast: false
matrix:
diff --git a/.github/workflows/build-flatpak.yml b/.github/workflows/build-flatpak.yml
index e0e804e..d74eec3 100644
--- a/.github/workflows/build-flatpak.yml
+++ b/.github/workflows/build-flatpak.yml
@@ -1,7 +1,4 @@
-on:
- push:
- branches: [main]
- pull_request:
+on: workflow_dispatch
name: Build Flatpak
jobs:
flatpak:
diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml
index 3ea185d..6164ea6 100644
--- a/.github/workflows/release-apple.yml
+++ b/.github/workflows/release-apple.yml
@@ -1,26 +1,27 @@
name: Build Apple Release
on:
- release:
- types:
- - created
+ pull_request:
+ branches:
+ - "*"
jobs:
build:
name: Build ${{ matrix.configuration['platform'] }} Release
- runs-on: macos-13
+ runs-on: macos-14
strategy:
fail-fast: false
matrix:
configuration:
- - scheme: App (iOS)
- destination: generic/platform=iOS
+ - destination: generic/platform=iOS
platform: iOS
- method: ad-hoc
artifact-file: Apple/Release/Burrow.ipa
- - scheme: App (macOS)
- destination: generic/platform=macOS
+ rust-targets:
+ - aarch64-apple-ios
+ - destination: generic/platform=macOS
platform: macOS
- method: mac-application
artifact-file: Burrow.app.txz
+ rust-targets:
+ - x86_64-apple-darwin
+ - aarch64-apple-darwin
env:
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
steps:
@@ -34,32 +35,36 @@ jobs:
with:
certificate: ${{ secrets.DEVELOPER_CERT }}
password: ${{ secrets.DEVELOPER_CERT_PASSWORD }}
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ join(matrix.rust-targets, ', ') }}
- name: Archive
uses: ./.github/actions/archive
with:
- scheme: ${{ matrix.configuration['scheme'] }}
+ scheme: App
destination: ${{ matrix.configuration['destination'] }}
app-store-key: ${{ secrets.APPSTORE_KEY }}
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
archive-path: Burrow.xcarchive
- - name: Export Locally
+ - name: Upload
uses: ./.github/actions/export
with:
- method: ${{ matrix.configuration['method'] }}
- destination: export
+ method: app-store-connect
+ destination: upload
app-store-key: ${{ secrets.APPSTORE_KEY }}
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
archive-path: Burrow.xcarchive
export-path: Release
- - name: Compress
+ - name: Notarize
if: ${{ matrix.configuration['platform'] == 'macOS' }}
- shell: bash
- run: tar --options xz:compression-level=9 -C Apple/Release -cJf Burrow.app.txz ./
- - name: Attach Artifact
- uses: SierraSoftworks/gh-releases@v1.0.6
+ uses: ./.github/actions/notarize
with:
- token: ${{ secrets.GITHUB_TOKEN }}
- overwrite: 'false'
- files: ${{ matrix.configuration['artifact-file'] }}
+ app-store-key: ${{ secrets.APPSTORE_KEY }}
+ app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
+ app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
+ archive-path: Burrow.xcarchive
+ product-name: Burrow.app
+
diff --git a/.swiftlint.yml b/.swiftlint.yml
index d609718..22ef035 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -46,7 +46,6 @@ opt_in_rules:
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
-- no_grouping_extension
- nslocalizedstring_key
- nslocalizedstring_require_bundle
- number_separator
@@ -76,9 +75,7 @@ opt_in_rules:
- sorted_first_last
- sorted_imports
- static_operator
-- strict_fileprivate
- strong_iboutlet
-- switch_case_on_newline
- test_case_accessibility
- toggle_bool
- trailing_closure
@@ -97,3 +94,5 @@ disabled_rules:
- force_try
- nesting
- todo
+- trailing_comma
+- switch_case_on_newline
diff --git a/Apple/App/App.xcconfig b/Apple/App/App.xcconfig
index 1d63205..4e42ddc 100644
--- a/Apple/App/App.xcconfig
+++ b/Apple/App/App.xcconfig
@@ -11,7 +11,12 @@ INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2
+EXCLUDED_SOURCE_FILE_NAMES = MainMenu.xib
+EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] =
+INFOPLIST_KEY_LSUIElement[sdk=macosx*] = YES
+INFOPLIST_KEY_NSMainNibFile[sdk=macosx*] = MainMenu
+INFOPLIST_KEY_NSPrincipalClass[sdk=macosx*] = NSApplication
INFOPLIST_KEY_LSApplicationCategoryType[sdk=macosx*] = public.app-category.utilities
CODE_SIGN_ENTITLEMENTS = App/App-iOS.entitlements
diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift
index f42b52f..6085d85 100644
--- a/Apple/App/AppDelegate.swift
+++ b/Apple/App/AppDelegate.swift
@@ -3,6 +3,7 @@ import AppKit
import SwiftUI
@MainActor
+@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private let quitItem: NSMenuItem = {
let quitItem = NSMenuItem(
@@ -16,7 +17,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}()
private let toggleItem: NSMenuItem = {
- let toggleView = NSHostingView(rootView: MenuItemToggleView(tunnel: BurrowApp.tunnel))
+ let toggleView = NSHostingView(rootView: MenuItemToggleView())
toggleView.frame.size = CGSize(width: 300, height: 32)
toggleView.autoresizingMask = [.width]
diff --git a/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json b/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json
new file mode 100644
index 0000000..911b4b1
--- /dev/null
+++ b/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x50",
+ "green" : "0x37",
+ "red" : "0xEC"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json b/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json
new file mode 100644
index 0000000..ddd0664
--- /dev/null
+++ b/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "flag-standalone-wtransparent.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf b/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf
new file mode 100644
index 0000000..1506fe9
Binary files /dev/null and b/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf differ
diff --git a/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json b/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
new file mode 100644
index 0000000..092ec69
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x1A",
+ "green" : "0x17",
+ "red" : "0x88"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json b/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json
new file mode 100644
index 0000000..e7fe15a
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "WireGuard.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg b/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg
new file mode 100644
index 0000000..9520f89
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json
new file mode 100644
index 0000000..782dd12
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "WireGuardTitle.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg
new file mode 100644
index 0000000..64946da
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg
@@ -0,0 +1,3 @@
+
diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift
index e8aed86..21ebf84 100644
--- a/Apple/App/BurrowApp.swift
+++ b/Apple/App/BurrowApp.swift
@@ -1,21 +1,13 @@
import SwiftUI
-@main
+#if !os(macOS)
@MainActor
+@main
struct BurrowApp: App {
- static let tunnel = Tunnel { manager, proto in
- proto.serverAddress = "hackclub.com"
- manager.localizedDescription = "Burrow"
- }
-
- #if os(macOS)
- @NSApplicationDelegateAdaptor(AppDelegate.self)
- var delegate
- #endif
-
var body: some Scene {
WindowGroup {
- TunnelView(tunnel: Self.tunnel)
+ BurrowView()
}
}
}
+#endif
diff --git a/Apple/App/BurrowView.swift b/Apple/App/BurrowView.swift
new file mode 100644
index 0000000..b78b1e1
--- /dev/null
+++ b/Apple/App/BurrowView.swift
@@ -0,0 +1,26 @@
+import SwiftUI
+
+struct BurrowView: View {
+ var body: some View {
+ NavigationStack {
+ VStack {
+ NetworkCarouselView()
+ Spacer()
+ TunnelStatusView()
+ TunnelButton()
+ .padding(.bottom)
+ }
+ .padding()
+ .navigationTitle("Networks")
+ }
+ }
+}
+
+#if DEBUG
+struct NetworkView_Previews: PreviewProvider {
+ static var previews: some View {
+ BurrowView()
+ .environment(\.tunnel, PreviewTunnel())
+ }
+}
+#endif
diff --git a/Apple/App/FloatingButtonStyle.swift b/Apple/App/FloatingButtonStyle.swift
new file mode 100644
index 0000000..53ab5ed
--- /dev/null
+++ b/Apple/App/FloatingButtonStyle.swift
@@ -0,0 +1,50 @@
+import SwiftUI
+
+struct FloatingButtonStyle: ButtonStyle {
+ static let duration = 0.08
+
+ var color: Color
+ var cornerRadius: CGFloat
+
+ func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .font(.headline)
+ .foregroundColor(.white)
+ .frame(minHeight: 48)
+ .padding(.horizontal)
+ .background(
+ RoundedRectangle(cornerRadius: cornerRadius)
+ .fill(
+ LinearGradient(
+ colors: [
+ configuration.isPressed ? color.opacity(0.9) : color.opacity(0.9),
+ configuration.isPressed ? color.opacity(0.9) : color
+ ],
+ startPoint: .init(x: 0.2, y: 0),
+ endPoint: .init(x: 0.8, y: 1)
+ )
+ )
+ .background(
+ RoundedRectangle(cornerRadius: cornerRadius)
+ .fill(configuration.isPressed ? .black : .white)
+ )
+ )
+ .shadow(color: .black.opacity(configuration.isPressed ? 0.0 : 0.1), radius: 2.5, x: 0, y: 2)
+ .scaleEffect(configuration.isPressed ? 0.975 : 1.0)
+ .padding(.bottom, 2)
+ .animation(
+ configuration.isPressed ? .easeOut(duration: Self.duration) : .easeIn(duration: Self.duration),
+ value: configuration.isPressed
+ )
+ }
+}
+
+extension ButtonStyle where Self == FloatingButtonStyle {
+ static var floating: FloatingButtonStyle {
+ floating()
+ }
+
+ static func floating(color: Color = .accentColor, cornerRadius: CGFloat = 10) -> FloatingButtonStyle {
+ FloatingButtonStyle(color: color, cornerRadius: cornerRadius)
+ }
+}
diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib
new file mode 100644
index 0000000..8933f30
--- /dev/null
+++ b/Apple/App/MainMenu.xib
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Apple/App/Menu/MenuView.swift b/Apple/App/Menu/MenuView.swift
deleted file mode 100644
index eab8da2..0000000
--- a/Apple/App/Menu/MenuView.swift
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// MenuView.swift
-// App
-//
-// Created by Thomas Stubblefield on 5/13/23.
-//
-
-import SwiftUI
-
-struct MenuItemToggleView: View {
- var tunnel: Tunnel
-
- var body: some View {
- HStack {
- Text("Burrow")
- .font(.headline)
- Spacer()
- Toggle("Burrow", isOn: tunnel.isOn)
- .labelsHidden()
- .disabled(tunnel.isDisabled)
- .toggleStyle(.switch)
- }
- .padding(.horizontal, 4)
- .padding(10)
- .frame(minWidth: 300, minHeight: 32, maxHeight: 32)
- }
-}
-
-extension Tunnel {
- var isDisabled: Bool {
- switch self.status {
- case .disconnected, .permissionRequired, .connected:
- return false
- case .unknown, .disabled, .connecting, .reasserting, .disconnecting, .invalid, .configurationReadWriteFailed:
- return true
- }
- }
-
- var isOn: Binding {
- Binding {
- switch self.status {
- case .connecting, .reasserting, .connected:
- true
- default:
- false
- }
- } set: { newValue in
- switch (self.status, newValue) {
- case (.permissionRequired, true):
- Task { try await self.configure() }
- case (.disconnected, true):
- try? self.start()
- case (.connected, false):
- self.stop()
- default:
- return
- }
- }
- }
-}
diff --git a/Apple/App/MenuItemToggleView.swift b/Apple/App/MenuItemToggleView.swift
new file mode 100644
index 0000000..07db51d
--- /dev/null
+++ b/Apple/App/MenuItemToggleView.swift
@@ -0,0 +1,64 @@
+//
+// MenuItemToggleView.swift
+// App
+//
+// Created by Thomas Stubblefield on 5/13/23.
+//
+
+import SwiftUI
+
+struct MenuItemToggleView: View {
+ @Environment(\.tunnel)
+ var tunnel: Tunnel
+
+ var body: some View {
+ HStack {
+ VStack(alignment: .leading) {
+ Text("Burrow")
+ .font(.headline)
+ Text(tunnel.status.description)
+ .font(.subheadline)
+ }
+ Spacer()
+ Toggle(isOn: tunnel.toggleIsOn) {
+ }
+ .disabled(tunnel.toggleDisabled)
+ .toggleStyle(.switch)
+ }
+ .accessibilityElement(children: .combine)
+ .padding(.horizontal, 4)
+ .padding(10)
+ .frame(minWidth: 300, minHeight: 32, maxHeight: 32)
+ }
+}
+
+extension Tunnel {
+ fileprivate var toggleDisabled: Bool {
+ switch status {
+ case .disconnected, .permissionRequired, .connected, .disconnecting:
+ false
+ case .unknown, .disabled, .connecting, .reasserting, .invalid, .configurationReadWriteFailed:
+ true
+ }
+ }
+
+ var toggleIsOn: Binding {
+ Binding {
+ switch status {
+ case .connecting, .reasserting, .connected:
+ true
+ default:
+ false
+ }
+ } set: { newValue in
+ switch (status, newValue) {
+ case (.permissionRequired, true):
+ enable()
+ case (_, true):
+ start()
+ case (_, false):
+ stop()
+ }
+ }
+ }
+}
diff --git a/Apple/App/NetworkExtensionTunnel.swift b/Apple/App/NetworkExtensionTunnel.swift
new file mode 100644
index 0000000..08002de
--- /dev/null
+++ b/Apple/App/NetworkExtensionTunnel.swift
@@ -0,0 +1,167 @@
+import BurrowShared
+import NetworkExtension
+
+@Observable
+class NetworkExtensionTunnel: Tunnel {
+ @MainActor private(set) var status: TunnelStatus = .unknown
+ private var error: NEVPNError?
+
+ private let logger = Logger.logger(for: Tunnel.self)
+ private let bundleIdentifier: String
+ private var tasks: [Task] = []
+
+ // Each manager corresponds to one entry in the Settings app.
+ // Our goal is to maintain a single manager, so we create one if none exist and delete any extra.
+ private var managers: [NEVPNManager]? {
+ didSet { Task { await updateStatus() } }
+ }
+
+ private var currentStatus: TunnelStatus {
+ guard let managers = managers else {
+ guard let error = error else {
+ return .unknown
+ }
+
+ switch error.code {
+ case .configurationReadWriteFailed:
+ return .configurationReadWriteFailed
+ default:
+ return .unknown
+ }
+ }
+
+ guard let manager = managers.first else {
+ return .permissionRequired
+ }
+
+ guard manager.isEnabled else {
+ return .disabled
+ }
+
+ return manager.connection.tunnelStatus
+ }
+
+ convenience init() {
+ self.init(Constants.networkExtensionBundleIdentifier)
+ }
+
+ init(_ bundleIdentifier: String) {
+ self.bundleIdentifier = bundleIdentifier
+
+ let center = NotificationCenter.default
+ let configurationChanged = Task { [weak self] in
+ for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
+ await self?.update()
+ }
+ }
+ let statusChanged = Task { [weak self] in
+ for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
+ await self?.updateStatus()
+ }
+ }
+ tasks = [configurationChanged, statusChanged]
+
+ Task { await update() }
+ }
+
+ private func update() async {
+ do {
+ managers = try await NETunnelProviderManager.managers
+ await self.updateStatus()
+ } catch let vpnError as NEVPNError {
+ error = vpnError
+ } catch {
+ logger.error("Failed to update VPN configurations: \(error)")
+ }
+ }
+
+ private func updateStatus() async {
+ await MainActor.run {
+ status = currentStatus
+ }
+ }
+
+ func configure() async throws {
+ if managers == nil {
+ await update()
+ }
+
+ guard let managers = managers else { return }
+
+ if managers.count > 1 {
+ try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
+ for manager in managers.suffix(from: 1) {
+ group.addTask { try await manager.remove() }
+ }
+ try await group.waitForAll()
+ }
+ }
+
+ guard managers.isEmpty else { return }
+
+ let manager = NETunnelProviderManager()
+ manager.localizedDescription = "Burrow"
+
+ let proto = NETunnelProviderProtocol()
+ proto.providerBundleIdentifier = bundleIdentifier
+ proto.serverAddress = "hackclub.com"
+
+ manager.protocolConfiguration = proto
+ try await manager.save()
+ }
+
+ func start() {
+ guard let manager = managers?.first else { return }
+ Task {
+ do {
+ if !manager.isEnabled {
+ manager.isEnabled = true
+ try await manager.save()
+ }
+ try manager.connection.startVPNTunnel()
+ } catch {
+ logger.error("Failed to start: \(error)")
+ }
+ }
+ }
+
+ func stop() {
+ guard let manager = managers?.first else { return }
+ manager.connection.stopVPNTunnel()
+ }
+
+ func enable() {
+ Task {
+ do {
+ try await configure()
+ } catch {
+ logger.error("Failed to enable: \(error)")
+ }
+ }
+ }
+
+ deinit {
+ tasks.forEach { $0.cancel() }
+ }
+}
+
+extension NEVPNConnection {
+ fileprivate var tunnelStatus: TunnelStatus {
+ switch status {
+ case .connected:
+ .connected(connectedDate!)
+ case .connecting:
+ .connecting
+ case .disconnecting:
+ .disconnecting
+ case .disconnected:
+ .disconnected
+ case .reasserting:
+ .reasserting
+ case .invalid:
+ .invalid
+ @unknown default:
+ .unknown
+ }
+ }
+}
diff --git a/Apple/App/NetworkView.swift b/Apple/App/NetworkView.swift
new file mode 100644
index 0000000..290254c
--- /dev/null
+++ b/Apple/App/NetworkView.swift
@@ -0,0 +1,88 @@
+import SwiftUI
+
+struct NetworkView: View {
+ var color: Color
+ var content: () -> Content
+
+ private var gradient: LinearGradient {
+ LinearGradient(
+ colors: [
+ color.opacity(0.8),
+ color
+ ],
+ startPoint: .init(x: 0.2, y: 0),
+ endPoint: .init(x: 0.8, y: 1)
+ )
+ }
+
+ var body: some View {
+ content()
+ .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(gradient)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.white)
+ )
+ )
+ .shadow(color: .black.opacity(0.1), radius: 3.0, x: 0, y: 2)
+ }
+}
+
+struct AddNetworkView: View {
+ var body: some View {
+ Text("Add Network")
+ .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(style: .init(lineWidth: 2, dash: [6]))
+ )
+ }
+}
+
+extension NetworkView where Content == AnyView {
+ init(network: any Network) {
+ color = network.backgroundColor
+ content = { AnyView(network.label) }
+ }
+}
+
+struct NetworkCarouselView: View {
+ var networks: [any Network] = [
+ HackClub(id: "1"),
+ HackClub(id: "2"),
+ WireGuard(id: "4"),
+ HackClub(id: "5"),
+ ]
+
+ var body: some View {
+ ScrollView(.horizontal) {
+ LazyHStack {
+ ForEach(networks, id: \.id) { network in
+ NetworkView(network: network)
+ .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center)
+ .scrollTransition(.interactive, axis: .horizontal) { content, phase in
+ content
+ .scaleEffect(1.0 - abs(phase.value) * 0.1)
+ }
+ }
+ AddNetworkView()
+ }
+ .scrollTargetLayout()
+ }
+ .scrollClipDisabled()
+ .scrollIndicators(.hidden)
+ .defaultScrollAnchor(.center)
+ .scrollTargetBehavior(.viewAligned)
+ .containerRelativeFrame(.horizontal)
+ }
+}
+
+#if DEBUG
+struct NetworkCarouselView_Previews: PreviewProvider {
+ static var previews: some View {
+ NetworkCarouselView()
+ }
+}
+#endif
diff --git a/Apple/App/Networks/HackClub.swift b/Apple/App/Networks/HackClub.swift
new file mode 100644
index 0000000..f7df674
--- /dev/null
+++ b/Apple/App/Networks/HackClub.swift
@@ -0,0 +1,23 @@
+import SwiftUI
+
+struct HackClub: Network {
+ var id: String
+ var backgroundColor: Color { .init("HackClub") }
+
+ var label: some View {
+ GeometryReader { reader in
+ VStack(alignment: .leading) {
+ Image("HackClub")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(height: reader.size.height / 4)
+ Spacer()
+ Text("@conradev")
+ .foregroundStyle(.white)
+ .font(.body.monospaced())
+ }
+ .padding()
+ .frame(maxWidth: .infinity)
+ }
+ }
+}
diff --git a/Apple/App/Networks/Network.swift b/Apple/App/Networks/Network.swift
new file mode 100644
index 0000000..d441d24
--- /dev/null
+++ b/Apple/App/Networks/Network.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+protocol Network {
+ associatedtype Label: View
+
+ var id: String { get }
+ var backgroundColor: Color { get }
+
+ var label: Label { get }
+}
diff --git a/Apple/App/Networks/WireGuard.swift b/Apple/App/Networks/WireGuard.swift
new file mode 100644
index 0000000..499288a
--- /dev/null
+++ b/Apple/App/Networks/WireGuard.swift
@@ -0,0 +1,30 @@
+import SwiftUI
+
+struct WireGuard: Network {
+ var id: String
+ var backgroundColor: Color { .init("WireGuard") }
+
+ var label: some View {
+ GeometryReader { reader in
+ VStack(alignment: .leading) {
+ HStack {
+ Image("WireGuard")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ Image("WireGuardTitle")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: reader.size.width / 2)
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, maxHeight: reader.size.height / 4)
+ Spacer()
+ Text("@conradev")
+ .foregroundStyle(.white)
+ .font(.body.monospaced())
+ }
+ .padding()
+ .frame(maxWidth: .infinity)
+ }
+ }
+}
diff --git a/Apple/App/Status.swift b/Apple/App/Status.swift
deleted file mode 100644
index c08cdd1..0000000
--- a/Apple/App/Status.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-import Foundation
-import NetworkExtension
-
-extension Tunnel {
- enum Status: CustomStringConvertible, Equatable, Hashable {
- case unknown
- case permissionRequired
- case disabled
- case connecting
- case connected(Date)
- case disconnecting
- case disconnected
- case reasserting
- case invalid
- case configurationReadWriteFailed
-
- var description: String {
- switch self {
- case .unknown:
- return "Unknown"
- case .permissionRequired:
- return "Permission Required"
- case .disconnected:
- return "Disconnected"
- case .disabled:
- return "Disabled"
- case .connecting:
- return "Connecting"
- case .connected:
- return "Connected"
- case .disconnecting:
- return "Disconnecting"
- case .reasserting:
- return "Reasserting"
- case .invalid:
- return "Invalid"
- case .configurationReadWriteFailed:
- return "System Error"
- }
- }
- }
-}
diff --git a/Apple/App/Tunnel.swift b/Apple/App/Tunnel.swift
index 5542170..8db366f 100644
--- a/Apple/App/Tunnel.swift
+++ b/Apple/App/Tunnel.swift
@@ -1,146 +1,50 @@
-import BurrowShared
-import NetworkExtension
import SwiftUI
+protocol Tunnel {
+ var status: TunnelStatus { get }
+
+ func start()
+ func stop()
+ func enable()
+}
+
+enum TunnelStatus: Equatable, Hashable {
+ case unknown
+ case permissionRequired
+ case disabled
+ case connecting
+ case connected(Date)
+ case disconnecting
+ case disconnected
+ case reasserting
+ case invalid
+ case configurationReadWriteFailed
+}
+
+struct TunnelKey: EnvironmentKey {
+ static let defaultValue: any Tunnel = NetworkExtensionTunnel()
+}
+
+extension EnvironmentValues {
+ var tunnel: any Tunnel {
+ get { self[TunnelKey.self] }
+ set { self[TunnelKey.self] = newValue }
+ }
+}
+
+#if DEBUG
@Observable
-class Tunnel {
- private(set) var status: Status = .unknown
- private var error: NEVPNError?
+class PreviewTunnel: Tunnel {
+ var status: TunnelStatus = .permissionRequired
- private let logger = Logger.logger(for: Tunnel.self)
- private let bundleIdentifier: String
- private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void
- private var tasks: [Task] = []
-
- // Each manager corresponds to one entry in the Settings app.
- // Our goal is to maintain a single manager, so we create one if none exist and delete extra if there are any.
- private var managers: [NEVPNManager]? {
- didSet { status = currentStatus }
+ func start() {
+ status = .connected(.now)
}
-
- private var currentStatus: Status {
- guard let managers = managers else {
- guard let error = error else {
- return .unknown
- }
-
- switch error.code {
- case .configurationReadWriteFailed:
- return .configurationReadWriteFailed
- default:
- return .unknown
- }
- }
-
- guard let manager = managers.first else {
- return .permissionRequired
- }
-
- guard manager.isEnabled else {
- return .disabled
- }
-
- return manager.connection.tunnelStatus
- }
-
- convenience init(configure: @escaping (NETunnelProviderManager, NETunnelProviderProtocol) -> Void) {
- self.init("com.hackclub.burrow.network", configure: configure)
- }
-
- init(_ bundleIdentifier: String, configure: @escaping (NETunnelProviderManager, NETunnelProviderProtocol) -> Void) {
- self.bundleIdentifier = bundleIdentifier
- self.configure = configure
-
- let center = NotificationCenter.default
- let configurationChanged = Task {
- for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
- await update()
- }
- }
- let statusChanged = Task {
- for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
- await MainActor.run {
- status = currentStatus
- }
- }
- }
- tasks = [configurationChanged, statusChanged]
-
- Task { await update() }
- }
-
- private func update() async {
- do {
- let updated = try await NETunnelProviderManager.managers
- await MainActor.run {
- managers = updated
- }
- } catch let vpnError as NEVPNError {
- error = vpnError
- } catch {
- logger.error("Failed to update VPN configurations: \(error)")
- }
- }
-
- func configure() async throws {
- if managers == nil {
- await update()
- }
-
- guard let managers = managers else { return }
-
- if managers.count > 1 {
- try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
- for manager in managers.suffix(from: 1) {
- group.addTask { try await manager.remove() }
- }
- try await group.waitForAll()
- }
- }
-
- if managers.isEmpty {
- let manager = NETunnelProviderManager()
- let proto = NETunnelProviderProtocol()
- proto.providerBundleIdentifier = bundleIdentifier
- configure(manager, proto)
-
- manager.protocolConfiguration = proto
- try await manager.save()
- }
- }
-
- func start() throws {
- guard let manager = managers?.first else { return }
- try manager.connection.startVPNTunnel()
- }
-
func stop() {
- guard let manager = managers?.first else { return }
- manager.connection.stopVPNTunnel()
+ status = .disconnected
}
-
- deinit {
- tasks.forEach { $0.cancel() }
- }
-}
-
-extension NEVPNConnection {
- var tunnelStatus: Tunnel.Status {
- switch status {
- case .connected:
- .connected(connectedDate!)
- case .connecting:
- .connecting
- case .disconnecting:
- .disconnecting
- case .disconnected:
- .disconnected
- case .reasserting:
- .reasserting
- case .invalid:
- .invalid
- @unknown default:
- .unknown
- }
+ func enable() {
+ status = .disconnected
}
}
+#endif
diff --git a/Apple/App/TunnelButton.swift b/Apple/App/TunnelButton.swift
new file mode 100644
index 0000000..df8d7e6
--- /dev/null
+++ b/Apple/App/TunnelButton.swift
@@ -0,0 +1,61 @@
+import SwiftUI
+
+struct TunnelButton: View {
+ @Environment(\.tunnel)
+ var tunnel: any Tunnel
+
+ var body: some View {
+ if let action = tunnel.action {
+ Button {
+ tunnel.perform(action)
+ } label: {
+ Text(action.description)
+ }
+ .padding(.horizontal)
+ .buttonStyle(.floating)
+ }
+ }
+}
+
+extension Tunnel {
+ fileprivate var action: TunnelButton.Action? {
+ switch status {
+ case .permissionRequired, .invalid:
+ .enable
+ case .disabled, .disconnecting, .disconnected:
+ .start
+ case .connecting, .connected, .reasserting:
+ .stop
+ case .unknown, .configurationReadWriteFailed:
+ nil
+ }
+ }
+}
+
+extension TunnelButton {
+ fileprivate enum Action {
+ case enable
+ case start
+ case stop
+ }
+}
+
+extension TunnelButton.Action {
+ var description: LocalizedStringKey {
+ switch self {
+ case .enable: "Enable"
+ case .start: "Start"
+ case .stop: "Stop"
+ }
+ }
+}
+
+extension Tunnel {
+ fileprivate func perform(_ action: TunnelButton.Action) {
+ switch action {
+ case .enable: enable()
+ case .start: start()
+ case .stop: stop()
+ }
+ }
+}
diff --git a/Apple/App/TunnelStatusView.swift b/Apple/App/TunnelStatusView.swift
new file mode 100644
index 0000000..3593516
--- /dev/null
+++ b/Apple/App/TunnelStatusView.swift
@@ -0,0 +1,37 @@
+import SwiftUI
+
+struct TunnelStatusView: View {
+ @Environment(\.tunnel)
+ var tunnel: any Tunnel
+
+ var body: some View {
+ Text(tunnel.status.description)
+ }
+}
+
+extension TunnelStatus: CustomStringConvertible {
+ var description: String {
+ switch self {
+ case .unknown:
+ "Unknown"
+ case .permissionRequired:
+ "Permission Required"
+ case .disconnected:
+ "Disconnected"
+ case .disabled:
+ "Disabled"
+ case .connecting:
+ "Connecting…"
+ case .connected:
+ "Connected"
+ case .disconnecting:
+ "Disconnecting…"
+ case .reasserting:
+ "Reasserting…"
+ case .invalid:
+ "Invalid"
+ case .configurationReadWriteFailed:
+ "System Error"
+ }
+ }
+}
diff --git a/Apple/App/TunnelView.swift b/Apple/App/TunnelView.swift
deleted file mode 100644
index dd91603..0000000
--- a/Apple/App/TunnelView.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-import SwiftUI
-
-struct TunnelView: View {
- var tunnel: Tunnel
-
- var body: some View {
- VStack {
- Text(verbatim: tunnel.status.description)
- switch tunnel.status {
- case .connected:
- Button("Disconnect", action: stop)
- case .permissionRequired:
- Button("Allow", action: configure)
- case .disconnected:
- Button("Start", action: start)
- default:
- EmptyView()
- }
- }
- .padding()
- }
-
- private func start() {
- try? tunnel.start()
- }
-
- private func stop() {
- tunnel.stop()
- }
-
- private func configure() {
- Task { try await tunnel.configure() }
- }
-}
diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj
index 428d9ab..8717a30 100644
--- a/Apple/Burrow.xcodeproj/project.pbxproj
+++ b/Apple/Burrow.xcodeproj/project.pbxproj
@@ -9,24 +9,32 @@
/* Begin PBXBuildFile section */
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; };
0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; };
- 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
+ 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; };
D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; };
D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; };
D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; };
D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
+ D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A79302B81630D0024EC91 /* NetworkView.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6512B8A79C20006B8AD /* HackClub.swift */; };
+ D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6532B8A79DA0006B8AD /* WireGuard.swift */; };
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
- D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */; };
+ D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */; };
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05B9F7929E39EED008CB1F9 /* Assets.xcassets */; };
+ D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */; };
D08252762B5C9FC4005DA378 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08252752B5C9FC4005DA378 /* Constants.swift */; };
+ D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; };
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */; };
- D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FE2A086E1C00AD070D /* Status.swift */; };
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B98FC629FDC5B5004E7149 /* Tunnel.swift */; };
D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; };
D0BCC60A2A09A0B800AD070D /* build-rust.sh in Resources */ = {isa = PBXBuildFile; fileRef = D0B98FBF29FD8072004E7149 /* build-rust.sh */; };
+ D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */; };
+ D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5952B818B2900F6A84B /* TunnelButton.swift */; };
+ D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */; };
+ D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5992B818B9600F6A84B /* Network.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -70,7 +78,7 @@
/* Begin PBXFileReference section */
0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = ""; };
0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; };
- 43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; };
+ 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; };
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWConnection+Async.swift"; sourceTree = ""; };
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewlineProtocolFramer.swift; sourceTree = ""; };
D00117382B30341C00D87C25 /* libBurrowShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBurrowShared.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -78,6 +86,7 @@
D00117412B30347800D87C25 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
D00117422B30348D00D87C25 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; };
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ D01A79302B81630D0024EC91 /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; };
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; };
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; };
D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -92,19 +101,26 @@
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetworkExtension-iOS.entitlements"; sourceTree = ""; };
D020F66829E4AA74002790F6 /* App-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-iOS.entitlements"; sourceTree = ""; };
D020F66929E4AA74002790F6 /* App-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-macOS.entitlements"; sourceTree = ""; };
+ D032E6512B8A79C20006B8AD /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = ""; };
+ D032E6532B8A79DA0006B8AD /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = ""; };
D05B9F7229E39EEC008CB1F9 /* Burrow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Burrow.app; sourceTree = BUILT_PRODUCTS_DIR; };
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowApp.swift; sourceTree = ""; };
- D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelView.swift; sourceTree = ""; };
+ D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.swift; sourceTree = ""; };
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingButtonStyle.swift; sourceTree = ""; };
D08252742B5C9DEB005DA378 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; };
D08252752B5C9FC4005DA378 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
+ D09150412B9D2AF700BE3CB0 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; };
D0B98FBF29FD8072004E7149 /* build-rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "build-rust.sh"; sourceTree = ""; };
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = ""; };
D0B98FD829FDDB6F004E7149 /* libburrow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libburrow.h; sourceTree = ""; };
D0B98FDC29FDDDCF004E7149 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = ""; };
- D0BCC5FE2A086E1C00AD070D /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; };
D0BCC6032A09535900AD070D /* libburrow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libburrow.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = ""; };
+ D0FAB5952B818B2900F6A84B /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = ""; };
+ D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = ""; };
+ D0FAB5992B818B9600F6A84B /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -135,14 +151,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 43AA26D62A0FFFD000F14CE6 /* Menu */ = {
- isa = PBXGroup;
- children = (
- 43AA26D72A10004900F14CE6 /* MenuView.swift */,
- );
- path = Menu;
- sourceTree = "";
- };
D00117392B30341C00D87C25 /* Shared */ = {
isa = PBXGroup;
children = (
@@ -199,6 +207,16 @@
path = NetworkExtension;
sourceTree = "";
};
+ D032E64D2B8A69C90006B8AD /* Networks */ = {
+ isa = PBXGroup;
+ children = (
+ D0FAB5992B818B9600F6A84B /* Network.swift */,
+ D032E6512B8A79C20006B8AD /* HackClub.swift */,
+ D032E6532B8A79DA0006B8AD /* WireGuard.swift */,
+ );
+ path = Networks;
+ sourceTree = "";
+ };
D05B9F6929E39EEC008CB1F9 = {
isa = PBXGroup;
children = (
@@ -224,14 +242,20 @@
D05B9F7429E39EEC008CB1F9 /* App */ = {
isa = PBXGroup;
children = (
- 43AA26D62A0FFFD000F14CE6 /* Menu */,
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
D00AA8962A4669BC005C8102 /* AppDelegate.swift */,
- D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
+ 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */,
+ D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */,
+ D01A79302B81630D0024EC91 /* NetworkView.swift */,
+ D032E64D2B8A69C90006B8AD /* Networks */,
+ D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */,
+ D0FAB5952B818B2900F6A84B /* TunnelButton.swift */,
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
- D0BCC5FE2A086E1C00AD070D /* Status.swift */,
+ D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */,
D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */,
+ D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */,
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */,
+ D09150412B9D2AF700BE3CB0 /* MainMenu.xib */,
D020F66829E4AA74002790F6 /* App-iOS.entitlements */,
D020F66929E4AA74002790F6 /* App-macOS.entitlements */,
D020F64929E4A34B002790F6 /* App.xcconfig */,
@@ -319,7 +343,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1510;
- LastUpgradeCheck = 1430;
+ LastUpgradeCheck = 1520;
TargetAttributes = {
D00117372B30341C00D87C25 = {
CreatedOnToolsVersion = 15.1;
@@ -369,6 +393,7 @@
buildActionMask = 2147483647;
files = (
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */,
+ D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -423,12 +448,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */,
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */,
- 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
- D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
- D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */,
+ D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */,
+ 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */,
+ D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */,
+ D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */,
+ D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */,
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */,
+ D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */,
+ D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */,
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,
+ D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */,
+ D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */,
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -568,8 +600,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/SwiftLint.git";
requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 0.54.0;
+ branch = main;
+ kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 7522840..9378372 100644
--- a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
- "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
- "version" : "1.2.2"
+ "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
+ "version" : "1.2.3"
}
},
{
@@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
- "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
- "version" : "509.0.2"
+ "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
+ "version" : "509.1.1"
}
},
{
@@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint.git",
"state" : {
- "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
- "version" : "0.54.0"
+ "branch" : "main",
+ "revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f"
}
},
{
@@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
- "revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
- "version" : "7.0.1"
+ "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
+ "version" : "7.0.2"
}
},
{
diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
index c63f8e6..670823d 100644
--- a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
+++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
@@ -1,6 +1,6 @@
: Codable where T: Codable {
struct ServerConfigData: Codable {
struct InternalConfig: Codable {
- let address: String?
+ let address: [String]
let name: String?
let mtu: Int32?
}
diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift
index 9231676..a07daa3 100644
--- a/Apple/NetworkExtension/PacketTunnelProvider.swift
+++ b/Apple/NetworkExtension/PacketTunnelProvider.swift
@@ -6,10 +6,16 @@ import os
class PacketTunnelProvider: NEPacketTunnelProvider {
private let logger = Logger.logger(for: PacketTunnelProvider.self)
- override func startTunnel(options: [String: NSObject]? = nil) async throws {
+ override init() {
do {
libburrow.spawnInProcess(socketPath: try Constants.socketURL.path)
+ } catch {
+ logger.error("Failed to spawn: \(error)")
+ }
+ }
+ override func startTunnel(options: [String: NSObject]? = nil) async throws {
+ do {
let client = try Client()
let command = BurrowRequest(id: 0, command: "ServerConfig")
@@ -31,7 +37,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
command: BurrowStartRequest(
Start: BurrowStartRequest.StartOptions(
tun: BurrowStartRequest.TunOptions(
- name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil
+ name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
)
)
)
@@ -44,14 +50,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
}
+ override func stopTunnel(with reason: NEProviderStopReason) async {
+ do {
+ let client = try Client()
+ let command = BurrowRequest(id: 0, command: "Stop")
+ let data = try await client.request(command, type: Response>.self)
+ self.logger.log("Stopped client.")
+ } catch {
+ self.logger.error("Failed to stop tunnel: \(error)")
+ }
+ }
+
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
let cfig = from.ServerConfig
- guard let addr = cfig.address else {
- return nil
- }
- // Using a makeshift remote tunnel address
let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
- nst.ipv4Settings = NEIPv4Settings(addresses: [addr], subnetMasks: ["255.255.255.0"])
+ var v4Addresses = [String]()
+ var v6Addresses = [String]()
+ for addr in cfig.address {
+ if IPv4Address(addr) != nil {
+ v6Addresses.append(addr)
+ }
+ if IPv6Address(addr) != nil {
+ v4Addresses.append(addr)
+ }
+ }
+ nst.ipv4Settings = NEIPv4Settings(addresses: v4Addresses, subnetMasks: v4Addresses.map { _ in
+ "255.255.255.0"
+ })
+ nst.ipv6Settings = NEIPv6Settings(addresses: v6Addresses, networkPrefixLengths: v6Addresses.map { _ in 64 })
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
return nst
}
diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh
index 1ac73fb..fffa0d0 100755
--- a/Apple/NetworkExtension/libburrow/build-rust.sh
+++ b/Apple/NetworkExtension/libburrow/build-rust.sh
@@ -56,10 +56,10 @@ CARGO_ARGS+=("--lib")
# Pass the configuration (Debug or Release) through to cargo
if [[ $SWIFT_ACTIVE_COMPILATION_CONDITIONS == *DEBUG* ]]; then
- CARGO_DIR="debug"
+ CARGO_TARGET_SUBDIR="debug"
else
CARGO_ARGS+=("--release")
- CARGO_DIR="release"
+ CARGO_TARGET_SUBDIR="release"
fi
if [[ -x "$(command -v rustup)" ]]; then
@@ -70,11 +70,11 @@ fi
# Run cargo without the various environment variables set by Xcode.
# Those variables can confuse cargo and the build scripts it runs.
-env -i PATH="$CARGO_PATH" cargo build "${CARGO_ARGS[@]}"
+env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" cargo build "${CARGO_ARGS[@]}"
mkdir -p "${BUILT_PRODUCTS_DIR}"
# Use `lipo` to merge the architectures together into BUILT_PRODUCTS_DIR
/usr/bin/xcrun --sdk $PLATFORM_NAME lipo \
- -create $(printf "${PROJECT_DIR}/../target/%q/${CARGO_DIR}/libburrow.a " "${RUST_TARGETS[@]}") \
+ -create $(printf "${CONFIGURATION_TEMP_DIR}/target/%q/${CARGO_TARGET_SUBDIR}/libburrow.a " "${RUST_TARGETS[@]}") \
-output "${BUILT_PRODUCTS_DIR}/libburrow.a"
diff --git a/Apple/Shared/Constants.swift b/Apple/Shared/Constants.swift
index cb56cb3..634c500 100644
--- a/Apple/Shared/Constants.swift
+++ b/Apple/Shared/Constants.swift
@@ -7,6 +7,7 @@ public enum Constants {
public static let bundleIdentifier = AppBundleIdentifier
public static let appGroupIdentifier = AppGroupIdentifier
+ public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier
public static var groupContainerURL: URL {
get throws { try _groupContainerURL.get() }
diff --git a/Apple/Shared/Constants/Constants.h b/Apple/Shared/Constants/Constants.h
index 09806c5..5278b61 100644
--- a/Apple/Shared/Constants/Constants.h
+++ b/Apple/Shared/Constants/Constants.h
@@ -7,5 +7,6 @@ NS_ASSUME_NONNULL_BEGIN
static NSString * const AppBundleIdentifier = MACRO_STRING(APP_BUNDLE_IDENTIFIER);
static NSString * const AppGroupIdentifier = MACRO_STRING(APP_GROUP_IDENTIFIER);
+static NSString * const NetworkExtensionBundleIdentifier = MACRO_STRING(NETWORK_EXTENSION_BUNDLE_IDENTIFIER);
NS_ASSUME_NONNULL_END
diff --git a/Apple/Shared/Shared.xcconfig b/Apple/Shared/Shared.xcconfig
index 50718bd..f344e8b 100644
--- a/Apple/Shared/Shared.xcconfig
+++ b/Apple/Shared/Shared.xcconfig
@@ -2,4 +2,4 @@ PRODUCT_NAME = BurrowShared
MERGEABLE_LIBRARY = YES
SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Shared/Constants
-GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER)
+GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER) NETWORK_EXTENSION_BUNDLE_IDENTIFIER=$(NETWORK_EXTENSION_BUNDLE_IDENTIFIER)
diff --git a/Cargo.lock b/Cargo.lock
index 85f11e7..a75bd28 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1074,7 +1074,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
- "socket2 0.5.5",
+ "socket2",
"tokio",
"tower-service",
"tracing",
@@ -2114,16 +2114,6 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
-[[package]]
-name = "socket2"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
-dependencies = [
- "libc",
- "winapi",
-]
-
[[package]]
name = "socket2"
version = "0.5.5"
@@ -2305,7 +2295,7 @@ dependencies = [
"mio",
"num_cpus",
"pin-project-lite",
- "socket2 0.5.5",
+ "socket2",
"tokio-macros",
"tracing",
"windows-sys 0.48.0",
@@ -2547,7 +2537,7 @@ dependencies = [
"reqwest",
"schemars",
"serde",
- "socket2 0.4.10",
+ "socket2",
"ssri",
"tempfile",
"tokio",
diff --git a/Dockerfile b/Dockerfile
index b1500bb..9f54478 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM docker.io/library/rust:1.70.0-slim-bookworm AS builder
+FROM docker.io/library/rust:1.76.0-slim-bookworm AS builder
ARG TARGETPLATFORM
ARG LLVM_VERSION=16
diff --git a/Makefile b/Makefile
index 18b4b27..d0c9bd9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-tun_num := $(shell ifconfig | awk -F 'utun|[: ]' '/utun[0-9]/ {print $$2}' | tail -n 1)
+tun := $(shell ifconfig -l | sed 's/ /\n/g' | grep utun | tail -n 1)
cargo_console := RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features
cargo_norm := RUST_BACKTRACE=1 RUST_LOG=debug cargo run
@@ -17,17 +17,33 @@ daemon:
start:
@$(cargo_norm) start
+stop:
+ @$(cargo_norm) stop
+
test-dns:
@sudo route delete 8.8.8.8
- @sudo route add 8.8.8.8 -interface utun$(tun_num)
+ @sudo route add 8.8.8.8 -interface $(tun)
@dig @8.8.8.8 hackclub.com
test-https:
@sudo route delete 193.183.0.162
- @sudo route add 193.183.0.162 -interface utun$(tun_num)
+ @sudo route add 193.183.0.162 -interface $(tun)
@curl -vv https://search.marginalia.nu
+v4_target := 146.190.62.39
test-http:
- @sudo route delete 146.190.62.39
- @sudo route add 146.190.62.39 -interface utun$(tun_num)
- @curl -vv 146.190.62.39:80
+ @sudo route delete ${v4_target}
+ @sudo route add ${v4_target} -interface $(tun)
+ @curl -vv ${v4_target}:80
+
+test-ipv4:
+ @sudo route delete ${v4_target}
+ @sudo route add ${v4_target} -interface $(tun)
+ @ping ${v4_target}
+
+v6_target := 2001:4860:4860::8888
+test-ipv6:
+ @sudo route delete ${v6_target}
+ @sudo route -n add -inet6 ${v6_target} -interface $(tun)
+ @echo preparing
+ @sudo ping6 -v ${v6_target}
diff --git a/README.md b/README.md
index 7492039..89914d0 100644
--- a/README.md
+++ b/README.md
@@ -8,13 +8,14 @@ Burrow is an open source tool for burrowing through firewalls, built by teenager
## Contributing
-Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! For more information on how to contribute, please see [CONTRIBUTING.md]
+Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! Checkout [GETTING_STARTED.md](./docs/GETTING_STARTED.md) for build instructions and [GTK_APP.md](./docs/GTK_APP.md) for the Linux app.
The project structure is divided in the following folders:
```
Apple/ # Xcode project for burrow on macOS and iOS
burrow/ # Higher-level API library for tun and tun-async
+burrow-gtk/ # GTK project for burrow on Linux
tun/ # Low-level interface to OS networking
src/
tokio/ # Async/Tokio code
diff --git a/burrow-gtk/Cargo.lock b/burrow-gtk/Cargo.lock
index d0b7009..6721318 100644
--- a/burrow-gtk/Cargo.lock
+++ b/burrow-gtk/Cargo.lock
@@ -257,16 +257,16 @@ dependencies = [
"caps",
"chacha20poly1305",
"clap",
- "env_logger",
+ "console",
"fehler",
"futures",
"hmac",
"ip_network",
"ip_network_table",
- "ipnet",
"libsystemd",
"log",
- "nix",
+ "nix 0.27.1",
+ "once_cell",
"parking_lot",
"rand",
"rand_core",
@@ -281,7 +281,6 @@ dependencies = [
"tracing-oslog",
"tracing-subscriber",
"tun",
- "uuid",
"x25519-dalek",
]
@@ -332,11 +331,11 @@ dependencies = [
[[package]]
name = "cairo-rs"
-version = "0.18.5"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
+checksum = "ab3603c4028a5e368d09b51c8b624b9a46edcd7c3778284077a6125af73c9f0a"
dependencies = [
- "bitflags 2.4.2",
+ "bitflags 1.3.2",
"cairo-sys-rs",
"glib",
"libc",
@@ -346,9 +345,9 @@ dependencies = [
[[package]]
name = "cairo-sys-rs"
-version = "0.18.2"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+checksum = "691d0c66b1fb4881be80a760cb8fe76ea97218312f9dfe2c9cc0f496ca279cb1"
dependencies = [
"glib-sys",
"libc",
@@ -501,6 +500,19 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -617,6 +629,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
[[package]]
name = "encoding_rs"
version = "0.8.33"
@@ -626,19 +644,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "env_logger"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
-dependencies = [
- "humantime",
- "is-terminal",
- "log",
- "regex",
- "termcolor",
-]
-
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -730,13 +735,14 @@ dependencies = [
[[package]]
name = "flume"
-version = "0.11.0"
+version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
+ "pin-project",
"spin",
]
@@ -867,10 +873,11 @@ dependencies = [
[[package]]
name = "gdk-pixbuf"
-version = "0.18.5"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
+checksum = "695d6bc846438c5708b07007537b9274d883373dd30858ca881d7d71b5540717"
dependencies = [
+ "bitflags 1.3.2",
"gdk-pixbuf-sys",
"gio",
"glib",
@@ -880,9 +887,9 @@ dependencies = [
[[package]]
name = "gdk-pixbuf-sys"
-version = "0.18.0"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
+checksum = "9285ec3c113c66d7d0ab5676599176f1f42f4944ca1b581852215bf5694870cb"
dependencies = [
"gio-sys",
"glib-sys",
@@ -893,10 +900,11 @@ dependencies = [
[[package]]
name = "gdk4"
-version = "0.7.3"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6"
+checksum = "c3abf96408a26e3eddf881a7f893a1e111767137136e347745e8ea6ed12731ff"
dependencies = [
+ "bitflags 1.3.2",
"cairo-rs",
"gdk-pixbuf",
"gdk4-sys",
@@ -908,9 +916,9 @@ dependencies = [
[[package]]
name = "gdk4-sys"
-version = "0.7.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0"
+checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -974,10 +982,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gio"
-version = "0.18.4"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
+checksum = "a6973e92937cf98689b6a054a9e56c657ed4ff76de925e36fc331a15f0c5d30a"
dependencies = [
+ "bitflags 1.3.2",
"futures-channel",
"futures-core",
"futures-io",
@@ -993,9 +1002,9 @@ dependencies = [
[[package]]
name = "gio-sys"
-version = "0.18.1"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -1006,11 +1015,11 @@ dependencies = [
[[package]]
name = "glib"
-version = "0.18.5"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
+checksum = "d3fad45ba8d4d2cea612b432717e834f48031cd8853c8aaf43b2c79fec8d144b"
dependencies = [
- "bitflags 2.4.2",
+ "bitflags 1.3.2",
"futures-channel",
"futures-core",
"futures-executor",
@@ -1035,23 +1044,24 @@ checksum = "3431c56f463443cba9bc3600248bc6d680cb614c2ee1cdd39dab5415bd12ac5c"
[[package]]
name = "glib-macros"
-version = "0.18.5"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
+checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26"
dependencies = [
+ "anyhow",
"heck",
- "proc-macro-crate 2.0.1",
+ "proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 1.0.109",
]
[[package]]
name = "glib-sys"
-version = "0.18.1"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
+checksum = "d80aa6ea7bba0baac79222204aa786a6293078c210abe69ef1336911d4bdc4f0"
dependencies = [
"libc",
"system-deps",
@@ -1065,9 +1075,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gobject-sys"
-version = "0.18.0"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
+checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062"
dependencies = [
"glib-sys",
"libc",
@@ -1076,9 +1086,9 @@ dependencies = [
[[package]]
name = "graphene-rs"
-version = "0.18.1"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401"
+checksum = "def4bb01265b59ed548b05455040d272d989b3012c42d4c1bbd39083cb9b40d9"
dependencies = [
"glib",
"graphene-sys",
@@ -1087,9 +1097,9 @@ dependencies = [
[[package]]
name = "graphene-sys"
-version = "0.18.1"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59"
+checksum = "1856fc817e6a6675e36cea0bd9a3afe296f5d9709d1e2d3182803ac77f0ab21d"
dependencies = [
"glib-sys",
"libc",
@@ -1099,10 +1109,11 @@ dependencies = [
[[package]]
name = "gsk4"
-version = "0.7.3"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e"
+checksum = "6f01ef44fa7cac15e2da9978529383e6bee03e570ba5bf7036b4c10a15cc3a3c"
dependencies = [
+ "bitflags 1.3.2",
"cairo-rs",
"gdk4",
"glib",
@@ -1114,9 +1125,9 @@ dependencies = [
[[package]]
name = "gsk4-sys"
-version = "0.7.3"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55"
+checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@@ -1130,10 +1141,11 @@ dependencies = [
[[package]]
name = "gtk4"
-version = "0.7.3"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842"
+checksum = "b28a32a04cd75cef14a0983f8b0c669e0fe152a0a7725accdeb594e2c764c88b"
dependencies = [
+ "bitflags 1.3.2",
"cairo-rs",
"field-offset",
"futures-channel",
@@ -1146,17 +1158,18 @@ dependencies = [
"gtk4-macros",
"gtk4-sys",
"libc",
+ "once_cell",
"pango",
]
[[package]]
name = "gtk4-macros"
-version = "0.7.2"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f"
+checksum = "6a4d6b61570f76d3ee542d984da443b1cd69b6105264c61afec3abed08c2500f"
dependencies = [
"anyhow",
- "proc-macro-crate 1.3.1",
+ "proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -1165,9 +1178,9 @@ dependencies = [
[[package]]
name = "gtk4-sys"
-version = "0.7.3"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8"
+checksum = "5f8283f707b07e019e76c7f2934bdd4180c277e08aa93f4c0d8dd07b7a34e22f"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -1277,12 +1290,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
-[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
[[package]]
name = "hyper"
version = "0.14.28"
@@ -1376,20 +1383,6 @@ name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "is-terminal"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
-dependencies = [
- "hermit-abi",
- "rustix",
- "windows-sys 0.52.0",
-]
[[package]]
name = "itoa"
@@ -1429,10 +1422,11 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libadwaita"
-version = "0.5.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c"
+checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf"
dependencies = [
+ "bitflags 1.3.2",
"gdk-pixbuf",
"gdk4",
"gio",
@@ -1445,9 +1439,9 @@ dependencies = [
[[package]]
name = "libadwaita-sys"
-version = "0.5.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a"
+checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404"
dependencies = [
"gdk4-sys",
"gio-sys",
@@ -1487,14 +1481,14 @@ dependencies = [
[[package]]
name = "libsystemd"
-version = "0.6.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88b9597a67aa1c81a6624603e6bd0bcefb9e0f94c9c54970ec53771082104b4e"
+checksum = "c592dc396b464005f78a5853555b9f240bc5378bf5221acc4e129910b2678869"
dependencies = [
"hmac",
"libc",
"log",
- "nix",
+ "nix 0.27.1",
"nom",
"once_cell",
"serde",
@@ -1675,6 +1669,18 @@ dependencies = [
"pin-utils",
]
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.9.0",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -1807,10 +1813,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pango"
-version = "0.18.3"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
+checksum = "35be456fc620e61f62dff7ff70fbd54dcbaf0a4b920c0f16de1107c47d921d48"
dependencies = [
+ "bitflags 1.3.2",
"gio",
"glib",
"libc",
@@ -1820,9 +1827,9 @@ dependencies = [
[[package]]
name = "pango-sys"
-version = "0.18.0"
+version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
+checksum = "3da69f9f3850b0d8990d462f8c709561975e95f689c1cdf0fecdebde78b35195"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -1894,6 +1901,26 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+[[package]]
+name = "pin-project"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.13"
@@ -1961,16 +1988,6 @@ dependencies = [
"toml_edit 0.19.15",
]
-[[package]]
-name = "proc-macro-crate"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a"
-dependencies = [
- "toml_datetime",
- "toml_edit 0.20.2",
-]
-
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -2098,8 +2115,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "relm4"
-version = "0.7.0-beta.2"
-source = "git+https://github.com/Relm4/Relm4#e189eee06b887470e0fd65cbaf6d7c0161bed5ea"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c16f3fad883034773b7f5af4d7e865532b8f3641e5a8bab2a34561a8d960d81"
dependencies = [
"async-trait",
"flume",
@@ -2115,8 +2133,9 @@ dependencies = [
[[package]]
name = "relm4-macros"
-version = "0.7.0-beta.2"
-source = "git+https://github.com/Relm4/Relm4#e189eee06b887470e0fd65cbaf6d7c0161bed5ea"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735"
dependencies = [
"proc-macro2",
"quote",
@@ -2547,15 +2566,6 @@ dependencies = [
"windows-sys 0.52.0",
]
-[[package]]
-name = "termcolor"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
-dependencies = [
- "winapi-util",
-]
-
[[package]]
name = "thiserror"
version = "1.0.56"
@@ -2633,6 +2643,7 @@ dependencies = [
"pin-project-lite",
"socket2 0.5.5",
"tokio-macros",
+ "tracing",
"windows-sys 0.48.0",
]
@@ -2839,7 +2850,7 @@ dependencies = [
"libc",
"libloading 0.7.4",
"log",
- "nix",
+ "nix 0.26.4",
"reqwest",
"schemars",
"serde",
@@ -2925,7 +2936,6 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [
- "getrandom",
"serde",
]
@@ -3078,15 +3088,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-[[package]]
-name = "winapi-util"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
-dependencies = [
- "winapi",
-]
-
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
diff --git a/burrow-gtk/Cargo.toml b/burrow-gtk/Cargo.toml
index 244c161..21cb52e 100644
--- a/burrow-gtk/Cargo.toml
+++ b/burrow-gtk/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
-relm4 = { features = ["libadwaita", "gnome_45"], git = "https://github.com/Relm4/Relm4" }
+relm4 = { version = "0.6", features = ["libadwaita", "gnome_44"]}
burrow = { version = "*", path = "../burrow/" }
tokio = { version = "1.35.0", features = ["time", "sync"] }
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
diff --git a/burrow-gtk/build-aux/Dockerfile b/burrow-gtk/build-aux/Dockerfile
new file mode 100644
index 0000000..df07c4a
--- /dev/null
+++ b/burrow-gtk/build-aux/Dockerfile
@@ -0,0 +1,18 @@
+FROM fedora:39
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN set -eux && \
+ dnf update -y && \
+ dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file
+
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+WORKDIR /app
+COPY . /app
+
+RUN cd /app/burrow-gtk/ && \
+ ./build-aux/build_appimage.sh
+
+
diff --git a/burrow-gtk/build-aux/build_appimage.sh b/burrow-gtk/build-aux/build_appimage.sh
new file mode 100755
index 0000000..cd58c17
--- /dev/null
+++ b/burrow-gtk/build-aux/build_appimage.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -ex
+
+BURROW_GTK_ROOT="$(readlink -f $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")/..)"
+BURROW_GTK_BUILD="$BURROW_GTK_ROOT/build-appimage"
+LINUXDEPLOY_VERSION="${LINUXDEPLOY_VERSION:-"1-alpha-20240109-1"}"
+BURROW_BUILD_TYPE="${BURROW_BUILD_TYPE:-"release"}"
+
+if [ "$BURROW_GTK_ROOT" != $(pwd) ]; then
+ echo "Make sure to cd into burrow-gtk"
+ exit 1
+fi
+
+ARCHITECTURE=$(lscpu | grep Architecture | awk '{print $2}')
+
+if [ "$ARCHITECTURE" == "x86_64" ]; then
+ wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VERSION/linuxdeploy-x86_64.AppImage" -o /dev/null -O /tmp/linuxdeploy
+ chmod a+x /tmp/linuxdeploy
+elif [ "$ARCHITECTURE" == "aarch64" ]; then
+ wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VERSION/linuxdeploy-aarch64.AppImage" -o /dev/null -O /tmp/linuxdeploy
+ chmod a+x /tmp/linuxdeploy
+fi
+
+meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE
+meson compile -C $BURROW_GTK_BUILD
+DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD
+cargo b --$BURROW_BUILD_TYPE --manifest-path=../Cargo.toml
+/tmp/linuxdeploy --appimage-extract-and-run --appdir $BURROW_GTK_BUILD/AppDir -e $BURROW_GTK_BUILD/../../target/$BURROW_BUILD_TYPE/burrow --output appimage
+mv *.AppImage $BURROW_GTK_BUILD
diff --git a/burrow-gtk/meson.build b/burrow-gtk/meson.build
index 70d8403..8c2d5c1 100644
--- a/burrow-gtk/meson.build
+++ b/burrow-gtk/meson.build
@@ -34,8 +34,8 @@ i18n = import('i18n')
gnome = import('gnome')
# External Dependencies
-dependency('gtk4', version: '>= 4.12')
-dependency('libadwaita-1', version: '>= 1.4')
+dependency('gtk4', version: '>= 4.0')
+dependency('libadwaita-1', version: '>= 1.2')
glib_compile_resources = find_program('glib-compile-resources', required: true)
glib_compile_schemas = find_program('glib-compile-schemas', required: true)
diff --git a/burrow-gtk/src/components/app.rs b/burrow-gtk/src/components/app.rs
index b42b718..62c98c0 100644
--- a/burrow-gtk/src/components/app.rs
+++ b/burrow-gtk/src/components/app.rs
@@ -6,7 +6,7 @@ const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5);
pub struct App {
daemon_client: Arc>>,
- _settings_screen: Controller,
+ settings_screen: Controller,
switch_screen: AsyncController,
}
@@ -81,23 +81,35 @@ impl AsyncComponent for App {
let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build();
view_switcher_bar.set_reveal(true);
- let toolbar = adw::ToolbarView::new();
- toolbar.add_top_bar(
+ // When libadwaita 1.4 support becomes more avaliable, this approach is more appropriate
+ //
+ // let toolbar = adw::ToolbarView::new();
+ // toolbar.add_top_bar(
+ // &adw::HeaderBar::builder()
+ // .title_widget(>k::Label::new(Some("Burrow")))
+ // .build(),
+ // );
+ // toolbar.add_bottom_bar(&view_switcher_bar);
+ // toolbar.set_content(Some(&view_stack));
+ // root.set_content(Some(&toolbar));
+
+ let content = gtk::Box::new(gtk::Orientation::Vertical, 0);
+ content.append(
&adw::HeaderBar::builder()
.title_widget(>k::Label::new(Some("Burrow")))
.build(),
);
- toolbar.add_bottom_bar(&view_switcher_bar);
- toolbar.set_content(Some(&view_stack));
+ content.append(&view_stack);
+ content.append(&view_switcher_bar);
- root.set_content(Some(&toolbar));
+ root.set_content(Some(&content));
sender.input(AppMsg::PostInit);
let model = App {
daemon_client,
switch_screen,
- _settings_screen: settings_screen,
+ settings_screen,
};
AsyncComponentParts { model, widgets }
@@ -120,14 +132,23 @@ impl AsyncComponent for App {
disconnected_daemon_client = true;
self.switch_screen
.emit(switch_screen::SwitchScreenMsg::DaemonDisconnect);
+ self.settings_screen
+ .emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
}
}
if disconnected_daemon_client || daemon_client.is_none() {
- *daemon_client = DaemonClient::new().await.ok();
- if daemon_client.is_some() {
- self.switch_screen
- .emit(switch_screen::SwitchScreenMsg::DaemonReconnect);
+ match DaemonClient::new().await {
+ Ok(new_daemon_client) => {
+ *daemon_client = Some(new_daemon_client);
+ self.switch_screen
+ .emit(switch_screen::SwitchScreenMsg::DaemonReconnect);
+ self.settings_screen
+ .emit(settings_screen::SettingsScreenMsg::DaemonStateChange)
+ }
+ Err(_e) => {
+ // TODO: Handle Error
+ }
}
}
}
diff --git a/burrow-gtk/src/components/mod.rs b/burrow-gtk/src/components/mod.rs
index b1cc938..b134809 100644
--- a/burrow-gtk/src/components/mod.rs
+++ b/burrow-gtk/src/components/mod.rs
@@ -18,3 +18,4 @@ mod settings_screen;
mod switch_screen;
pub use app::*;
+pub use settings::{DaemonGroupMsg, DiagGroupMsg};
diff --git a/burrow-gtk/src/components/settings/daemon_group.rs b/burrow-gtk/src/components/settings/daemon_group.rs
new file mode 100644
index 0000000..3817ca6
--- /dev/null
+++ b/burrow-gtk/src/components/settings/daemon_group.rs
@@ -0,0 +1,111 @@
+use super::*;
+use std::process::Command;
+
+#[derive(Debug)]
+pub struct DaemonGroup {
+ system_setup: SystemSetup,
+ daemon_client: Arc>>,
+ already_running: bool,
+}
+
+pub struct DaemonGroupInit {
+ pub daemon_client: Arc>>,
+ pub system_setup: SystemSetup,
+}
+
+#[derive(Debug)]
+pub enum DaemonGroupMsg {
+ LaunchLocal,
+ DaemonStateChange,
+}
+
+#[relm4::component(pub, async)]
+impl AsyncComponent for DaemonGroup {
+ type Init = DaemonGroupInit;
+ type Input = DaemonGroupMsg;
+ type Output = ();
+ type CommandOutput = ();
+
+ view! {
+ #[name(group)]
+ adw::PreferencesGroup {
+ #[watch]
+ set_sensitive:
+ (model.system_setup == SystemSetup::AppImage || model.system_setup == SystemSetup::Other) &&
+ !model.already_running,
+ set_title: "Local Daemon",
+ set_description: Some("Run Local Daemon"),
+
+ gtk::Button {
+ set_label: "Launch",
+ connect_clicked => DaemonGroupMsg::LaunchLocal
+ }
+ }
+ }
+
+ async fn init(
+ init: Self::Init,
+ root: Self::Root,
+ sender: AsyncComponentSender,
+ ) -> AsyncComponentParts {
+ // Should be impossible to panic here
+ let model = DaemonGroup {
+ system_setup: init.system_setup,
+ daemon_client: init.daemon_client.clone(),
+ already_running: init.daemon_client.lock().await.is_some(),
+ };
+
+ let widgets = view_output!();
+
+ AsyncComponentParts { model, widgets }
+ }
+
+ async fn update(
+ &mut self,
+ msg: Self::Input,
+ _sender: AsyncComponentSender,
+ _root: &Self::Root,
+ ) {
+ match msg {
+ DaemonGroupMsg::LaunchLocal => {
+ let burrow_original_bin = std::env::vars()
+ .find(|(k, _)| k == "APPDIR")
+ .map(|(_, v)| v + "/usr/bin/burrow")
+ .unwrap_or("/usr/bin/burrow".to_owned());
+
+ let mut burrow_bin =
+ String::from_utf8(Command::new("mktemp").output().unwrap().stdout).unwrap();
+ burrow_bin.pop();
+
+ let privileged_spawn_script = format!(
+ r#"TEMP=$(mktemp -p /root)
+cp {} $TEMP
+chmod +x $TEMP
+setcap CAP_NET_BIND_SERVICE,CAP_NET_ADMIN+eip $TEMP
+mv $TEMP /tmp/burrow-detached-daemon"#,
+ burrow_original_bin
+ )
+ .replace('\n', "&&");
+
+ // TODO: Handle error condition
+
+ Command::new("pkexec")
+ .arg("sh")
+ .arg("-c")
+ .arg(privileged_spawn_script)
+ .arg(&burrow_bin)
+ .output()
+ .unwrap();
+
+ Command::new("/tmp/burrow-detached-daemon")
+ .env("RUST_LOG", "debug")
+ .arg("daemon")
+ .spawn()
+ .unwrap();
+ }
+ DaemonGroupMsg::DaemonStateChange => {
+ self.already_running = self.daemon_client.lock().await.is_some();
+ }
+ }
+ }
+}
diff --git a/burrow-gtk/src/components/settings/diag_group.rs b/burrow-gtk/src/components/settings/diag_group.rs
index be542cd..a15e0ea 100644
--- a/burrow-gtk/src/components/settings/diag_group.rs
+++ b/burrow-gtk/src/components/settings/diag_group.rs
@@ -1,11 +1,10 @@
use super::*;
-use diag::{StatusTernary, SystemSetup};
#[derive(Debug)]
pub struct DiagGroup {
daemon_client: Arc>>,
- init_system: SystemSetup,
+ system_setup: SystemSetup,
service_installed: StatusTernary,
socket_installed: StatusTernary,
socket_enabled: StatusTernary,
@@ -14,19 +13,20 @@ pub struct DiagGroup {
pub struct DiagGroupInit {
pub daemon_client: Arc>>,
+ pub system_setup: SystemSetup,
}
impl DiagGroup {
async fn new(daemon_client: Arc>>) -> Result {
- let setup = SystemSetup::new();
+ let system_setup = SystemSetup::new();
let daemon_running = daemon_client.lock().await.is_some();
Ok(Self {
- service_installed: setup.is_service_installed()?,
- socket_installed: setup.is_socket_installed()?,
- socket_enabled: setup.is_socket_enabled()?,
+ service_installed: system_setup.is_service_installed()?,
+ socket_installed: system_setup.is_socket_installed()?,
+ socket_enabled: system_setup.is_socket_enabled()?,
daemon_running,
- init_system: setup,
+ system_setup,
daemon_client,
})
}
@@ -52,7 +52,7 @@ impl AsyncComponent for DiagGroup {
adw::ActionRow {
#[watch]
- set_title: &format!("Init System: {}", model.init_system)
+ set_title: &format!("System Type: {}", model.system_setup)
},
adw::ActionRow {
#[watch]
diff --git a/burrow-gtk/src/components/settings/mod.rs b/burrow-gtk/src/components/settings/mod.rs
index 53f46d4..aa87db2 100644
--- a/burrow-gtk/src/components/settings/mod.rs
+++ b/burrow-gtk/src/components/settings/mod.rs
@@ -1,5 +1,8 @@
use super::*;
+use diag::{StatusTernary, SystemSetup};
+mod daemon_group;
mod diag_group;
-pub use diag_group::{DiagGroup, DiagGroupInit};
+pub use daemon_group::{DaemonGroup, DaemonGroupInit, DaemonGroupMsg};
+pub use diag_group::{DiagGroup, DiagGroupInit, DiagGroupMsg};
diff --git a/burrow-gtk/src/components/settings_screen.rs b/burrow-gtk/src/components/settings_screen.rs
index 778eb84..971f262 100644
--- a/burrow-gtk/src/components/settings_screen.rs
+++ b/burrow-gtk/src/components/settings_screen.rs
@@ -1,17 +1,24 @@
use super::*;
+use diag::SystemSetup;
pub struct SettingsScreen {
- _diag_group: AsyncController,
+ diag_group: AsyncController,
+ daemon_group: AsyncController,
}
pub struct SettingsScreenInit {
pub daemon_client: Arc>>,
}
+#[derive(Debug, PartialEq, Eq)]
+pub enum SettingsScreenMsg {
+ DaemonStateChange,
+}
+
#[relm4::component(pub)]
impl SimpleComponent for SettingsScreen {
type Init = SettingsScreenInit;
- type Input = ();
+ type Input = SettingsScreenMsg;
type Output = ();
view! {
@@ -21,24 +28,44 @@ impl SimpleComponent for SettingsScreen {
fn init(
init: Self::Init,
- root: Self::Root,
+ root: &Self::Root,
sender: ComponentSender,
) -> ComponentParts {
+ let system_setup = SystemSetup::new();
+
let diag_group = settings::DiagGroup::builder()
.launch(settings::DiagGroupInit {
+ system_setup,
daemon_client: Arc::clone(&init.daemon_client),
})
- .forward(sender.input_sender(), |_| ());
+ .forward(sender.input_sender(), |_| {
+ SettingsScreenMsg::DaemonStateChange
+ });
+
+ let daemon_group = settings::DaemonGroup::builder()
+ .launch(settings::DaemonGroupInit {
+ system_setup,
+ daemon_client: Arc::clone(&init.daemon_client),
+ })
+ .forward(sender.input_sender(), |_| {
+ SettingsScreenMsg::DaemonStateChange
+ });
let widgets = view_output!();
widgets.preferences.add(diag_group.widget());
+ widgets.preferences.add(daemon_group.widget());
- let model = SettingsScreen {
- _diag_group: diag_group,
- };
+ let model = SettingsScreen { diag_group, daemon_group };
ComponentParts { model, widgets }
}
- fn update(&mut self, _: Self::Input, _sender: ComponentSender) {}
+ fn update(&mut self, _: Self::Input, _sender: ComponentSender) {
+ // Currently, `SettingsScreenMsg` only has one variant, so the if is ambiguous.
+ //
+ // if let SettingsScreenMsg::DaemonStateChange = msg {
+ self.diag_group.emit(DiagGroupMsg::Refresh);
+ self.daemon_group.emit(DaemonGroupMsg::DaemonStateChange);
+ // }
+ }
}
diff --git a/burrow-gtk/src/components/switch_screen.rs b/burrow-gtk/src/components/switch_screen.rs
index a296c09..f660536 100644
--- a/burrow-gtk/src/components/switch_screen.rs
+++ b/burrow-gtk/src/components/switch_screen.rs
@@ -29,7 +29,7 @@ impl AsyncComponent for SwitchScreen {
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
- set_valign: Align::BaselineFill,
+ set_valign: Align::Fill,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
diff --git a/burrow-gtk/src/diag.rs b/burrow-gtk/src/diag.rs
index 348293e..ab4757e 100644
--- a/burrow-gtk/src/diag.rs
+++ b/burrow-gtk/src/diag.rs
@@ -15,15 +15,18 @@ pub enum StatusTernary {
// Realistically, we may not explicitly "support" non-systemd platforms which would simply this
// code greatly.
// Along with replacing [`StatusTernary`] with good old [`bool`].
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SystemSetup {
Systemd,
+ AppImage,
Other,
}
impl SystemSetup {
pub fn new() -> Self {
- if Command::new("systemctl").arg("--version").output().is_ok() {
+ if is_appimage() {
+ SystemSetup::AppImage
+ } else if Command::new("systemctl").arg("--version").output().is_ok() {
SystemSetup::Systemd
} else {
SystemSetup::Other
@@ -33,6 +36,7 @@ impl SystemSetup {
pub fn is_service_installed(&self) -> Result {
match self {
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into()),
+ SystemSetup::AppImage => Ok(StatusTernary::NA),
SystemSetup::Other => Ok(StatusTernary::NA),
}
}
@@ -40,6 +44,7 @@ impl SystemSetup {
pub fn is_socket_installed(&self) -> Result {
match self {
SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into()),
+ SystemSetup::AppImage => Ok(StatusTernary::NA),
SystemSetup::Other => Ok(StatusTernary::NA),
}
}
@@ -55,6 +60,7 @@ impl SystemSetup {
let output = String::from_utf8(output)?;
Ok((output == "enabled\n").into())
}
+ SystemSetup::AppImage => Ok(StatusTernary::NA),
SystemSetup::Other => Ok(StatusTernary::NA),
}
}
@@ -74,7 +80,12 @@ impl Display for SystemSetup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
SystemSetup::Systemd => "Systemd",
+ SystemSetup::AppImage => "AppImage",
SystemSetup::Other => "Other",
})
}
}
+
+pub fn is_appimage() -> bool {
+ std::env::vars().any(|(k, _)| k == "APPDIR")
+}
diff --git a/burrow-server-compose.yml b/burrow-server-compose.yml
new file mode 100644
index 0000000..4ba31ee
--- /dev/null
+++ b/burrow-server-compose.yml
@@ -0,0 +1,38 @@
+version: "2.1"
+networks:
+ wg6:
+ enable_ipv6: true
+ ipam:
+ driver: default
+ config:
+ - subnet: "aa:bb:cc:de::/64"
+services:
+ burrow:
+ image: lscr.io/linuxserver/wireguard:latest
+ privileged: true
+ container_name: burrow_server
+ cap_add:
+ - NET_ADMIN
+ - SYS_MODULE
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=Asia/Calcutta
+ - SERVERURL=wg.burrow.rs
+ - SERVERPORT=51820
+ - PEERS=10
+ - PEERDNS=1.1.1.1
+ - INTERNAL_SUBNET=10.13.13.0
+ - ALLOWEDIPS=0.0.0.0/0, ::/0
+ - PERSISTENTKEEPALIVE_PEERS=all
+ - LOG_CONFS=true #optional
+ volumes:
+ - ./config:/config
+ - /lib/modules:/lib/modules
+ ports:
+ - 51820:51820/udp
+ sysctls:
+ - net.ipv4.conf.all.src_valid_mark=1
+ - net.ipv6.conf.all.disable_ipv6=0
+ - net.ipv6.conf.eth0.proxy_ndp=1
+ restart: unless-stopped
\ No newline at end of file
diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs
index 34e9878..0d3e726 100644
--- a/burrow/src/daemon/instance.rs
+++ b/burrow/src/daemon/instance.rs
@@ -21,7 +21,7 @@ enum RunState {
pub struct DaemonInstance {
rx: async_channel::Receiver,
sx: async_channel::Sender,
- tun_interface: Option>>,
+ tun_interface: Arc>>,
wg_interface: Arc>,
wg_state: RunState,
}
@@ -36,7 +36,7 @@ impl DaemonInstance {
rx,
sx,
wg_interface,
- tun_interface: None,
+ tun_interface: Arc::new(RwLock::new(None)),
wg_state: RunState::Idle,
}
}
@@ -50,15 +50,15 @@ impl DaemonInstance {
warn!("Got start, but tun interface already up.");
}
RunState::Idle => {
- let tun_if = Arc::new(RwLock::new(st.tun.open()?));
+ let tun_if = st.tun.open()?;
+ debug!("Setting tun on wg_interface");
+ self.wg_interface.read().await.set_tun(tun_if).await;
+ debug!("tun set on wg_interface");
debug!("Setting tun_interface");
- self.tun_interface = Some(tun_if.clone());
+ self.tun_interface = self.wg_interface.read().await.get_tun();
debug!("tun_interface set: {:?}", self.tun_interface);
- debug!("Setting tun on wg_interface");
- self.wg_interface.write().await.set_tun(tun_if);
- debug!("tun set on wg_interface");
debug!("Cloning wg_interface");
let tmp_wg = self.wg_interface.clone();
@@ -82,22 +82,18 @@ impl DaemonInstance {
}
Ok(DaemonResponseData::None)
}
- DaemonCommand::ServerInfo => match &self.tun_interface {
+ DaemonCommand::ServerInfo => match &self.tun_interface.read().await.as_ref() {
None => Ok(DaemonResponseData::None),
Some(ti) => {
info!("{:?}", ti);
Ok(DaemonResponseData::ServerInfo(ServerInfo::try_from(
- ti.read().await.inner.get_ref(),
+ ti.inner.get_ref(),
)?))
}
},
DaemonCommand::Stop => {
- if self.tun_interface.is_some() {
- self.tun_interface = None;
- info!("Daemon stopping tun interface.");
- } else {
- warn!("Got stop, but tun interface is not up.")
- }
+ self.wg_interface.read().await.remove_tun().await;
+ self.wg_state = RunState::Idle;
Ok(DaemonResponseData::None)
}
DaemonCommand::ServerConfig => {
diff --git a/burrow/src/daemon/response.rs b/burrow/src/daemon/response.rs
index 172d4c7..37ee5d9 100644
--- a/burrow/src/daemon/response.rs
+++ b/burrow/src/daemon/response.rs
@@ -57,7 +57,7 @@ impl TryFrom<&TunInterface> for ServerInfo {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ServerConfig {
- pub address: Option,
+ pub address: Vec,
pub name: Option,
pub mtu: Option,
}
@@ -65,7 +65,7 @@ pub struct ServerConfig {
impl Default for ServerConfig {
fn default() -> Self {
Self {
- address: Some("10.13.13.2".to_string()), // Dummy remote address
+ address: vec!["10.13.13.2".to_string()], // Dummy remote address
name: None,
mtu: None,
}
diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap
index 0eb9096..f78eeaa 100644
--- a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap
+++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap
@@ -2,4 +2,4 @@
source: burrow/src/daemon/command.rs
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()"
---
-{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
+{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}}
diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap
index bfd5117..eee563d 100644
--- a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap
+++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap
@@ -2,4 +2,4 @@
source: burrow/src/daemon/command.rs
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
---
-{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
+{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}}
diff --git a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap
index 9752ebc..0b9385c 100644
--- a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap
+++ b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap
@@ -2,4 +2,4 @@
source: burrow/src/daemon/response.rs
expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
---
-{"result":{"Ok":{"ServerConfig":{"address":"10.13.13.2","name":null,"mtu":null}}},"id":0}
+{"result":{"Ok":{"ServerConfig":{"address":["10.13.13.2"],"name":null,"mtu":null}}},"id":0}
diff --git a/burrow/src/main.rs b/burrow/src/main.rs
index 79bb70b..71d1c02 100644
--- a/burrow/src/main.rs
+++ b/burrow/src/main.rs
@@ -55,7 +55,7 @@ async fn try_start() -> Result<()> {
let mut client = DaemonClient::new().await?;
client
.send_command(DaemonCommand::Start(DaemonStartOptions {
- tun: TunOptions::new().address("10.13.13.2"),
+ tun: TunOptions::new().address(vec!["10.13.13.2", "::2"]),
}))
.await
.map(|_| ())
diff --git a/burrow/src/tracing.rs b/burrow/src/tracing.rs
index 279f45d..861b41f 100644
--- a/burrow/src/tracing.rs
+++ b/burrow/src/tracing.rs
@@ -39,6 +39,7 @@ pub fn initialize() {
tracing_subscriber::fmt::layer()
.with_level(true)
.with_writer(std::io::stderr)
+ .with_line_number(true)
.compact()
.with_filter(EnvFilter::from_default_env())
});
diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs
index afe7499..ed7b3cd 100644
--- a/burrow/src/wireguard/config.rs
+++ b/burrow/src/wireguard/config.rs
@@ -42,7 +42,7 @@ pub struct Peer {
pub struct Interface {
pub private_key: String,
- pub address: String,
+ pub address: Vec,
pub listen_port: u32,
pub dns: Vec,
pub mtu: Option,
@@ -93,8 +93,8 @@ impl Default for Config {
fn default() -> Self {
Self {
interface: Interface {
- private_key: "GNqIAOCRxjl/cicZyvkvpTklgQuUmGUIEkH7IXF/sEE=".into(),
- address: "10.13.13.2/24".into(),
+ private_key: "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=".into(),
+ address: vec!["10.13.13.2/24".into()],
listen_port: 51820,
dns: Default::default(),
mtu: Default::default(),
@@ -102,8 +102,8 @@ impl Default for Config {
peers: vec![Peer {
endpoint: "wg.burrow.rs:51820".into(),
allowed_ips: vec!["8.8.8.8/32".into(), "0.0.0.0/0".into()],
- public_key: "uy75leriJay0+oHLhRMpV+A5xAQ0hCJ+q7Ww81AOvT4=".into(),
- preshared_key: Some("s7lx/mg+reVEMnGnqeyYOQkzD86n2+gYnx1M9ygi08k=".into()),
+ public_key: "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=".into(),
+ preshared_key: Some("ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=".into()),
persistent_keepalive: Default::default(),
name: Default::default(),
}],
diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs
index 620c96c..6097082 100755
--- a/burrow/src/wireguard/iface.rs
+++ b/burrow/src/wireguard/iface.rs
@@ -1,10 +1,11 @@
use std::{net::IpAddr, sync::Arc};
+use std::ops::Deref;
use anyhow::Error;
use fehler::throws;
use futures::future::join_all;
use ip_network_table::IpNetworkTable;
-use tokio::sync::RwLock;
+use tokio::sync::{RwLock, Notify};
use tracing::{debug, error};
use tun::tokio::TunInterface;
@@ -46,9 +47,21 @@ impl FromIterator for IndexedPcbs {
}
}
+enum IfaceStatus {
+ Running,
+ Idle
+}
+
pub struct Interface {
- tun: Option>>,
+ tun: Arc>>,
pcbs: Arc,
+ status: Arc>,
+ stop_notifier: Arc,
+}
+
+async fn is_running(status: Arc>) -> bool {
+ let st = status.read().await;
+ matches!(st.deref(), IfaceStatus::Running)
}
impl Interface {
@@ -60,35 +73,54 @@ impl Interface {
.collect::>()?;
let pcbs = Arc::new(pcbs);
- Self { pcbs, tun: None }
+ Self { pcbs, tun: Arc::new(RwLock::new(None)), status: Arc::new(RwLock::new(IfaceStatus::Idle)), stop_notifier: Arc::new(Notify::new()) }
}
- pub fn set_tun(&mut self, tun: Arc>) {
- self.tun = Some(tun);
+ pub async fn set_tun(&self, tun: TunInterface) {
+ debug!("Setting tun interface");
+ self.tun.write().await.replace(tun);
+ let mut st = self.status.write().await;
+ *st = IfaceStatus::Running;
+ }
+
+ pub fn get_tun(&self) -> Arc>> {
+ self.tun.clone()
+ }
+
+ pub async fn remove_tun(&self){
+ let mut st = self.status.write().await;
+ self.stop_notifier.notify_waiters();
+ *st = IfaceStatus::Idle;
}
pub async fn run(&self) -> anyhow::Result<()> {
let pcbs = self.pcbs.clone();
let tun = self
.tun
- .clone()
- .ok_or(anyhow::anyhow!("tun interface does not exist"))?;
+ .clone();
+ let status = self.status.clone();
+ let stop_notifier = self.stop_notifier.clone();
log::info!("Starting interface");
let outgoing = async move {
- loop {
+ while is_running(status.clone()).await {
let mut buf = [0u8; 3000];
let src = {
- let src = match tun.read().await.recv(&mut buf[..]).await {
- Ok(len) => &buf[..len],
- Err(e) => {
- error!("Failed to read from interface: {}", e);
- continue
- }
+ let t = tun.read().await;
+ let Some(_tun) = t.as_ref() else {
+ continue;
};
- debug!("Read {} bytes from interface", src.len());
- src
+ tokio::select! {
+ _ = stop_notifier.notified() => continue,
+ pkg = _tun.recv(&mut buf[..]) => match pkg {
+ Ok(len) => &buf[..len],
+ Err(e) => {
+ error!("Failed to read from interface: {}", e);
+ continue
+ }
+ },
+ }
};
let dst_addr = match Tunnel::dst_address(src) {
@@ -123,8 +155,7 @@ impl Interface {
let mut tsks = vec![];
let tun = self
.tun
- .clone()
- .ok_or(anyhow::anyhow!("tun interface does not exist"))?;
+ .clone();
let outgoing = tokio::task::spawn(outgoing);
tsks.push(outgoing);
debug!("preparing to spawn read tasks");
@@ -149,9 +180,10 @@ impl Interface {
};
let pcb = pcbs.pcbs[i].clone();
+ let status = self.status.clone();
let update_timers_tsk = async move {
let mut buf = [0u8; 65535];
- loop {
+ while is_running(status.clone()).await {
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
match pcb.update_timers(&mut buf).await {
Ok(..) => (),
@@ -164,8 +196,9 @@ impl Interface {
};
let pcb = pcbs.pcbs[i].clone();
+ let status = self.status.clone();
let reset_rate_limiter_tsk = async move {
- loop {
+ while is_running(status.clone()).await {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
pcb.reset_rate_limiter().await;
}
diff --git a/burrow/src/wireguard/pcb.rs b/burrow/src/wireguard/pcb.rs
index db57968..974d84e 100755
--- a/burrow/src/wireguard/pcb.rs
+++ b/burrow/src/wireguard/pcb.rs
@@ -54,7 +54,7 @@ impl PeerPcb {
Ok(())
}
- pub async fn run(&self, tun_interface: Arc>) -> Result<(), Error> {
+ pub async fn run(&self, tun_interface: Arc>>) -> Result<(), Error> {
tracing::debug!("starting read loop for pcb... for {:?}", &self);
let rid: i32 = random();
let mut buf: [u8; 3000] = [0u8; 3000];
@@ -106,12 +106,12 @@ impl PeerPcb {
}
TunnResult::WriteToTunnelV4(packet, addr) => {
tracing::debug!("WriteToTunnelV4: {:?}, {:?}", packet, addr);
- tun_interface.read().await.send(packet).await?;
+ tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?;
break
}
TunnResult::WriteToTunnelV6(packet, addr) => {
tracing::debug!("WriteToTunnelV6: {:?}, {:?}", packet, addr);
- tun_interface.read().await.send(packet).await?;
+ tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?;
break
}
}
diff --git a/docs/GTK_APP.md b/docs/GTK_APP.md
index 9b103a3..ef73d2b 100644
--- a/docs/GTK_APP.md
+++ b/docs/GTK_APP.md
@@ -1,22 +1,79 @@
# Linux GTK App Getting Started
+Currently, the GTK App can be built as a binary or as an AppImage.
+Note that the flatpak version can compile but will not run properly!
+
## Dependencies
### Install Build Dependencies
- Fedora
+ Debian
- 1. Install build dependencies
+ > Note: Burrow currently cannot compile on Debian Stable (Bookworm) due to its outdated dependencies
+
+ 1. Install build dependencies
```
- sudo dnf install clang ninja cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib
+ sudo apt install -y clang meson cmake pkg-config libgtk-4-dev libadwaita-1-dev gettext desktop-file-utils
```
2. Install flatpak builder (Optional)
```
- sudo dnf install flatpak-builder
+ sudo apt install -y flatpak-builder
+ ```
+
+ 3. Install AppImage build tools (Optional)
+
+ ```
+ sudo apt install -y wget fuse file
+ ```
+
+
+
+
+ Fedora
+
+ 1. Install build dependencies
+
+ ```
+ sudo dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib
+ ```
+
+ 2. Install flatpak builder (Optional)
+
+ ```
+ sudo dnf install -y flatpak-builder
+ ```
+
+ 3. Install AppImage build tools (Optional)
+
+ ```
+ sudo dnf install -y util-linux wget fuse fuse-libs file
+ ```
+
+
+
+
+ Void Linux (glibc)
+
+ 1. Install build dependencies
+
+ ```
+ sudo xbps-install -Sy gcc clang meson cmake pkg-config gtk4-devel gettext desktop-file-utils gtk4-update-icon-cache appstream-glib
+ ```
+
+ 2. Install flatpak builder (Optional)
+
+ ```
+ sudo xbps-install -Sy flatpak-builder
+ ```
+
+ 3. Install AppImage build tools (Optional)
+
+ ```
+ sudo xbps-install -Sy wget fuse file
```
@@ -51,10 +108,10 @@ flatpak install --user \
Flatpak
- 1. Compile and install the flatpak
+ 1. Compile and install the flatpak
```
- flatpak-builder
+ flatpak-builder
--user --install --force-clean --disable-rofiles-fuse \
flatpak_debug/ \
burrow-gtk/build-aux/com.hackclub.burrow.devel.json
@@ -62,6 +119,23 @@ flatpak install --user \
+
+ AppImage
+
+ 1. Enter the `burrow-gtk`
+
+ ```bash
+ cd burrow-gtk
+ ```
+
+ 2. Compile the AppImage
+
+ ```
+ ./build-aux/build_appimage.sh
+ ```
+
+
+
## Running
@@ -83,3 +157,14 @@ flatpak install --user \
```
+
+
+ AppImage
+
+ The compiled binary can be found in `build-appimage/Burrow-*.AppImage`.
+
+ ```
+ ./build-appimage/Burrow-*.AppImage
+ ```
+
+
diff --git a/server_patch.txt b/server_patch.txt
new file mode 100644
index 0000000..de8e14c
--- /dev/null
+++ b/server_patch.txt
@@ -0,0 +1,21 @@
+# Add this to ~/server/wg0.conf upon regeneration
+
+PostUp = iptables -A FORWARD -i %i -j ACCEPT
+
+PostUp = iptables -A FORWARD -o %i -j ACCEPT
+
+PostUp = iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
+
+PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
+
+PostUp = ip6tables -A FORWARD -o %i -j ACCEPT
+
+PostDown = iptables -D FORWARD -i %i -j ACCEPT
+
+PostDown = iptables -D FORWARD -o %i -j ACCEPT
+
+PostDown = iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE
+
+PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
+
+PostDown = ip6tables -D FORWARD -o %i -j ACCEPT
\ No newline at end of file
diff --git a/tun/Cargo.toml b/tun/Cargo.toml
index e67e45f..7413f65 100644
--- a/tun/Cargo.toml
+++ b/tun/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
libc = "0.2"
fehler = "1.0"
nix = { version = "0.26", features = ["ioctl"] }
-socket2 = "0.4"
+socket2 = "0.5"
tokio = { version = "1.28", features = [] }
byteorder = "1.4"
tracing = "0.1"
diff --git a/tun/src/options.rs b/tun/src/options.rs
index 339f71a..e21bf5f 100644
--- a/tun/src/options.rs
+++ b/tun/src/options.rs
@@ -21,7 +21,7 @@ pub struct TunOptions {
/// (Apple) Retrieve the tun interface
pub tun_retrieve: bool,
/// (Linux) The IP address of the tun interface.
- pub address: Option,
+ pub address: Vec,
}
impl TunOptions {
@@ -44,8 +44,8 @@ impl TunOptions {
self
}
- pub fn address(mut self, address: impl ToString) -> Self {
- self.address = Some(address.to_string());
+ pub fn address(mut self, address: Vec) -> Self {
+ self.address = address.iter().map(|x| x.to_string()).collect();
self
}
diff --git a/tun/src/unix/apple/kern_control.rs b/tun/src/unix/apple/kern_control.rs
index 76e576f..6075233 100644
--- a/tun/src/unix/apple/kern_control.rs
+++ b/tun/src/unix/apple/kern_control.rs
@@ -21,7 +21,7 @@ impl SysControlSocket for socket2::Socket {
unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? };
let (_, addr) = unsafe {
- socket2::SockAddr::init(|addr_storage, len| {
+ socket2::SockAddr::try_init(|addr_storage, len| {
*len = size_of::() as u32;
let addr: &mut sys::sockaddr_ctl = &mut *addr_storage.cast();
diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs
index 2787cde..6e859ca 100644
--- a/tun/src/unix/apple/mod.rs
+++ b/tun/src/unix/apple/mod.rs
@@ -1,13 +1,11 @@
-use std::{
- io::{Error, IoSlice},
- mem,
- net::{Ipv4Addr, SocketAddrV4},
- os::fd::{AsRawFd, FromRawFd, RawFd},
-};
+use std::{io::{Error, IoSlice}, mem, net::{Ipv4Addr, SocketAddrV4}, os::fd::{AsRawFd, FromRawFd, RawFd}, ptr};
+use std::net::{IpAddr, Ipv6Addr, SocketAddrV6};
+use std::ptr::addr_of;
use byteorder::{ByteOrder, NetworkEndian};
use fehler::throws;
-use libc::{c_char, iovec, writev, AF_INET, AF_INET6};
+use libc::{c_char, iovec, writev, AF_INET, AF_INET6, sockaddr_in6};
+use nix::sys::socket::SockaddrIn6;
use socket2::{Domain, SockAddr, Socket, Type};
use tracing::{self, instrument};
@@ -49,7 +47,7 @@ impl TunInterface {
pub fn retrieve() -> Option {
(3..100)
.filter_map(|fd| unsafe {
- let peer_addr = socket2::SockAddr::init(|storage, len| {
+ let peer_addr = socket2::SockAddr::try_init(|storage, len| {
*len = mem::size_of::() as u32;
libc::getpeername(fd, storage as *mut _, len);
Ok(())
@@ -71,9 +69,12 @@ impl TunInterface {
#[throws]
fn configure(&self, options: TunOptions) {
- if let Some(addr) = options.address {
- if let Ok(addr) = addr.parse() {
- self.set_ipv4_addr(addr)?;
+ for addr in options.address{
+ if let Ok(addr) = addr.parse::() {
+ match addr {
+ IpAddr::V4(addr) => {self.set_ipv4_addr(addr)?}
+ IpAddr::V6(addr) => {self.set_ipv6_addr(addr)?}
+ }
}
}
}
@@ -117,6 +118,14 @@ impl TunInterface {
iff
}
+ #[throws]
+ #[instrument]
+ fn in6_ifreq(&self) -> sys::in6_ifreq {
+ let mut iff: sys::in6_ifreq = unsafe { mem::zeroed() };
+ iff.ifr_name = string_to_ifname(&self.name()?);
+ iff
+ }
+
#[throws]
#[instrument]
pub fn set_ipv4_addr(&self, addr: Ipv4Addr) {
@@ -136,6 +145,21 @@ impl TunInterface {
Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr))
}
+ #[throws]
+ pub fn set_ipv6_addr(&self, addr: Ipv6Addr) {
+ // let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0));
+ // println!("addr: {:?}", addr);
+ // let mut iff = self.in6_ifreq()?;
+ // let sto = addr.as_storage();
+ // let ifadddr_ptr: *const sockaddr_in6 = addr_of!(sto).cast();
+ // iff.ifr_ifru.ifru_addr = unsafe { *ifadddr_ptr };
+ // println!("ifru addr set");
+ // println!("{:?}", sys::SIOCSIFADDR_IN6);
+ // self.perform6(|fd| unsafe { sys::if_set_addr6(fd, &iff) })?;
+ // tracing::info!("ipv6_addr_set");
+ tracing::warn!("Setting IPV6 address on MacOS CLI mode is not supported yet.");
+ }
+
#[throws]
fn perform(&self, perform: impl FnOnce(RawFd) -> Result) -> R {
let span = tracing::info_span!("perform", fd = self.as_raw_fd());
@@ -145,6 +169,15 @@ impl TunInterface {
perform(socket.as_raw_fd())?
}
+ #[throws]
+ fn perform6(&self, perform: impl FnOnce(RawFd) -> Result) -> R {
+ let span = tracing::info_span!("perform6", fd = self.as_raw_fd());
+ let _enter = span.enter();
+
+ let socket = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
+ perform(socket.as_raw_fd())?
+ }
+
#[throws]
#[instrument]
pub fn mtu(&self) -> i32 {
diff --git a/tun/src/unix/apple/sys.rs b/tun/src/unix/apple/sys.rs
index b4d4a6a..d48d6ee 100644
--- a/tun/src/unix/apple/sys.rs
+++ b/tun/src/unix/apple/sys.rs
@@ -1,6 +1,6 @@
use std::mem;
-use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr};
+use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr, sockaddr_in6, time_t};
pub use libc::{
c_void,
sockaddr_ctl,
@@ -23,6 +23,7 @@ pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
pub const UTUN_OPT_IFNAME: libc::c_int = 2;
pub const MAX_KCTL_NAME: usize = 96;
+pub const SCOPE6_ID_MAX: usize = 16;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
@@ -74,7 +75,107 @@ pub struct ifreq {
pub ifr_ifru: ifr_ifru,
}
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct in6_addrlifetime{
+ pub ia6t_expire: time_t,
+ pub ia6t_preferred: time_t,
+ pub ia6t_vltime: u32,
+ pub ia6t_pltime: u32,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct in6_ifstat {
+ pub ifs6_in_receive: u64,
+ pub ifs6_in_hdrerr: u64,
+ pub ifs6_in_toobig: u64,
+ pub ifs6_in_noroute: u64,
+ pub ifs6_in_addrerr: u64,
+ pub ifs6_in_protounknown: u64,
+ pub ifs6_in_truncated: u64,
+ pub ifs6_in_discard: u64,
+ pub ifs6_in_deliver: u64,
+ pub ifs6_out_forward: u64,
+ pub ifs6_out_request: u64,
+ pub ifs6_out_discard: u64,
+ pub ifs6_out_fragok: u64,
+ pub ifs6_out_fragfail: u64,
+ pub ifs6_out_fragcreat: u64,
+ pub ifs6_reass_reqd: u64,
+ pub ifs6_reass_ok: u64,
+ pub ifs6_atmfrag_rcvd: u64,
+ pub ifs6_reass_fail: u64,
+ pub ifs6_in_mcast: u64,
+ pub ifs6_out_mcast: u64,
+ pub ifs6_cantfoward_icmp6: u64,
+ pub ifs6_addr_expiry_cnt: u64,
+ pub ifs6_pfx_expiry_cnt: u64,
+ pub ifs6_defrtr_expiry_cnt: u64,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct icmp6_ifstat {
+ pub ifs6_in_msg: u64,
+ pub ifs6_in_error: u64,
+ pub ifs6_in_dstunreach: u64,
+ pub ifs6_in_adminprohib: u64,
+ pub ifs6_in_timeexceed: u64,
+ pub ifs6_in_paramprob: u64,
+ pub ifs6_in_pkttoobig: u64,
+ pub ifs6_in_echo: u64,
+ pub ifs6_in_echoreply: u64,
+ pub ifs6_in_routersolicit: u64,
+ pub ifs6_in_routeradvert: u64,
+ pub ifs6_in_neighborsolicit: u64,
+ pub ifs6_in_neighboradvert: u64,
+ pub ifs6_in_redirect: u64,
+ pub ifs6_in_mldquery: u64,
+ pub ifs6_in_mldreport: u64,
+ pub ifs6_in_mlddone: u64,
+ pub ifs6_out_msg: u64,
+ pub ifs6_out_error: u64,
+ pub ifs6_out_dstunreach: u64,
+ pub ifs6_out_adminprohib: u64,
+ pub ifs6_out_timeexceed: u64,
+ pub ifs6_out_paramprob: u64,
+ pub ifs6_out_pkttoobig: u64,
+ pub ifs6_out_echo: u64,
+ pub ifs6_out_echoreply: u64,
+ pub ifs6_out_routersolicit: u64,
+ pub ifs6_out_routeradvert: u64,
+ pub ifs6_out_neighborsolicit: u64,
+ pub ifs6_out_neighboradvert: u64,
+ pub ifs6_out_redirect: u64,
+ pub ifs6_out_mldquery: u64,
+ pub ifs6_out_mldreport: u64,
+ pub ifs6_out_mlddone: u64,
+}
+
+#[repr(C)]
+pub union ifr_ifru6 {
+ pub ifru_addr: sockaddr_in6,
+ pub ifru_dstaddr: sockaddr_in6,
+ pub ifru_flags: c_int,
+ pub ifru_flags6: c_int,
+ pub ifru_metric: c_int,
+ pub ifru_intval: c_int,
+ pub ifru_data: *mut c_char,
+ pub ifru_lifetime: in6_addrlifetime, // ifru_lifetime
+ pub ifru_stat: in6_ifstat,
+ pub ifru_icmp6stat: icmp6_ifstat,
+ pub ifru_scope_id: [u32; SCOPE6_ID_MAX]
+}
+
+#[repr(C)]
+pub struct in6_ifreq {
+ pub ifr_name: [c_char; IFNAMSIZ],
+ pub ifr_ifru: ifr_ifru6,
+}
+
pub const SIOCSIFADDR: c_ulong = request_code_write!(b'i', 12, mem::size_of::());
+pub const SIOCSIFADDR_IN6: c_ulong = request_code_write!(b'i', 12, mem::size_of::());
pub const SIOCGIFMTU: c_ulong = request_code_readwrite!(b'i', 51, mem::size_of::());
pub const SIOCSIFMTU: c_ulong = request_code_write!(b'i', 52, mem::size_of::());
pub const SIOCGIFNETMASK: c_ulong = request_code_readwrite!(b'i', 37, mem::size_of::());
@@ -97,5 +198,6 @@ ioctl_read_bad!(if_get_addr, libc::SIOCGIFADDR, ifreq);
ioctl_read_bad!(if_get_mtu, SIOCGIFMTU, ifreq);
ioctl_read_bad!(if_get_netmask, SIOCGIFNETMASK, ifreq);
ioctl_write_ptr_bad!(if_set_addr, SIOCSIFADDR, ifreq);
+ioctl_write_ptr_bad!(if_set_addr6, SIOCSIFADDR_IN6, in6_ifreq);
ioctl_write_ptr_bad!(if_set_mtu, SIOCSIFMTU, ifreq);
ioctl_write_ptr_bad!(if_set_netmask, SIOCSIFNETMASK, ifreq);
diff --git a/tun/tests/packets.rs b/tun/tests/packets.rs
index 28090a2..80c078b 100644
--- a/tun/tests/packets.rs
+++ b/tun/tests/packets.rs
@@ -1,4 +1,5 @@
use std::{io::Error, net::Ipv4Addr};
+use std::net::Ipv6Addr;
use fehler::throws;
use tun::TunInterface;
@@ -33,3 +34,15 @@ fn write_packets() {
let bytes_written = tun.send(&buf)?;
assert_eq!(bytes_written, 1504);
}
+
+#[test]
+#[throws]
+#[ignore = "requires interactivity"]
+#[cfg(not(target_os = "windows"))]
+fn set_ipv6() {
+ let tun = TunInterface::new()?;
+ println!("tun name: {:?}", tun.name()?);
+ let targ_addr: Ipv6Addr = "::1".parse().unwrap();
+ println!("v6 addr: {:?}", targ_addr);
+ tun.set_ipv6_addr(targ_addr)?;
+}
\ No newline at end of file