Skip to content

Commit cac5e07

Browse files
committed
Add pre/post up/down support for rooted GoBackend
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]>
1 parent 3a4bf35 commit cac5e07

File tree

8 files changed

+277
-4
lines changed

8 files changed

+277
-4
lines changed

tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class GoBackend implements Backend {
4444
@Nullable private static AlwaysOnCallback alwaysOnCallback;
4545
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
4646
private final Context context;
47+
private final TunnelActionHandler tunnelActionHandler;
4748
@Nullable private Config currentConfig;
4849
@Nullable private Tunnel currentTunnel;
4950
private int currentTunnelHandle = -1;
@@ -53,9 +54,10 @@ public final class GoBackend implements Backend {
5354
*
5455
* @param context An Android {@link Context}
5556
*/
56-
public GoBackend(final Context context) {
57+
public GoBackend(final Context context, final TunnelActionHandler tunnelActionHandler) {
5758
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
5859
this.context = context;
60+
this.tunnelActionHandler = tunnelActionHandler;
5961
}
6062

6163
/**
@@ -279,7 +281,9 @@ private void setStateInternal(final Tunnel tunnel, @Nullable final Config config
279281
if (tun == null)
280282
throw new BackendException(Reason.TUN_CREATION_ERROR);
281283
Log.d(TAG, "Go backend v" + wgVersion());
284+
tunnelActionHandler.runPreUp(config.getInterface().getPreUp());
282285
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
286+
tunnelActionHandler.runPostUp(config.getInterface().getPostUp());
283287
}
284288
if (currentTunnelHandle < 0)
285289
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
@@ -295,7 +299,11 @@ private void setStateInternal(final Tunnel tunnel, @Nullable final Config config
295299
return;
296300
}
297301

302+
if (currentConfig != null)
303+
tunnelActionHandler.runPreDown(currentConfig.getInterface().getPreDown());
298304
wgTurnOff(currentTunnelHandle);
305+
if (currentConfig != null)
306+
tunnelActionHandler.runPostDown(currentConfig.getInterface().getPostDown());
299307
currentTunnel = null;
300308
currentTunnelHandle = -1;
301309
currentConfig = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.wireguard.android.backend;
7+
8+
import java.util.Collection;
9+
10+
/**
11+
* A {@link TunnelActionHandler} implementation that does not execute any scripts.
12+
*/
13+
public final class NoopTunnelActionHandler implements TunnelActionHandler {
14+
15+
@Override
16+
public void runPreUp(final Collection<String> scripts) {
17+
18+
}
19+
20+
@Override
21+
public void runPostUp(final Collection<String> scripts) {
22+
23+
}
24+
25+
@Override
26+
public void runPreDown(final Collection<String> scripts) {
27+
28+
}
29+
30+
@Override
31+
public void runPostDown(final Collection<String> scripts) {
32+
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.wireguard.android.backend;
7+
8+
import android.util.Log;
9+
10+
import com.wireguard.android.util.RootShell;
11+
import com.wireguard.android.util.RootShell.RootShellException;
12+
import com.wireguard.util.NonNullForAll;
13+
14+
import java.io.IOException;
15+
import java.util.Collection;
16+
17+
/**
18+
* A {@link TunnelActionHandler} implementation that executes scripts using a root shell.
19+
* Scripts are executed sequentially. If there is an error executing a script for a given step
20+
* the remaining scripts in that step are skipped.
21+
*/
22+
@NonNullForAll
23+
public final class RootTunnelActionHandler implements TunnelActionHandler {
24+
25+
private static final String TAG = "WireGuard/TunnelAction";
26+
private final RootShell rootShell;
27+
28+
public RootTunnelActionHandler(final RootShell rootShell) {
29+
this.rootShell = rootShell;
30+
}
31+
32+
@Override
33+
public void runPreDown(final Collection<String> scripts) {
34+
if (!scripts.isEmpty()) {
35+
Log.d(TAG, "Running PreDown scripts");
36+
runTunnelScripts(scripts);
37+
}
38+
}
39+
40+
@Override
41+
public void runPostDown(final Collection<String> scripts) {
42+
if (!scripts.isEmpty()) {
43+
Log.d(TAG, "Running PostDown scripts");
44+
runTunnelScripts(scripts);
45+
}
46+
}
47+
48+
@Override
49+
public void runPreUp(final Collection<String> scripts) {
50+
if (!scripts.isEmpty()) {
51+
Log.d(TAG, "Running PreUp scripts");
52+
runTunnelScripts(scripts);
53+
}
54+
}
55+
56+
@Override
57+
public void runPostUp(final Collection<String> scripts) {
58+
if (!scripts.isEmpty()) {
59+
Log.d(TAG, "Running PostUp scripts");
60+
runTunnelScripts(scripts);
61+
}
62+
}
63+
64+
private void runTunnelScripts(final Iterable<String> scripts) {
65+
for (final String script : scripts) {
66+
if (script.contains("%i")) {
67+
Log.e(TAG, "'%i' syntax is not supported with the GoBackend. Aborting");
68+
return;
69+
}
70+
71+
try {
72+
rootShell.run(null, script);
73+
} catch (final IOException | RootShellException e) {
74+
Log.e(TAG, "Failed to execute script.", e);
75+
return;
76+
}
77+
}
78+
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.wireguard.android.backend;
7+
8+
import java.util.Collection;
9+
10+
/**
11+
* Handles executing Pre/Post Up/Down scripts when the state of the WireGuard tunnel changes
12+
*/
13+
public interface TunnelActionHandler {
14+
15+
/**
16+
* Execute scripts before bringing up the tunnel
17+
*
18+
* @param scripts Collection of scripts to execute
19+
*/
20+
void runPreUp(Collection<String> scripts);
21+
22+
/**
23+
* Execute scripts after bringing up the tunnel
24+
*
25+
* @param scripts Collection of scripts to execute
26+
*/
27+
void runPostUp(Collection<String> scripts);
28+
29+
/**
30+
* Execute scripts before bringing down the tunnel
31+
*
32+
* @param scripts Collection of scripts to execute
33+
*/
34+
void runPreDown(Collection<String> scripts);
35+
36+
/**
37+
* Execute scripts after bringing down the tunnel
38+
*
39+
* @param scripts Collection of scripts to execute
40+
*/
41+
void runPostDown(Collection<String> scripts);
42+
}

tunnel/src/main/java/com/wireguard/config/Interface.java

+83-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.wireguard.util.NonNullForAll;
1515

1616
import java.net.InetAddress;
17+
import java.util.ArrayList;
1718
import java.util.Collection;
1819
import java.util.Collections;
1920
import java.util.LinkedHashSet;
@@ -45,6 +46,10 @@ public final class Interface {
4546
private final KeyPair keyPair;
4647
private final Optional<Integer> listenPort;
4748
private final Optional<Integer> mtu;
49+
private final List<String> preUp;
50+
private final List<String> postUp;
51+
private final List<String> preDown;
52+
private final List<String> postDown;
4853

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

6069
/**
@@ -93,6 +102,18 @@ public static Interface parse(final Iterable<? extends CharSequence> lines)
93102
case "privatekey":
94103
builder.parsePrivateKey(attribute.getValue());
95104
break;
105+
case "preup":
106+
builder.parsePreUp(attribute.getValue());
107+
break;
108+
case "postup":
109+
builder.parsePostUp(attribute.getValue());
110+
break;
111+
case "predown":
112+
builder.parsePreDown(attribute.getValue());
113+
break;
114+
case "postdown":
115+
builder.parsePostDown(attribute.getValue());
116+
break;
96117
default:
97118
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL,
98119
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
@@ -112,7 +133,12 @@ public boolean equals(final Object obj) {
112133
&& includedApplications.equals(other.includedApplications)
113134
&& keyPair.equals(other.keyPair)
114135
&& listenPort.equals(other.listenPort)
115-
&& mtu.equals(other.mtu);
136+
&& mtu.equals(other.mtu)
137+
&& preUp.equals(other.preUp)
138+
&& postUp.equals(other.postUp)
139+
&& preDown.equals(other.preDown)
140+
&& postDown.equals(other.postDown);
141+
116142
}
117143

118144
/**
@@ -182,6 +208,22 @@ public Optional<Integer> getMtu() {
182208
return mtu;
183209
}
184210

211+
public List<String> getPreUp() {
212+
return preUp;
213+
}
214+
215+
public List<String> getPostUp() {
216+
return postUp;
217+
}
218+
219+
public List<String> getPreDown() {
220+
return preDown;
221+
}
222+
223+
public List<String> getPostDown() {
224+
return postDown;
225+
}
226+
185227
@Override
186228
public int hashCode() {
187229
int hash = 1;
@@ -192,6 +234,10 @@ public int hashCode() {
192234
hash = 31 * hash + keyPair.hashCode();
193235
hash = 31 * hash + listenPort.hashCode();
194236
hash = 31 * hash + mtu.hashCode();
237+
hash = 31 * hash + preUp.hashCode();
238+
hash = 31 * hash + postUp.hashCode();
239+
hash = 31 * hash + preDown.hashCode();
240+
hash = 31 * hash + postDown.hashCode();
195241
return hash;
196242
}
197243

@@ -231,6 +277,14 @@ public String toWgQuickString() {
231277
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
232278
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
233279
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
280+
for (final String script : preUp)
281+
sb.append("PreUp = ").append(script).append('\n');
282+
for (final String script : postUp)
283+
sb.append("PostUp = ").append(script).append('\n');
284+
for (final String script : preDown)
285+
sb.append("PreDown = ").append(script).append('\n');
286+
for (final String script : postDown)
287+
sb.append("PostDown = ").append(script).append('\n');
234288
return sb.toString();
235289
}
236290

@@ -263,6 +317,14 @@ public static final class Builder {
263317
private Optional<Integer> listenPort = Optional.empty();
264318
// Defaults to not present.
265319
private Optional<Integer> mtu = Optional.empty();
320+
// Defaults to empty list
321+
private List<String> preUp = new ArrayList<>();
322+
// Defaults to empty list
323+
private List<String> postUp = new ArrayList<>();
324+
// Defaults to empty list
325+
private List<String> preDown = new ArrayList<>();
326+
// Defaults to empty list
327+
private List<String> postDown = new ArrayList<>();
266328

267329
public Builder addAddress(final InetNetwork address) {
268330
addresses.add(address);
@@ -366,6 +428,26 @@ public Builder parsePrivateKey(final String privateKey) throws BadConfigExceptio
366428
}
367429
}
368430

431+
public Builder parsePreUp(final String script) throws BadConfigException {
432+
preUp.add(script);
433+
return this;
434+
}
435+
436+
public Builder parsePostUp(final String script) throws BadConfigException {
437+
postUp.add(script);
438+
return this;
439+
}
440+
441+
public Builder parsePreDown(final String script) throws BadConfigException {
442+
preDown.add(script);
443+
return this;
444+
}
445+
446+
public Builder parsePostDown(final String script) throws BadConfigException {
447+
postDown.add(script);
448+
return this;
449+
}
450+
369451
public Builder setKeyPair(final KeyPair keyPair) {
370452
this.keyPair = keyPair;
371453
return this;

tunnel/src/test/java/com/wireguard/config/ConfigTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.io.InputStream;
1212
import java.util.Arrays;
1313
import java.util.Collection;
14+
import java.util.Collections;
1415
import java.util.HashSet;
1516
import java.util.Objects;
1617

@@ -45,5 +46,9 @@ public void valid_config_parses_correctly() throws IOException, ParseException {
4546
assertEquals("Test config has exactly one peer", 1, config.getPeers().size());
4647
assertEquals("Test config's allowed IPs are 0.0.0.0/0 and ::0/0", config.getPeers().get(0).getAllowedIps(), expectedAllowedIps);
4748
assertEquals("Test config has one DNS server", 1, config.getInterface().getDnsServers().size());
49+
assertEquals("Test config loads multiple pre up scripts", Arrays.asList("echo \"pre up 1\"", "echo \"pre up 2\""), config.getInterface().getPreUp());
50+
assertEquals("Test config loads single post up script", Collections.singletonList("echo \"post up 1\""), config.getInterface().getPostUp());
51+
assertEquals("Test config loads single pre down script", Collections.singletonList("echo \"pre down 1\""), config.getInterface().getPreDown());
52+
assertEquals("Test config loads single post down scripts", Collections.singletonList("echo \"post down 1\""), config.getInterface().getPostDown());
4853
}
4954
}

tunnel/src/test/resources/working.conf

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
33
DNS = 192.0.2.0
44
PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
5+
PreUp = echo "pre up 1"
6+
PreUp = echo "pre up 2"
7+
PostUp = echo "post up 1"
8+
PreDown = echo "pre down 1"
9+
PostDown = echo "post down 1"
10+
511
[Peer]
612
AllowedIPs = 0.0.0.0/0, ::0/0
713
Endpoint = 192.0.2.1:51820

0 commit comments

Comments
 (0)