diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2023-03-11 23:10:19 +0100 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2023-03-23 22:55:18 +0100 |
commit | c999793a8ba411f32d465bced2815f74aa40b409 (patch) | |
tree | 025a85b9b5d3415f6c64c29e631af333f937110e | |
parent | 378edd57a126c54bf3837d0c5087be29cc5d6788 (diff) |
WIP: add bgp-java
Add bgp-java.
Update peer list based on TunnelEncapsAttribute in BGP.
11 files changed, 444 insertions, 25 deletions
diff --git a/settings.gradle b/settings.gradle index e6dd51a5..cde735b8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,4 +25,5 @@ dependencyResolutionManagement { rootProject.name = "wireguard-android" include ':tunnel' +include ':bgp-java' include ':ui' diff --git a/tunnel/build.gradle b/tunnel/build.gradle index d365e3df..9bb120fb 100644 --- a/tunnel/build.gradle +++ b/tunnel/build.gradle @@ -57,6 +57,7 @@ android { } dependencies { + implementation project(":bgp-java") implementation "androidx.annotation:annotation:$annotationsVersion" implementation "androidx.collection:collection:$collectionVersion" implementation "io.grpc:grpc-okhttp:$grpcVersion" diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java b/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java new file mode 100644 index 00000000..12668621 --- /dev/null +++ b/tunnel/src/main/java/com/wireguard/android/backend/Bgp.java @@ -0,0 +1,281 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.backend; + +import android.net.TrafficStats; +import android.util.Log; + +import com.lumaserv.bgp.BGPListener; +import com.lumaserv.bgp.BGPServer; +import com.lumaserv.bgp.BGPSession; +import com.lumaserv.bgp.BGPSessionConfiguration; +import com.lumaserv.bgp.protocol.AFI; +import com.lumaserv.bgp.protocol.BGPPacket; +import com.lumaserv.bgp.protocol.IPPrefix; +import com.lumaserv.bgp.protocol.SAFI; +import com.lumaserv.bgp.protocol.attribute.ASPathAttribute; +import com.lumaserv.bgp.protocol.attribute.MPReachableNLRIAttribute; +import com.lumaserv.bgp.protocol.attribute.NextHopAttribute; +import com.lumaserv.bgp.protocol.attribute.OriginAttribute; +import com.lumaserv.bgp.protocol.attribute.PathAttribute; +import com.lumaserv.bgp.protocol.attribute.TunnelEncapsAttribute; +import com.lumaserv.bgp.protocol.message.BGPUpdate; + +import com.wireguard.config.InetEndpoint; +import com.wireguard.config.InetNetwork; +import com.wireguard.crypto.Key; +import com.wireguard.crypto.KeyFormatException; + +import io.grpc.ManagedChannel; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import javax.net.SocketFactory; + +public class Bgp implements BGPListener { + private static final String TAG = "WireGuard/Bgp"; + private static final String SESSION = "demosession"; + private static final int MY_ASN = (int)4200000201L; + private static final int REMOTE_ASN = (int)4200000010L; + private static final String REMOTE_ADDR = "10.49.32.1"; + private static final String REMOTE_ID = "10.49.160.1"; + private static final String LOCAL_ID = "10.49.33.218"; + private static final int PORT = 0; + private static final int STATS_TAG = 1; // FIXME + + private ManagedChannel channel; + private Tunnel tunnel; + private int tunnelHandle; + private BGPServer server; + + public Bgp(ManagedChannel channel, Tunnel tunnel, int tunnelHandle) { + this.channel = channel; + this.tunnel = tunnel; + this.tunnelHandle = tunnelHandle; + } + + @Override + public void onOpen(BGPSession session) { + // DO WHAT YOU WANT + Log.i(TAG, "onOpen"); + + // BGPUpdate update; + // { + // List<IPPrefix> prefixes = new ArrayList<>(1); + // prefixes.add(new IPPrefix(new byte[]{10, 49, 124, 105}, 32)); + // List<PathAttribute> attrs = new ArrayList<>(); + // attrs.add(new OriginAttribute(OriginAttribute.Origin.IGP)); + // attrs.add(new NextHopAttribute().setAddress(new byte[]{10, 49, 125, 105})); + // ASPathAttribute.Segment seg = new ASPathAttribute.Segment(); + // seg.setType(ASPathAttribute.Segment.Type.SEQUENCE); + // seg.getAsns().add(MY_ASN); + // ASPathAttribute asPath = new ASPathAttribute(session); + // asPath.getSegments().add(seg); + // attrs.add(asPath); + // update = new BGPUpdate().setAttributes(attrs).setPrefixes(prefixes); + // } + + // BGPUpdate update2; + // try { + // List<PathAttribute> attrs = new ArrayList<>(); + // attrs.add(new OriginAttribute(OriginAttribute.Origin.IGP)); + // ASPathAttribute.Segment seg = new ASPathAttribute.Segment(); + // seg.setType(ASPathAttribute.Segment.Type.SEQUENCE); + // seg.getAsns().add(MY_ASN); + // ASPathAttribute asPath = new ASPathAttribute(session); + // List<IPPrefix> prefixes = new ArrayList<>(1); + // prefixes.add(new IPPrefix(new byte[]{0x20, 0x1, 0x04, 0x70, (byte)0xdf, (byte)0xae, 0x63, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x05}, 128)); + // MPReachableNLRIAttribute mpr = new MPReachableNLRIAttribute(); + // mpr.setAfi(AFI.IPV6).setSafi(SAFI.UNICAST).setNextHop(InetAddress.getByName("2001:470:dfae:6300::1:105")).setNlriPrefixes(prefixes); + // attrs.add(mpr); + // asPath.getSegments().add(seg); + // attrs.add(asPath); + // update2 = new BGPUpdate().setAttributes(attrs); + // } catch (UnknownHostException ex) { + // throw new RuntimeException(ex); + // } + + // try { + // session.sendUpdate(update); + // session.sendUpdate(update2); + // } catch (IOException ex) { + // throw new RuntimeException(ex); + // } + } + + @Override + public void onUpdate(BGPSession session, BGPUpdate update) { + // DO WHAT YOU WANT + Log.i(TAG, "onUpdate: " + update.getPrefixes() + ",-" + update.getWithdrawnPrefixes() + "," + update.getAttributes()); + MPReachableNLRIAttribute mpr = null; + TunnelEncapsAttribute te = null; + + for (PathAttribute attr: update.getAttributes()) { + if (attr instanceof TunnelEncapsAttribute) { + te = (TunnelEncapsAttribute)attr; + } else if (attr instanceof MPReachableNLRIAttribute) { + mpr = (MPReachableNLRIAttribute)attr; + } + } + + if (te == null) { + return; + } + + TunnelEncapsAttribute.WireGuard wg = null; + TunnelEncapsAttribute.Color col = null; + TunnelEncapsAttribute.EgressEndpoint ep = null; + TunnelEncapsAttribute.UDPDestinationPort port = null; + + for (TunnelEncapsAttribute.Tunnel t: te.getTunnels()) { + if (t.getType() != 51820) { + continue; + } + + for (TunnelEncapsAttribute.SubTlv st: t.getSubTlvs()) { + if (st instanceof TunnelEncapsAttribute.WireGuard) { + wg = (TunnelEncapsAttribute.WireGuard)st; + } else if (st instanceof TunnelEncapsAttribute.Color) { + col = (TunnelEncapsAttribute.Color)st; + } else if (st instanceof TunnelEncapsAttribute.EgressEndpoint) { + ep = (TunnelEncapsAttribute.EgressEndpoint)st; + } else if (st instanceof TunnelEncapsAttribute.UDPDestinationPort) { + port = (TunnelEncapsAttribute.UDPDestinationPort)st; + } + } + } + + if (wg == null) { + return; + } + + try { + Key publicKey = Key.fromBytes(wg.getPublicKey()); + InetEndpoint endpoint = null; + + if (ep != null && port != null) { + endpoint = InetEndpoint.fromAddress(ep.getAddress(), port.getPort()); + } + + tunnel.onEndpointChange(publicKey, endpoint); + + List<InetNetwork> addNetworks = new ArrayList<>(); + List<InetNetwork> removeNetworks = new ArrayList<>(); + + for (IPPrefix prefix: update.getPrefixes()) { + try { + addNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength())); + } catch (UnknownHostException ignore) { + } + } + + for (IPPrefix prefix: update.getWithdrawnPrefixes()) { + try { + removeNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength())); + } catch (UnknownHostException ignore) { + } + } + + if (mpr != null && (mpr.getAfi() == AFI.IPV6 || mpr.getAfi() == AFI.IPV4) && mpr.getSafi() == SAFI.UNICAST) { + for (IPPrefix prefix: mpr.getNlriPrefixes()) { + try { + addNetworks.add(new InetNetwork(InetAddress.getByAddress(prefix.getAddress()), prefix.getLength())); + } catch (UnknownHostException ignore) { + } + } + } + + tunnel.onAllowedIpsChange(publicKey, addNetworks, removeNetworks); + + } catch (KeyFormatException ex) { + Log.w(TAG, "Key.fromBytes " + ex); + } + } + + @Override + public void onClose(BGPSession session) { + // NOT YET IMPLEMENTED + Log.i(TAG, "onClose"); + } + + public boolean startServer() { + stopServer(); + try { + SocketFactory factory = new SocketFactory() { + private Socket taggedSocket(Socket sock) throws SocketException { + TrafficStats.tagSocket(sock); + return sock; + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + TrafficStats.setThreadStatsTag(STATS_TAG); + return taggedSocket(new Socket(host, port)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + TrafficStats.setThreadStatsTag(STATS_TAG); + return taggedSocket(new Socket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + TrafficStats.setThreadStatsTag(STATS_TAG); + return taggedSocket(new Socket(address, port, localAddress, localPort)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + TrafficStats.setThreadStatsTag(STATS_TAG); + return taggedSocket(new Socket(host, port, localHost, localPort)); + } + }; + + BGPSessionConfiguration config = + new BGPSessionConfiguration(SESSION, + MY_ASN, + ip(LOCAL_ID), + REMOTE_ASN, + ip(REMOTE_ID), + null, // Remote address + factory, + this); + TrafficStats.setThreadStatsTag(STATS_TAG); + ServerSocket socket = new ServerSocket(PORT); + //TrafficStats.tagSocket(socket); + // Set + server = new BGPServer(socket); + server.getSessionConfigurations().add(config); + server.connect(config, REMOTE_ADDR); + return true; + } catch (IOException ex) { + return false; + } + } + + public void stopServer() { + if (server != null) { + server.shutdown(); + server = null; + } + } + + private static byte[] ip(String s) throws UnknownHostException { + InetAddress addr = InetAddress.getByName(s); + byte[] data = addr.getAddress(); + if (data.length != 4) + throw new UnknownHostException(s + ": Not an IPv4 address"); + return data; + } +} diff --git a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java index c1225c20..09795d33 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -114,6 +114,7 @@ public final class GoBackend implements Backend { private ConnectivityManager.NetworkCallback vpnNetworkCallback; @Nullable private Network activeNetwork; private boolean obtainDhcpLease = false; + @Nullable private Bgp bgp; /** * Public constructor for GoBackend. @@ -435,6 +436,10 @@ public final class GoBackend implements Backend { service.protect(wgGetSocketV4(currentTunnelHandle)); service.protect(wgGetSocketV6(currentTunnelHandle)); Log.i(TAG, "Dhcp done"); + + bgp = new Bgp(channel, currentTunnel, currentTunnelHandle); + bgp.startServer(); + currentTunnel.onDhcpChange(dhcp); } @@ -714,6 +719,10 @@ public final class GoBackend implements Backend { currentTunnel = null; currentTunnelHandle = -1; currentConfig = null; + if (bgp != null) { + bgp.stopServer(); + bgp = null; + } stopHttpProxy(); if (vpnNetworkCallback != null) connectivityManager.unregisterNetworkCallback(vpnNetworkCallback); @@ -782,6 +791,10 @@ public final class GoBackend implements Backend { @Override public void onDestroy() { if (owner != null) { + if (owner.bgp != null) { + owner.bgp.stopServer(); + owner.bgp = null; + } owner.stopHttpProxy(); final Tunnel tunnel = owner.currentTunnel; if (tunnel != null) { diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java index 1dfadbce..fc94375b 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java @@ -8,9 +8,11 @@ package com.wireguard.android.backend; import androidx.annotation.Nullable; import com.wireguard.config.InetEndpoint; +import com.wireguard.config.InetNetwork; import com.wireguard.crypto.Key; import com.wireguard.util.NonNullForAll; +import java.util.List; import java.util.regex.Pattern; /** @@ -66,5 +68,7 @@ public interface Tunnel { */ void onDhcpChange(Dhcp newDhcp); - void onEndpointChange(@Nullable Key publicKey, @Nullable InetEndpoint newEndpoint); + void onEndpointChange(Key publicKey, @Nullable InetEndpoint newEndpoint); + + void onAllowedIpsChange(Key publicKey, @Nullable List<InetNetwork> addNetworks, @Nullable List<InetNetwork> removeNetworks); } diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java index 86f5f9a8..dffd534b 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java +++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java @@ -65,6 +65,10 @@ public final class InetEndpoint { } } + public static InetEndpoint fromAddress(final InetAddress address, final int port) { + return new InetEndpoint(address.getHostAddress(), true, port); + } + @Override public boolean equals(final Object obj) { if (!(obj instanceof InetEndpoint)) @@ -82,7 +86,11 @@ public final class InetEndpoint { } public Optional<InetEndpoint> getResolved() { - return Optional.ofNullable(resolved); + if (isResolved) { + return Optional.of(this); + } else { + return Optional.ofNullable(resolved); + } } /** diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt index bb3e76a7..e7c1262e 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt @@ -119,7 +119,7 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider { for (i in 0 until binding.peersLayout.childCount) { val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)) ?: continue - val publicKey = peer.item!!.peer.publicKey + val publicKey = peer.item!!.publicKey val rx = statistics.peerRx(publicKey) val tx = statistics.peerTx(publicKey) if (rx == 0L && tx == 0L) { diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt index d4a0b1c0..e38e473f 100644 --- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt +++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt @@ -7,6 +7,7 @@ package com.wireguard.android.model import android.util.Log import androidx.databinding.BaseObservable import androidx.databinding.Bindable +import com.wireguard.android.Application import com.wireguard.android.BR import com.wireguard.android.backend.Dhcp import com.wireguard.android.backend.Statistics @@ -14,10 +15,12 @@ import com.wireguard.android.backend.Tunnel import com.wireguard.android.databinding.Keyed import com.wireguard.android.util.applicationScope import com.wireguard.android.viewmodel.ConfigDetail +import com.wireguard.android.viewmodel.PeerDetail import com.wireguard.config.Config import com.wireguard.config.InetEndpoint -import com.wireguard.crypto.Key; -import java.util.Optional; +import com.wireguard.config.InetNetwork +import com.wireguard.crypto.Key +import java.util.Optional import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -63,10 +66,12 @@ class ObservableTunnel internal constructor( if (state != Tunnel.State.UP) { onStatisticsChanged(null) onDhcpChanged(null) - onEndpointChange(null, null) + Application.getCoroutineScope().launch { + onPeersReset() + } } else { configDetail?.peers?.forEach { - var endpoint: InetEndpoint? = it.peer.endpoint.orElse(null) + var endpoint: InetEndpoint? = it.peer?.endpoint?.orElse(null) it.endpoint = Optional.ofNullable(endpoint?.getResolved()?.get()) } } @@ -118,8 +123,8 @@ class ObservableTunnel internal constructor( } fun onConfigChanged(config: Config?): ConfigDetail? { - this.config = config this.configDetail = ConfigDetail(config) + this.config = config notifyPropertyChanged(BR.config) return configDetail } @@ -171,18 +176,89 @@ class ObservableTunnel internal constructor( return dhcp } - override fun onEndpointChange(publicKey: Key?, newEndpoint: InetEndpoint?) { + // Remove dynamic peers, and reset static peers + fun onPeersReset() { + Log.i(TAG, "ObservableTunnel onPeersReset") + var toRemove: MutableList<PeerDetail> = ArrayList() + + configDetail?.peers?.forEach { + if (it.peer == null) { + toRemove.add(it) + } else { + it.endpoint = Optional.empty() + } + } + + toRemove.forEach { + Log.i(TAG, "ObservableTunnel remove " + it) + configDetail?.peers?.remove(it) + } + } + + override fun onEndpointChange(publicKey: Key, newEndpoint: InetEndpoint?) { + Application.getCoroutineScope().launch { + onEndpointChanged(publicKey, newEndpoint) + } + } + + private fun onEndpointChanged(publicKey: Key, newEndpoint: InetEndpoint?) { + Log.i(TAG, "ObservableTunnel onEndpointChange " + newEndpoint) + var peer: PeerDetail? = null + configDetail?.peers?.forEach { - Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer) - if (publicKey == null || it.peer.publicKey.equals(publicKey)) { - if (newEndpoint == null) { - it.endpoint = it.peer.endpoint - } else { - it.endpoint = newEndpoint.getResolved() - } + if (it.publicKey.equals(publicKey) == true) { + Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer) + peer = it; + } + } + + if (peer == null) { + Log.i(TAG, "ObservableTunnel create peer " + publicKey) + peer = PeerDetail(publicKey) + configDetail?.peers?.add(peer) + } + + var peer2: PeerDetail = peer!! + + if (newEndpoint != null) { + peer2.endpoint = newEndpoint.getResolved() + } else { + var peer3 = peer2.peer + peer2.endpoint = if (peer3 != null) peer3.endpoint else Optional.empty() + } + } + + fun lookupPeer(publicKey: Key): PeerDetail { + configDetail?.peers?.forEach { + if (it.publicKey.equals(publicKey) == true) { + Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer) + return it } } + + Log.i(TAG, "ObservableTunnel create peer " + publicKey) + var peer: PeerDetail = PeerDetail(publicKey) + configDetail?.peers?.add(peer) + + return peer + } + + override fun onAllowedIpsChange(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) { + Application.getCoroutineScope().launch { + onAllowedIpsChanged(publicKey, addNetworks, removeNetworks) + } + } + + private fun onAllowedIpsChanged(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) { + var peer: PeerDetail = lookupPeer(publicKey) + + removeNetworks?.let() { + peer.allowedIps.removeAll(removeNetworks) + } + addNetworks?.let() { + peer.allowedIps.addAll(addNetworks) + } } diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt index abc81998..80b32fd5 100644 --- a/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt +++ b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt @@ -4,25 +4,37 @@ import android.util.Log import androidx.databinding.BaseObservable import androidx.databinding.Bindable import androidx.databinding.Observable +import androidx.databinding.ObservableList +import androidx.databinding.ObservableArrayList import com.wireguard.android.BR import com.wireguard.config.InetEndpoint +import com.wireguard.config.InetNetwork import com.wireguard.config.Peer +import com.wireguard.crypto.Key; import java.util.Optional; +import kotlin.collections.LinkedHashSet + class PeerDetail : BaseObservable { - var peer: Peer + var peer: Peer? private var owner: ConfigDetail? = null @get:Bindable + var publicKey: Key + + @get:Bindable + var allowedIps: ObservableList<InetNetwork> = ObservableArrayList<InetNetwork>() + + @get:Bindable var endpoint: Optional<InetEndpoint> = Optional.empty() get() { if (!field.isEmpty()) { return field } else { - return peer.endpoint + return Optional.ofNullable(peer?.endpoint?.get()) } } @@ -32,8 +44,20 @@ class PeerDetail : BaseObservable { notifyPropertyChanged(BR.endpoint) } + @get:Bindable + var persistentKeepalive: Optional<Int> = Optional.empty() + constructor(other: Peer) { peer = other + publicKey = other.getPublicKey() + allowedIps.addAll(other.getAllowedIps()) + endpoint = other.getEndpoint(); + persistentKeepalive = other.getPersistentKeepalive() + } + + constructor(publicKey: Key) { + peer = null + this.publicKey = publicKey } fun bind(owner: ConfigDetail) { @@ -45,6 +69,16 @@ class PeerDetail : BaseObservable { super.addOnPropertyChangedCallback(callback) } + /** + * Converts the {@code Peer} into a string suitable for debugging purposes. The {@code Peer} is + * identified by its public key and (if known) its endpoint. + * + * @return a concise single-line identifier for the {@code Peer} + */ + override fun toString(): String { + return "(Peer " + publicKey.toBase64() + ")" + } + companion object { private const val TAG = "WireGuard/PeerDetail" } diff --git a/ui/src/main/res/layout/tunnel_detail_fragment.xml b/ui/src/main/res/layout/tunnel_detail_fragment.xml index 1c4c25d6..59112d5e 100644 --- a/ui/src/main/res/layout/tunnel_detail_fragment.xml +++ b/ui/src/main/res/layout/tunnel_detail_fragment.xml @@ -367,6 +367,7 @@ android:layout_marginTop="8dp" android:divider="@null" android:orientation="vertical" + app:fragment="@{fragment}" app:items="@{config.peers}" app:layout="@{@layout/tunnel_detail_peer}" app:layout_constraintStart_toStartOf="parent" diff --git a/ui/src/main/res/layout/tunnel_detail_peer.xml b/ui/src/main/res/layout/tunnel_detail_peer.xml index e70c3df5..b4a07003 100644 --- a/ui/src/main/res/layout/tunnel_detail_peer.xml +++ b/ui/src/main/res/layout/tunnel_detail_peer.xml @@ -52,7 +52,7 @@ android:nextFocusForward="@id/pre_shared_key_text" android:onClick="@{ClipboardUtils::copyTextView}" android:singleLine="true" - android:text="@{item.peer.publicKey.toBase64}" + android:text="@{item.publicKey.toBase64}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/public_key_label" tools:text="wOs2eguFEohqIZxlSJ1CAT9584tc6ejj9hfGFsoBVkA=" /> @@ -93,7 +93,7 @@ android:layout_marginTop="8dp" android:labelFor="@+id/allowed_ips_text" android:text="@string/allowed_ips" - android:visibility="@{item.peer.allowedIps.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}" + android:visibility="@{item.allowedIps.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/pre_shared_key_text" /> @@ -107,8 +107,8 @@ android:nextFocusDown="@id/endpoint_text" android:nextFocusForward="@id/endpoint_text" android:onClick="@{ClipboardUtils::copyTextView}" - android:text="@{item.peer.allowedIps}" - android:visibility="@{item.peer.allowedIps.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}" + android:text="@{item.allowedIps}" + android:visibility="@{item.allowedIps.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/allowed_ips_label" tools:text="0.0.0.0/5, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3" /> @@ -147,7 +147,7 @@ android:layout_marginTop="8dp" android:labelFor="@+id/persistent_keepalive_text" android:text="@string/persistent_keepalive" - android:visibility="@{!item.peer.persistentKeepalive.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}" + android:visibility="@{!item.persistentKeepalive.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/endpoint_text" /> @@ -161,8 +161,8 @@ android:nextFocusDown="@id/transfer_text" android:nextFocusForward="@id/transfer_text" android:onClick="@{ClipboardUtils::copyTextView}" - android:text="@{@plurals/persistent_keepalive_seconds_unit(item.peer.persistentKeepalive.orElse(0), item.peer.persistentKeepalive.orElse(0))}" - android:visibility="@{!item.peer.persistentKeepalive.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}" + android:text="@{@plurals/persistent_keepalive_seconds_unit(item.persistentKeepalive.orElse(0), item.persistentKeepalive.orElse(0))}" + android:visibility="@{!item.persistentKeepalive.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/persistent_keepalive_label" tools:text="every 3 seconds" /> |