Skip to content

Commit

Permalink
Add pre/post up/down support for rooted GoBackend
Browse files Browse the repository at this point in the history
When using the GoBackend on a rooted device allow for pre/post up/down
actions to be executed when the tunnel state changes. On non-rooted
devices the scripts are not executed but will still be parsed from the
configuration file. %i syntax is not supported. If any script fails to
execute the remaining scripts in that step are skipped

Signed-off-by: Adam Irr <[email protected]>
  • Loading branch information
adamirr committed Oct 10, 2020
1 parent 3a4bf35 commit cac5e07
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public final class GoBackend implements Backend {
@Nullable private static AlwaysOnCallback alwaysOnCallback;
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
private final Context context;
private final TunnelActionHandler tunnelActionHandler;
@Nullable private Config currentConfig;
@Nullable private Tunnel currentTunnel;
private int currentTunnelHandle = -1;
Expand All @@ -53,9 +54,10 @@ public final class GoBackend implements Backend {
*
* @param context An Android {@link Context}
*/
public GoBackend(final Context context) {
public GoBackend(final Context context, final TunnelActionHandler tunnelActionHandler) {
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
this.context = context;
this.tunnelActionHandler = tunnelActionHandler;
}

/**
Expand Down Expand Up @@ -279,7 +281,9 @@ private void setStateInternal(final Tunnel tunnel, @Nullable final Config config
if (tun == null)
throw new BackendException(Reason.TUN_CREATION_ERROR);
Log.d(TAG, "Go backend v" + wgVersion());
tunnelActionHandler.runPreUp(config.getInterface().getPreUp());
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
tunnelActionHandler.runPostUp(config.getInterface().getPostUp());
}
if (currentTunnelHandle < 0)
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
Expand All @@ -295,7 +299,11 @@ private void setStateInternal(final Tunnel tunnel, @Nullable final Config config
return;
}

if (currentConfig != null)
tunnelActionHandler.runPreDown(currentConfig.getInterface().getPreDown());
wgTurnOff(currentTunnelHandle);
if (currentConfig != null)
tunnelActionHandler.runPostDown(currentConfig.getInterface().getPostDown());
currentTunnel = null;
currentTunnelHandle = -1;
currentConfig = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.wireguard.android.backend;

import java.util.Collection;

/**
* A {@link TunnelActionHandler} implementation that does not execute any scripts.
*/
public final class NoopTunnelActionHandler implements TunnelActionHandler {

@Override
public void runPreUp(final Collection<String> scripts) {

}

@Override
public void runPostUp(final Collection<String> scripts) {

}

@Override
public void runPreDown(final Collection<String> scripts) {

}

@Override
public void runPostDown(final Collection<String> scripts) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.wireguard.android.backend;

import android.util.Log;

import com.wireguard.android.util.RootShell;
import com.wireguard.android.util.RootShell.RootShellException;
import com.wireguard.util.NonNullForAll;

import java.io.IOException;
import java.util.Collection;

/**
* A {@link TunnelActionHandler} implementation that executes scripts using a root shell.
* Scripts are executed sequentially. If there is an error executing a script for a given step
* the remaining scripts in that step are skipped.
*/
@NonNullForAll
public final class RootTunnelActionHandler implements TunnelActionHandler {

private static final String TAG = "WireGuard/TunnelAction";
private final RootShell rootShell;

public RootTunnelActionHandler(final RootShell rootShell) {
this.rootShell = rootShell;
}

@Override
public void runPreDown(final Collection<String> scripts) {
if (!scripts.isEmpty()) {
Log.d(TAG, "Running PreDown scripts");
runTunnelScripts(scripts);
}
}

@Override
public void runPostDown(final Collection<String> scripts) {
if (!scripts.isEmpty()) {
Log.d(TAG, "Running PostDown scripts");
runTunnelScripts(scripts);
}
}

@Override
public void runPreUp(final Collection<String> scripts) {
if (!scripts.isEmpty()) {
Log.d(TAG, "Running PreUp scripts");
runTunnelScripts(scripts);
}
}

@Override
public void runPostUp(final Collection<String> scripts) {
if (!scripts.isEmpty()) {
Log.d(TAG, "Running PostUp scripts");
runTunnelScripts(scripts);
}
}

private void runTunnelScripts(final Iterable<String> scripts) {
for (final String script : scripts) {
if (script.contains("%i")) {
Log.e(TAG, "'%i' syntax is not supported with the GoBackend. Aborting");
return;
}

try {
rootShell.run(null, script);
} catch (final IOException | RootShellException e) {
Log.e(TAG, "Failed to execute script.", e);
return;
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.wireguard.android.backend;

import java.util.Collection;

/**
* Handles executing Pre/Post Up/Down scripts when the state of the WireGuard tunnel changes
*/
public interface TunnelActionHandler {

/**
* Execute scripts before bringing up the tunnel
*
* @param scripts Collection of scripts to execute
*/
void runPreUp(Collection<String> scripts);

/**
* Execute scripts after bringing up the tunnel
*
* @param scripts Collection of scripts to execute
*/
void runPostUp(Collection<String> scripts);

/**
* Execute scripts before bringing down the tunnel
*
* @param scripts Collection of scripts to execute
*/
void runPreDown(Collection<String> scripts);

/**
* Execute scripts after bringing down the tunnel
*
* @param scripts Collection of scripts to execute
*/
void runPostDown(Collection<String> scripts);
}
84 changes: 83 additions & 1 deletion tunnel/src/main/java/com/wireguard/config/Interface.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.wireguard.util.NonNullForAll;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -45,6 +46,10 @@ public final class Interface {
private final KeyPair keyPair;
private final Optional<Integer> listenPort;
private final Optional<Integer> mtu;
private final List<String> preUp;
private final List<String> postUp;
private final List<String> preDown;
private final List<String> postDown;

private Interface(final Builder builder) {
// Defensively copy to ensure immutability even if the Builder is reused.
Expand All @@ -55,6 +60,10 @@ private Interface(final Builder builder) {
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
listenPort = builder.listenPort;
mtu = builder.mtu;
preUp = Collections.unmodifiableList(new ArrayList<>(builder.preUp));
postUp = Collections.unmodifiableList(new ArrayList<>(builder.postUp));
preDown = Collections.unmodifiableList(new ArrayList<>(builder.preDown));
postDown = Collections.unmodifiableList(new ArrayList<>(builder.postDown));
}

/**
Expand Down Expand Up @@ -93,6 +102,18 @@ public static Interface parse(final Iterable<? extends CharSequence> lines)
case "privatekey":
builder.parsePrivateKey(attribute.getValue());
break;
case "preup":
builder.parsePreUp(attribute.getValue());
break;
case "postup":
builder.parsePostUp(attribute.getValue());
break;
case "predown":
builder.parsePreDown(attribute.getValue());
break;
case "postdown":
builder.parsePostDown(attribute.getValue());
break;
default:
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
Expand All @@ -112,7 +133,12 @@ public boolean equals(final Object obj) {
&& includedApplications.equals(other.includedApplications)
&& keyPair.equals(other.keyPair)
&& listenPort.equals(other.listenPort)
&& mtu.equals(other.mtu);
&& mtu.equals(other.mtu)
&& preUp.equals(other.preUp)
&& postUp.equals(other.postUp)
&& preDown.equals(other.preDown)
&& postDown.equals(other.postDown);

}

/**
Expand Down Expand Up @@ -182,6 +208,22 @@ public Optional<Integer> getMtu() {
return mtu;
}

public List<String> getPreUp() {
return preUp;
}

public List<String> getPostUp() {
return postUp;
}

public List<String> getPreDown() {
return preDown;
}

public List<String> getPostDown() {
return postDown;
}

@Override
public int hashCode() {
int hash = 1;
Expand All @@ -192,6 +234,10 @@ public int hashCode() {
hash = 31 * hash + keyPair.hashCode();
hash = 31 * hash + listenPort.hashCode();
hash = 31 * hash + mtu.hashCode();
hash = 31 * hash + preUp.hashCode();
hash = 31 * hash + postUp.hashCode();
hash = 31 * hash + preDown.hashCode();
hash = 31 * hash + postDown.hashCode();
return hash;
}

Expand Down Expand Up @@ -231,6 +277,14 @@ public String toWgQuickString() {
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
for (final String script : preUp)
sb.append("PreUp = ").append(script).append('\n');
for (final String script : postUp)
sb.append("PostUp = ").append(script).append('\n');
for (final String script : preDown)
sb.append("PreDown = ").append(script).append('\n');
for (final String script : postDown)
sb.append("PostDown = ").append(script).append('\n');
return sb.toString();
}

Expand Down Expand Up @@ -263,6 +317,14 @@ public static final class Builder {
private Optional<Integer> listenPort = Optional.empty();
// Defaults to not present.
private Optional<Integer> mtu = Optional.empty();
// Defaults to empty list
private List<String> preUp = new ArrayList<>();
// Defaults to empty list
private List<String> postUp = new ArrayList<>();
// Defaults to empty list
private List<String> preDown = new ArrayList<>();
// Defaults to empty list
private List<String> postDown = new ArrayList<>();

public Builder addAddress(final InetNetwork address) {
addresses.add(address);
Expand Down Expand Up @@ -366,6 +428,26 @@ public Builder parsePrivateKey(final String privateKey) throws BadConfigExceptio
}
}

public Builder parsePreUp(final String script) throws BadConfigException {
preUp.add(script);
return this;
}

public Builder parsePostUp(final String script) throws BadConfigException {
postUp.add(script);
return this;
}

public Builder parsePreDown(final String script) throws BadConfigException {
preDown.add(script);
return this;
}

public Builder parsePostDown(final String script) throws BadConfigException {
postDown.add(script);
return this;
}

public Builder setKeyPair(final KeyPair keyPair) {
this.keyPair = keyPair;
return this;
Expand Down
5 changes: 5 additions & 0 deletions tunnel/src/test/java/com/wireguard/config/ConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;

Expand Down Expand Up @@ -45,5 +46,9 @@ public void valid_config_parses_correctly() throws IOException, ParseException {
assertEquals("Test config has exactly one peer", 1, config.getPeers().size());
assertEquals("Test config's allowed IPs are 0.0.0.0/0 and ::0/0", config.getPeers().get(0).getAllowedIps(), expectedAllowedIps);
assertEquals("Test config has one DNS server", 1, config.getInterface().getDnsServers().size());
assertEquals("Test config loads multiple pre up scripts", Arrays.asList("echo \"pre up 1\"", "echo \"pre up 2\""), config.getInterface().getPreUp());
assertEquals("Test config loads single post up script", Collections.singletonList("echo \"post up 1\""), config.getInterface().getPostUp());
assertEquals("Test config loads single pre down script", Collections.singletonList("echo \"pre down 1\""), config.getInterface().getPreDown());
assertEquals("Test config loads single post down scripts", Collections.singletonList("echo \"post down 1\""), config.getInterface().getPostDown());
}
}
6 changes: 6 additions & 0 deletions tunnel/src/test/resources/working.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
DNS = 192.0.2.0
PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
PreUp = echo "pre up 1"
PreUp = echo "pre up 2"
PostUp = echo "post up 1"
PreDown = echo "pre down 1"
PostDown = echo "post down 1"

[Peer]
AllowedIPs = 0.0.0.0/0, ::0/0
Endpoint = 192.0.2.1:51820
Expand Down
Loading

0 comments on commit cac5e07

Please sign in to comment.