diff --git a/.codespellrc b/.codespellrc index 5cad30ba2..be8de04df 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] skip = ./website/.next,./website/pnpm-lock.yaml,./rust/target,Cargo.lock,./website/docs/reference/api/*.mdx,./erl_crash.dump,./apps/*/erl_crash.dump,./cover,./vendor,*.json,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./_build -ignore-words-list = crate,keypair,keypairs,iif,statics,wee,anull,commitish,inout +ignore-words-list = optin,crate,keypair,keypairs,iif,statics,wee,anull,commitish,inout diff --git a/kotlin/android/app/build.gradle b/kotlin/android/app/build.gradle index 8873afa3f..d30247d7e 100644 --- a/kotlin/android/app/build.gradle +++ b/kotlin/android/app/build.gradle @@ -114,14 +114,11 @@ dependencies { // Moshi implementation "com.squareup.moshi:moshi-kotlin:1.12.0" + implementation 'com.squareup.moshi:moshi:1.12.0' // Gson implementation "com.google.code.gson:gson:2.9.0" - // Deep Link - implementation "com.airbnb:deeplinkdispatch:6.1.0" - kapt "com.airbnb:deeplinkdispatch-processor:6.1.0" - // Security implementation "androidx.security:security-crypto:1.1.0-alpha05" diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/di/AppModule.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/di/AppModule.kt index fe443bba6..e7e308425 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/di/AppModule.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/di/AppModule.kt @@ -6,13 +6,15 @@ import android.content.SharedPreferences import android.content.res.Resources import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey -import dev.firezone.android.features.session.backend.SessionManager +import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dev.firezone.android.core.domain.preference.GetConfigUseCase import dev.firezone.android.core.domain.preference.SaveIsConnectedUseCase +import dev.firezone.android.tunnel.TunnelManager internal const val ENCRYPTED_SHARED_PREFERENCES = "encryptedSharedPreferences" @@ -39,8 +41,10 @@ object AppModule { ) @Provides - internal fun provideSessionManager( + internal fun provideTunnelManager( + @ApplicationContext appContext: Context, getConfigUseCase: GetConfigUseCase, saveIsConnectedUseCase: SaveIsConnectedUseCase, - ): SessionManager = SessionManager(getConfigUseCase, saveIsConnectedUseCase) + moshi: Moshi, + ): TunnelManager = TunnelManager(appContext, getConfigUseCase, saveIsConnectedUseCase, moshi) } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt index 11de81793..49f328720 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt @@ -7,15 +7,9 @@ import android.util.Log import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import dagger.hilt.android.AndroidEntryPoint -import dev.firezone.android.BuildConfig import dev.firezone.android.R import dev.firezone.android.core.presentation.MainActivity import dev.firezone.android.databinding.ActivityAppLinkHandlerBinding -import dev.firezone.android.features.session.backend.SessionManager -import dev.firezone.android.features.splash.ui.SplashFragmentDirections -import dev.firezone.android.tunnel.TunnelManager -import dev.firezone.android.tunnel.TunnelSession -import javax.inject.Inject @AndroidEntryPoint class AppLinkHandlerActivity : AppCompatActivity(R.layout.activity_app_link_handler) { diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt index 4f0739810..26d76f195 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt @@ -7,13 +7,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import dev.firezone.android.BuildConfig import dev.firezone.android.core.domain.preference.SaveTokenUseCase import dev.firezone.android.core.domain.preference.ValidateCsrfTokenUseCase -import dev.firezone.android.features.session.backend.SessionManager -import dev.firezone.android.tunnel.TunnelLogger -import dev.firezone.android.tunnel.TunnelManager -import dev.firezone.android.tunnel.TunnelSession import kotlinx.coroutines.flow.collect import javax.inject.Inject import kotlinx.coroutines.flow.firstOrNull @@ -24,7 +19,6 @@ internal class AppLinkViewModel @Inject constructor( private val validateCsrfTokenUseCase: ValidateCsrfTokenUseCase, private val saveTokenUseCase: SaveTokenUseCase, ) : ViewModel() { - private val callback: TunnelManager = TunnelManager() private val actionMutableLiveData = MutableLiveData() val actionLiveData: LiveData = actionMutableLiveData diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/backend/SessionManager.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/backend/SessionManager.kt deleted file mode 100644 index 47e46fced..000000000 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/backend/SessionManager.kt +++ /dev/null @@ -1,70 +0,0 @@ -package dev.firezone.android.features.session.backend - -import android.net.VpnService -import android.util.Log -import android.provider.Settings -import dev.firezone.android.BuildConfig -import dev.firezone.android.core.domain.preference.GetConfigUseCase -import dev.firezone.android.core.domain.preference.SaveIsConnectedUseCase -import dev.firezone.android.tunnel.TunnelCallbacks -import dev.firezone.android.tunnel.TunnelLogger -import dev.firezone.android.tunnel.TunnelSession -import dev.firezone.android.tunnel.TunnelManager -import dev.firezone.android.tunnel.TunnelService -import javax.inject.Inject - -internal class SessionManager @Inject constructor( - private val getConfigUseCase: GetConfigUseCase, - private val saveIsConnectedUseCase: SaveIsConnectedUseCase, -) { - private val callback: TunnelManager = TunnelManager() - - fun connect() { - try { - val config = getConfigUseCase.sync() - - Log.d("Connlib", "accountId: ${config.accountId}") - Log.d("Connlib", "token: ${config.token}") - - if (config.accountId != null && config.token != null) { - Log.d("Connlib", "Attempting to establish TunnelSession...") - sessionPtr = TunnelSession.connect( - BuildConfig.CONTROL_PLANE_URL, - config.token, - Settings.Secure.ANDROID_ID, - TunnelCallbacks() - ) - Log.d("Connlib", "connlib session started! sessionPtr: $sessionPtr") - setConnectionStatus(true) - } - } catch (exception: Exception) { - Log.e("Connection error:", exception.message.toString()) - } - } - - fun disconnect() { - try { - TunnelSession.disconnect(sessionPtr!!) - setConnectionStatus(false) - } catch (exception: Exception) { - Log.e("Disconnection error:", exception.message.toString()) - } - } - - private fun setConnectionStatus(value: Boolean) { - saveIsConnectedUseCase.sync(value) - } - - - - internal companion object { - var sessionPtr: Long? = null - init { - Log.d("Connlib","Attempting to load library from main app...") - System.loadLibrary("connlib") - Log.d("Connlib","Library loaded from main app!") - TunnelLogger.init() - Log.d("Connlib","Connlib Logger initialized!") - } - } -} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/ResourcesAdapter.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/ResourcesAdapter.kt new file mode 100644 index 000000000..5822d483e --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/ResourcesAdapter.kt @@ -0,0 +1,58 @@ +package dev.firezone.android.features.session.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import dev.firezone.android.databinding.ListItemResourceBinding +import dev.firezone.android.tunnel.model.Resource +import javax.annotation.Nullable + +internal class ResourcesAdapter: RecyclerView.Adapter() { + + private val resources: MutableList = mutableListOf() + + fun updateResources(updatedResources: List) { + val diffCallback = ResourcesCallback(resources, updatedResources) + val diffCourses = DiffUtil.calculateDiff(diffCallback) + resources.clear() + resources.addAll(updatedResources) + diffCourses.dispatchUpdatesTo(this) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + ListItemResourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun getItemCount(): Int = resources.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(resources[position]) + } + + class ViewHolder(private val binding: ListItemResourceBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(resource: Resource) { + binding.resourceNameText.text = resource.name + binding.typeChip.text = resource.type + binding.addressText.text = resource.address + } + } +} + +class ResourcesCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { + override fun getOldListSize(): Int = oldList.size + + override fun getNewListSize(): Int = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] === newList[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val (type1, id1, address1, name1) = oldList[oldItemPosition] + val (type2, id2, address2, name2) = newList[newItemPosition] + return type1 == type2 && id1 == id2 && address1 == address2 && name1 == name2 + } +} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionFragment.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionFragment.kt index 7593d6dce..fa1c57482 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionFragment.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionFragment.kt @@ -1,32 +1,55 @@ package dev.firezone.android.features.session.ui import android.os.Bundle -import android.view.View import android.util.Log +import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import dev.firezone.android.R import dev.firezone.android.databinding.FragmentSessionBinding -import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint internal class SessionFragment : Fragment(R.layout.fragment_session) { private lateinit var binding: FragmentSessionBinding private val viewModel: SessionViewModel by viewModels() + private val resourcesAdapter: ResourcesAdapter = ResourcesAdapter() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentSessionBinding.bind(view) - setupButtonListeners() - setupActionObservers() + setupViews() + setupObservers() Log.d("SessionFragment", "Starting session...") viewModel.startSession() } - private fun setupActionObservers() { + private fun setupViews() { + binding.btSignOut.setOnClickListener { + viewModel.onDisconnect() + } + + val layoutManager = LinearLayoutManager(requireContext()) + val dividerItemDecoration = DividerItemDecoration( + requireContext(), + layoutManager.orientation + ) + binding.resourcesList.addItemDecoration(dividerItemDecoration) + binding.resourcesList.adapter = resourcesAdapter + binding.resourcesList.layoutManager = layoutManager + } + + private fun setupObservers() { viewModel.actionLiveData.observe(viewLifecycleOwner) { action -> when (action) { SessionViewModel.ViewAction.NavigateToSignInFragment -> @@ -36,11 +59,15 @@ internal class SessionFragment : Fragment(R.layout.fragment_session) { SessionViewModel.ViewAction.ShowError -> showError() } } - } - private fun setupButtonListeners() { - binding.btSignOut.setOnClickListener { - viewModel.onDisconnect() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { uiState -> + uiState.resources?.let { + resourcesAdapter.updateResources(it) + } + } + } } } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionViewModel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionViewModel.kt index b3a6686c8..32621972b 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionViewModel.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/session/ui/SessionViewModel.kt @@ -1,31 +1,92 @@ package dev.firezone.android.features.session.ui -import dev.firezone.android.features.session.backend.SessionManager +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import dev.firezone.android.tunnel.callback.TunnelListener +import dev.firezone.android.tunnel.TunnelManager +import dev.firezone.android.tunnel.model.Resource +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject import kotlinx.coroutines.launch @HiltViewModel internal class SessionViewModel @Inject constructor( - private val sessionManager: SessionManager + private val tunnelManager: TunnelManager, ) : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow = _uiState + private val actionMutableLiveData = MutableLiveData() val actionLiveData: LiveData = actionMutableLiveData + private val tunnelListener = object: TunnelListener { + override fun onSetInterfaceConfig( + tunnelAddressIPv4: String, + tunnelAddressIPv6: String, + dnsAddress: String, + dnsFallbackStrategy: String + ) { + //TODO("Not yet implemented") + } + + override fun onTunnelReady(): Boolean { + //TODO("Not yet implemented") + return true + } + + override fun onAddRoute(cidrAddress: String) { + //TODO("Not yet implemented") + } + + override fun onRemoveRoute(cidrAddress: String) { + //TODO("Not yet implemented") + } + + override fun onUpdateResources(resources: List) { + //TODO("Not yet implemented") + Log.d("TunnelManager", "onUpdateResources: $resources") + _uiState.value = _uiState.value.copy ( + resources = resources + ) + } + + override fun onDisconnect(error: String?): Boolean { + //TODO("Not yet implemented") + return true + } + + override fun onError(error: String): Boolean { + //TODO("Not yet implemented") + return true + } + } + fun startSession() { viewModelScope.launch { - sessionManager.connect() + tunnelManager.addListener(tunnelListener) + tunnelManager.connect() } } + override fun onCleared() { + super.onCleared() + + tunnelManager.removeListener(tunnelListener) + } + fun onDisconnect() { actionMutableLiveData.postValue(ViewAction.NavigateToSignInFragment) } + internal data class UiState( + val resources: List? = null, + ) + internal sealed class ViewAction { object NavigateToSignInFragment : ViewAction() diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/Tunnel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/Tunnel.kt deleted file mode 100644 index 1fb3d137c..000000000 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/Tunnel.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.firezone.android.tunnel - -class Tunnel( - val config: TunnelConfig, - var state: State = State.Down -) { - - sealed interface State { - object Up: State - object Down: State - } -} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelCallbacks.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelCallbacks.kt deleted file mode 100644 index 31bf3c341..000000000 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelCallbacks.kt +++ /dev/null @@ -1,66 +0,0 @@ -package dev.firezone.android.tunnel - -import android.net.VpnService -import android.util.Log - -class TunnelCallbacks { - fun onUpdateResources(resourceListJSON: String) { - // TODO: Call into client app to update resources list and routing table - Log.d(TunnelCallbacks.TAG, "onUpdateResources: $resourceListJSON") - } - - fun onSetInterfaceConfig( - tunnelAddressIPv4: String, - tunnelAddressIPv6: String, - dnsAddress: String, - dnsFallbackStrategy: String, - ): Int { - Log.d(TunnelCallbacks.TAG, "onSetInterfaceConfig: [IPv4:$tunnelAddressIPv4] [IPv6:$tunnelAddressIPv6] [dns:$dnsAddress] [dnsFallbackStrategy:$dnsFallbackStrategy]") - return buildVpnService(tunnelAddressIPv4, tunnelAddressIPv6).establish()?.detachFd() ?: -1 - } - - fun onTunnelReady(): Boolean { - Log.d(TunnelCallbacks.TAG, "onTunnelReady") - - return true - } - - fun onError(error: String): Boolean { - Log.d(TunnelCallbacks.TAG, "onError: $error") - - return true - } - - fun onAddRoute(cidrAddress: String) { - Log.d(TunnelCallbacks.TAG, "onAddRoute: $cidrAddress") - } - - fun onRemoveRoute(cidrAddress: String) { - Log.d(TunnelCallbacks.TAG, "onRemoveRoute: $cidrAddress") - } - - fun onDisconnect(error: String?): Boolean { - Log.d(TunnelCallbacks.TAG, "onDisconnect $error") - - return true - } - private fun buildVpnService(ipv4Address: String, ipv6Address: String): VpnService.Builder = - TunnelService().Builder().apply { - addAddress(ipv4Address, 32) - addAddress(ipv6Address, 128) - - // TODO: These are the staging Resources. Remove these in favor of the onUpdateResources callback. - addRoute("172.31.93.123", 32) - addRoute("172.31.83.10", 32) - addRoute("172.31.82.179", 32) - - setSession("Firezone VPN") - - // TODO: Can we do better? - setMtu(1280) - } - - companion object { - private const val TAG = "TunnelCallbacks" - } -} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelManager.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelManager.kt index d6946fda7..8420e74bd 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelManager.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelManager.kt @@ -1,20 +1,45 @@ package dev.firezone.android.tunnel +import android.content.Context +import android.content.Intent +import android.net.VpnService +import android.provider.Settings import android.util.Log +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import dev.firezone.android.BuildConfig +import dev.firezone.android.core.domain.preference.GetConfigUseCase +import dev.firezone.android.core.domain.preference.SaveIsConnectedUseCase +import dev.firezone.android.tunnel.callback.ConnlibCallback +import dev.firezone.android.tunnel.model.Resource +import dev.firezone.android.tunnel.callback.TunnelListener +import dev.firezone.android.tunnel.model.Tunnel +import dev.firezone.android.tunnel.model.TunnelConfig import java.lang.ref.WeakReference +import javax.inject.Inject +import javax.inject.Singleton -class TunnelManager { +@Singleton +@OptIn(ExperimentalStdlibApi::class) +internal class TunnelManager @Inject constructor( + private val appContext: Context, + private val getConfigUseCase: GetConfigUseCase, + private val saveIsConnectedUseCase: SaveIsConnectedUseCase, + private val moshi: Moshi, +) { private var activeTunnel: Tunnel? = null private val listeners: MutableSet> = mutableSetOf() - private val callback: TunnelListener = object: TunnelListener { + private val callback: ConnlibCallback = object: ConnlibCallback { override fun onUpdateResources(resourceListJSON: String) { // TODO: Call into client app to update resources list and routing table Log.d(TAG, "onUpdateResources: $resourceListJSON") - listeners.onEach { - it.get()?.onUpdateResources(resourceListJSON) + moshi.adapter>().fromJson(resourceListJSON)?.let { resources -> + listeners.onEach { + it.get()?.onUpdateResources(resources) + } } } @@ -23,12 +48,20 @@ class TunnelManager { tunnelAddressIPv6: String, dnsAddress: String, dnsFallbackStrategy: String - ) { + ): Int { Log.d(TAG, "onSetInterfaceConfig: [IPv4:$tunnelAddressIPv4] [IPv6:$tunnelAddressIPv6] [dns:$dnsAddress] [dnsFallbackStrategy:$dnsFallbackStrategy]") + val tunnel = Tunnel( + config = TunnelConfig( + tunnelAddressIPv4, tunnelAddressIPv6, dnsAddress, dnsFallbackStrategy + ) + ) + listeners.onEach { it.get()?.onSetInterfaceConfig(tunnelAddressIPv4, tunnelAddressIPv6, dnsAddress, dnsFallbackStrategy) } + + return buildVpnService(tunnelAddressIPv4, tunnelAddressIPv6).establish()?.detachFd() ?: -1 } override fun onTunnelReady(): Boolean { @@ -94,7 +127,81 @@ class TunnelManager { } } - companion object { + fun startVPN() { + val intent = Intent(appContext, TunnelService::class.java) + intent.action = TunnelService.ACTION_CONNECT + appContext.startService(intent) + } + + fun stopVPN() { + val intent = Intent(appContext, TunnelService::class.java) + intent.action = TunnelService.ACTION_DISCONNECT + appContext.startService(intent) + } + + fun connect() { + try { + val config = getConfigUseCase.sync() + + Log.d("Connlib", "accountId: ${config.accountId}") + Log.d("Connlib", "token: ${config.token}") + + if (config.accountId != null && config.token != null) { + Log.d("Connlib", "Attempting to establish TunnelSession...") + sessionPtr = TunnelSession.connect( + controlPlaneUrl = BuildConfig.CONTROL_PLANE_URL, + token = config.token, + externalId = Settings.Secure.ANDROID_ID, + callback = callback + ) + Log.d("Connlib", "connlib session started! sessionPtr: ${sessionPtr}") + setConnectionStatus(true) + } + } catch (exception: Exception) { + Log.e("Connection error:", exception.message.toString()) + } + } + + fun disconnect() { + try { + TunnelSession.disconnect(sessionPtr!!) + setConnectionStatus(false) + } catch (exception: Exception) { + Log.e("Disconnection error:", exception.message.toString()) + } + } + + private fun setConnectionStatus(value: Boolean) { + saveIsConnectedUseCase.sync(value) + } + + private fun buildVpnService(ipv4Address: String, ipv6Address: String): VpnService.Builder = + TunnelService().Builder().apply { + addAddress(ipv4Address, 32) + addAddress(ipv6Address, 128) + + // TODO: These are the staging Resources. Remove these in favor of the onUpdateResources callback. + addRoute("172.31.93.123", 32) + addRoute("172.31.83.10", 32) + addRoute("172.31.82.179", 32) + + setSession("Firezone VPN") + + // TODO: Can we do better? + setMtu(1280) + } + + internal companion object { + var sessionPtr: Long? = null + private const val TAG: String = "TunnelManager" + + init { + Log.d("Connlib","Attempting to load library from main app...") + System.loadLibrary("connlib") + Log.d("Connlib","Library loaded from main app!") + TunnelLogger.init() + Log.d("Connlib","Connlib Logger initialized!") + } } } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt index fcc3ff42f..ed323f841 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt @@ -20,4 +20,8 @@ class TunnelService: VpnService() { } + companion object { + val ACTION_CONNECT = "dev.firezone.android.tunnel.CONNECT" + val ACTION_DISCONNECT = "dev.firezone.android.tunnel.DISCONNECT" + } } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelListener.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt similarity index 82% rename from kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelListener.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt index 43db6e4e5..ecc2b6daa 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelListener.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt @@ -1,8 +1,7 @@ -package dev.firezone.android.tunnel +package dev.firezone.android.tunnel.callback -interface TunnelListener { - - fun onSetInterfaceConfig(tunnelAddressIPv4: String, tunnelAddressIPv6: String, dnsAddress: String, dnsFallbackStrategy: String) +interface ConnlibCallback { + fun onSetInterfaceConfig(tunnelAddressIPv4: String, tunnelAddressIPv6: String, dnsAddress: String, dnsFallbackStrategy: String): Int fun onTunnelReady(): Boolean diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/TunnelListener.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/TunnelListener.kt new file mode 100644 index 000000000..54d3e305d --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/TunnelListener.kt @@ -0,0 +1,20 @@ +package dev.firezone.android.tunnel.callback + +import dev.firezone.android.tunnel.model.Resource + +interface TunnelListener { + + fun onSetInterfaceConfig(tunnelAddressIPv4: String, tunnelAddressIPv6: String, dnsAddress: String, dnsFallbackStrategy: String) + + fun onTunnelReady(): Boolean + + fun onAddRoute(cidrAddress: String) + + fun onRemoveRoute(cidrAddress: String) + + fun onUpdateResources(resources: List) + + fun onDisconnect(error: String?): Boolean + + fun onError(error: String): Boolean +} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Resource.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Resource.kt new file mode 100644 index 000000000..d6aa2a88f --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Resource.kt @@ -0,0 +1,11 @@ +package dev.firezone.android.tunnel.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class Resource( + val type: String, + val id: String, + val address: String, + val name: String, +) diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Tunnel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Tunnel.kt new file mode 100644 index 000000000..1b39f791a --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/Tunnel.kt @@ -0,0 +1,15 @@ +package dev.firezone.android.tunnel.model + +data class Tunnel( + val config: TunnelConfig, + var state: State = State.Down, + val routes: MutableList = mutableListOf(), + val resources: MutableList = mutableListOf(), +) { + + sealed interface State { + object Up: State + object CONNECTING: State + object Down: State + } +} diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelConfig.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/TunnelConfig.kt similarity index 79% rename from kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelConfig.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/TunnelConfig.kt index 076e360ab..54ed8e1de 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelConfig.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/model/TunnelConfig.kt @@ -1,4 +1,4 @@ -package dev.firezone.android.tunnel +package dev.firezone.android.tunnel.model data class TunnelConfig ( val tunnelAddressIPv4: String, diff --git a/kotlin/android/app/src/main/res/layout/fragment_session.xml b/kotlin/android/app/src/main/res/layout/fragment_session.xml index 54502df6e..ec1b4cf06 100644 --- a/kotlin/android/app/src/main/res/layout/fragment_session.xml +++ b/kotlin/android/app/src/main/res/layout/fragment_session.xml @@ -35,16 +35,20 @@ + + + + + + + + + + + diff --git a/kotlin/android/app/src/main/res/values/strings.xml b/kotlin/android/app/src/main/res/values/strings.xml index 77f4055ef..bf34c443a 100644 --- a/kotlin/android/app/src/main/res/values/strings.xml +++ b/kotlin/android/app/src/main/res/values/strings.xml @@ -12,7 +12,7 @@ Signed Out - Authenticated + Resources Sign Out diff --git a/kotlin/android/app/src/main/res/values/styles.xml b/kotlin/android/app/src/main/res/values/styles.xml index 82e702518..1ab520637 100644 --- a/kotlin/android/app/src/main/res/values/styles.xml +++ b/kotlin/android/app/src/main/res/values/styles.xml @@ -26,4 +26,10 @@ @color/black_firezone + +