summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorJonathan <jonathan@mullvad.net>2023-12-05 10:03:08 +0100
committerJonathan <jonathan@mullvad.net>2024-01-03 14:38:41 +0100
commit4fdc34acbba60d5092e45ce3e513d30ec996c317 (patch)
tree80d3a23c1a96bd3d80e05ac66b530e39c252d48a /gui/src
parentc510df96772b1e4ab7998e739ced42806c78e931 (diff)
downloadmullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.tar.xz
mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.zip
Allow app to use custom socks5 and shadwosocks proxies
This PR has a couple of different purposes - Allow users to use socks5 local proxies with the CLI without having to be root nor use split-tunneling. This only works for OpenVPN. - Unify the types used by different proxy parts of the codebase, such as the Access Methods as well as some already existing OpenVPN proxy code. This PR changes the firewall on all desktop platforms as well as changes the routing table slightly on MacOS and Windows. On Linux the firewall code is modified to apply the appropriate firewall marks to all packages that go to a remote endpoint corresponding to the remote part of a local socks5 proxy. The firewall marks will allow the routing to be done without having to modify the routing table. On MacOS and Windows the routing table is modified to allow packages to go to that same endpoint to pass outside the VPN tunnel, it will additionally punch a hole in the firewall. The PR also migrates the settings file from version 7 to version 8 in order to properly and neatly unify Proxy related types. Finally it provides some slight extensions to the gRPC interface in order to allow for control over the custom proxy settings.
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/daemon-rpc.ts138
-rw-r--r--gui/src/main/default-settings.ts2
-rw-r--r--gui/src/renderer/app.tsx18
-rw-r--r--gui/src/renderer/components/select-location/select-location-hooks.ts14
-rw-r--r--gui/src/renderer/lib/utilityHooks.ts2
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts15
-rw-r--r--gui/src/shared/bridge-settings-builder.ts2
-rw-r--r--gui/src/shared/daemon-rpc-types.ts24
8 files changed, 144 insertions, 71 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index ad7b60dec2..d5d29ad709 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -15,6 +15,7 @@ import {
AuthFailedError,
BridgeSettings,
BridgeState,
+ BridgeType,
ConnectionConfig,
Constraint,
CustomListError,
@@ -51,7 +52,6 @@ import {
ObfuscationSettings,
ObfuscationType,
Ownership,
- ProxySettings,
ProxyType,
RelayEndpointType,
RelayLocation,
@@ -341,13 +341,52 @@ export class DaemonRpc {
public async setBridgeSettings(bridgeSettings: BridgeSettings): Promise<void> {
const grpcBridgeSettings = new grpcTypes.BridgeSettings();
- if ('normal' in bridgeSettings) {
- const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal);
- grpcBridgeSettings.setNormal(normalSettings);
+ if (bridgeSettings.type === 'custom') {
+ throw configNotSupported;
}
- if ('custom' in bridgeSettings) {
- throw configNotSupported;
+ grpcBridgeSettings.setBridgeType(grpcTypes.BridgeSettings.BridgeType.NORMAL);
+
+ const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal);
+ grpcBridgeSettings.setNormal(normalSettings);
+
+ if (bridgeSettings.custom) {
+ const customProxy = new grpcTypes.CustomProxy();
+
+ const customSettings = bridgeSettings.custom;
+
+ if ('local' in customSettings) {
+ const local = customSettings.local;
+ const socks5Local = new grpcTypes.Socks5Local();
+ socks5Local.setLocalPort(local.localPort);
+ socks5Local.setRemoteIp(local.remoteIp);
+ socks5Local.setRemotePort(local.remotePort);
+ customProxy.setSocks5local(socks5Local);
+ }
+ if ('remote' in customSettings) {
+ const remote = customSettings.remote;
+ const socks5Remote = new grpcTypes.Socks5Remote();
+ if (remote.auth) {
+ const auth = new grpcTypes.SocksAuth();
+ auth.setUsername(remote.auth.username);
+ auth.setPassword(remote.auth.password);
+ socks5Remote.setAuth(auth);
+ }
+ socks5Remote.setIp(remote.ip);
+ socks5Remote.setPort(remote.port);
+ customProxy.setSocks5remote(socks5Remote);
+ }
+ if ('shadowsocks' in customSettings) {
+ const shadowsocks = customSettings.shadowsocks;
+ const shadowOut = new grpcTypes.Shadowsocks();
+ shadowOut.setCipher(shadowsocks.cipher);
+ shadowOut.setIp(shadowsocks.ip);
+ shadowOut.setPort(shadowsocks.port);
+ shadowOut.setPassword(shadowsocks.password);
+ customProxy.setShadowsocks(shadowOut);
+ }
+
+ grpcBridgeSettings.setCustom(customProxy);
}
await this.call<grpcTypes.BridgeSettings, Empty>(
@@ -1140,49 +1179,64 @@ function convertFromRelaySettings(
function convertFromBridgeSettings(bridgeSettings: grpcTypes.BridgeSettings): BridgeSettings {
const bridgeSettingsObject = bridgeSettings.toObject();
+
+ const detailsMap: Record<grpcTypes.BridgeSettings.BridgeType, BridgeType> = {
+ [grpcTypes.BridgeSettings.BridgeType.NORMAL]: 'normal',
+ [grpcTypes.BridgeSettings.BridgeType.CUSTOM]: 'custom',
+ };
+ const type = detailsMap[bridgeSettingsObject.bridgeType];
+
const normalSettings = bridgeSettingsObject.normal;
- if (normalSettings) {
- const locationConstraint = convertFromLocationConstraint(
- bridgeSettings.getNormal()?.getLocation(),
- );
- const location = wrapConstraint(locationConstraint);
- const providers = normalSettings.providersList;
- const ownership = convertFromOwnership(normalSettings.ownership);
- return {
- normal: {
- location,
- providers,
- ownership,
- },
- };
- }
+ const locationConstraint = convertFromLocationConstraint(
+ bridgeSettings.getNormal()?.getLocation(),
+ );
+ const location = wrapConstraint(locationConstraint);
+ const providers = normalSettings!.providersList;
+ const ownership = convertFromOwnership(normalSettings!.ownership);
- const customSettings = (settings: ProxySettings): BridgeSettings => {
- return { custom: settings };
+ const normal = {
+ location,
+ providers,
+ ownership,
};
- const localSettings = bridgeSettingsObject.local;
- if (localSettings) {
- return customSettings({
- port: localSettings.port,
- peer: localSettings.peer,
- });
- }
+ let custom = undefined;
- const remoteSettings = bridgeSettingsObject.remote;
- if (remoteSettings) {
- return customSettings({
- address: remoteSettings.address,
- auth: remoteSettings.auth && { ...remoteSettings.auth },
- });
+ if (bridgeSettingsObject.custom) {
+ const localSettings = bridgeSettingsObject.custom.socks5local;
+ if (localSettings) {
+ custom = {
+ local: {
+ localPort: localSettings.localPort,
+ remoteIp: localSettings.remoteIp,
+ remotePort: localSettings.remotePort,
+ },
+ };
+ }
+ const remoteSettings = bridgeSettingsObject.custom.socks5remote;
+ if (remoteSettings) {
+ custom = {
+ remote: {
+ ip: remoteSettings.ip,
+ port: remoteSettings.port,
+ auth: remoteSettings.auth && { ...remoteSettings.auth },
+ },
+ };
+ }
+ const shadowsocksSettings = bridgeSettingsObject.custom.shadowsocks;
+ if (shadowsocksSettings) {
+ custom = {
+ shadowsocks: {
+ ip: shadowsocksSettings.ip,
+ port: shadowsocksSettings.port,
+ password: shadowsocksSettings.password,
+ cipher: shadowsocksSettings.cipher,
+ },
+ };
+ }
}
- const shadowsocksSettings = bridgeSettingsObject.shadowsocks!;
- return customSettings({
- peer: shadowsocksSettings.peer!,
- password: shadowsocksSettings.password!,
- cipher: shadowsocksSettings.cipher!,
- });
+ return { type, normal, custom };
}
function convertFromConnectionConfig(
diff --git a/gui/src/main/default-settings.ts b/gui/src/main/default-settings.ts
index 55f420b659..fea88e4c27 100644
--- a/gui/src/main/default-settings.ts
+++ b/gui/src/main/default-settings.ts
@@ -29,11 +29,13 @@ export function getDefaultSettings(): ISettings {
},
},
bridgeSettings: {
+ type: 'normal',
normal: {
location: 'any',
providers: [],
ownership: Ownership.any,
},
+ custom: undefined,
},
bridgeState: 'auto',
tunnelOptions: {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index b5eacf17b3..3806c12413 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -624,17 +624,13 @@ export default class AppRenderer {
private setBridgeSettings(bridgeSettings: BridgeSettings) {
const actions = this.reduxActions;
- if ('normal' in bridgeSettings) {
- actions.settings.updateBridgeSettings({
- normal: {
- location: liftConstraint(bridgeSettings.normal.location),
- },
- });
- } else if ('custom' in bridgeSettings) {
- actions.settings.updateBridgeSettings({
- custom: bridgeSettings.custom,
- });
- }
+ actions.settings.updateBridgeSettings({
+ type: bridgeSettings.type,
+ normal: {
+ location: liftConstraint(bridgeSettings.normal.location),
+ },
+ custom: bridgeSettings.custom,
+ });
}
private onDaemonConnected() {
diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts
index 48d81e594c..b795f4a74f 100644
--- a/gui/src/renderer/components/select-location/select-location-hooks.ts
+++ b/gui/src/renderer/components/select-location/select-location-hooks.ts
@@ -11,6 +11,7 @@ import log from '../../../shared/logging';
import { useAppContext } from '../../context';
import { useRelaySettingsModifier } from '../../lib/constraint-updater';
import { useHistory } from '../../lib/history';
+import { useSelector } from '../../redux/store';
import { LocationType, SpecialBridgeLocationType } from './select-location-types';
import { useSelectLocationContext } from './SelectLocationContainer';
@@ -88,6 +89,7 @@ function useOnSelectLocation() {
export function useOnSelectBridgeLocation() {
const { updateBridgeSettings } = useAppContext();
const { setLocationType } = useSelectLocationContext();
+ const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
const setLocation = useCallback(async (bridgeUpdate: BridgeSettings) => {
if (bridgeUpdate) {
@@ -101,10 +103,14 @@ export function useOnSelectBridgeLocation() {
}
}, []);
- const onSelectRelay = useCallback((location: RelayLocation) => {
- const bridgeUpdate = new BridgeSettingsBuilder().location.fromRaw(location).build();
- return setLocation(bridgeUpdate);
- }, []);
+ const onSelectRelay = useCallback(
+ (location: RelayLocation) => {
+ const bridgeUpdate = new BridgeSettingsBuilder().location.fromRaw(location).build();
+ bridgeUpdate.custom = bridgeSettings.custom;
+ return setLocation(bridgeUpdate);
+ },
+ [bridgeSettings],
+ );
const onSelectSpecial = useCallback((location: SpecialBridgeLocationType) => {
switch (location) {
diff --git a/gui/src/renderer/lib/utilityHooks.ts b/gui/src/renderer/lib/utilityHooks.ts
index 49f508a883..8c3925762e 100644
--- a/gui/src/renderer/lib/utilityHooks.ts
+++ b/gui/src/renderer/lib/utilityHooks.ts
@@ -67,5 +67,5 @@ export function useNormalRelaySettings() {
export function useNormalBridgeSettings() {
const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
- return 'normal' in bridgeSettings ? bridgeSettings.normal : undefined;
+ return bridgeSettings.normal;
}
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index b400799095..3d03093e0a 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -1,6 +1,7 @@
import { IWindowsApplication } from '../../../shared/application-types';
import {
BridgeState,
+ BridgeType,
CustomLists,
IDnsOptions,
IpVersion,
@@ -51,13 +52,11 @@ export type RelaySettingsRedux =
};
};
-export type BridgeSettingsRedux =
- | {
- normal: NormalBridgeSettingsRedux;
- }
- | {
- custom: ProxySettings;
- };
+export type BridgeSettingsRedux = {
+ type: BridgeType;
+ normal: NormalBridgeSettingsRedux;
+ custom?: ProxySettings;
+};
export interface IRelayLocationRelayRedux {
hostname: string;
@@ -140,9 +139,11 @@ const initialState: ISettingsReduxState = {
allowLan: false,
enableIpv6: true,
bridgeSettings: {
+ type: 'normal',
normal: {
location: 'any',
},
+ custom: undefined,
},
bridgeState: 'auto',
blockWhenDisconnected: false,
diff --git a/gui/src/shared/bridge-settings-builder.ts b/gui/src/shared/bridge-settings-builder.ts
index 858bea055d..2ee5469707 100644
--- a/gui/src/shared/bridge-settings-builder.ts
+++ b/gui/src/shared/bridge-settings-builder.ts
@@ -7,11 +7,13 @@ export default class BridgeSettingsBuilder {
public build(): BridgeSettings {
if (this.payload.location) {
return {
+ type: 'normal',
normal: {
location: this.payload.location,
providers: this.payload.providers ?? [],
ownership: this.payload.ownership ?? Ownership.any,
},
+ custom: undefined,
};
} else {
throw new Error('Unsupported configuration');
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index e83f9e5afc..48a4110e13 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -345,15 +345,20 @@ export interface IDnsOptions {
};
}
-export type ProxySettings = ILocalProxySettings | IRemoteProxySettings | IShadowsocksProxySettings;
+export type ProxySettings =
+ | { local: ILocalProxySettings }
+ | { remote: IRemoteProxySettings }
+ | { shadowsocks: IShadowsocksProxySettings };
export interface ILocalProxySettings {
- port: number;
- peer: string;
+ localPort: number;
+ remoteIp: string;
+ remotePort: number;
}
export interface IRemoteProxySettings {
- address: string;
+ ip: string;
+ port: number;
auth?: IRemoteProxyAuth;
}
@@ -363,7 +368,8 @@ export interface IRemoteProxyAuth {
}
export interface IShadowsocksProxySettings {
- peer: string;
+ ip: string;
+ port: number;
password: string;
cipher: string;
}
@@ -451,7 +457,13 @@ export interface IBridgeConstraints {
ownership: Ownership;
}
-export type BridgeSettings = { normal: IBridgeConstraints } | { custom: ProxySettings };
+export type BridgeType = 'normal' | 'custom';
+
+export interface BridgeSettings {
+ type: BridgeType;
+ normal: IBridgeConstraints;
+ custom?: ProxySettings;
+}
export interface ISocketAddress {
host: string;