summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-08-13 17:21:05 +0200
committerOskar <oskar@mullvad.net>2024-08-21 17:05:50 +0200
commite2167ef3c42006db2b0b5ba041f1199d0d3f306d (patch)
tree81b8941c12880af51faaab0be8933a9e7b53cd49
parentb26659e05b50c3a1b0499620f683748287707769 (diff)
downloadmullvadvpn-e2167ef3c42006db2b0b5ba041f1199d0d3f306d.tar.xz
mullvadvpn-e2167ef3c42006db2b0b5ba041f1199d0d3f306d.zip
Add feature indicator plumbing
-rw-r--r--gui/src/main/daemon-rpc.ts52
-rw-r--r--gui/src/main/tunnel-state.ts2
-rw-r--r--gui/src/renderer/app.tsx4
-rw-r--r--gui/src/renderer/redux/connection/actions.ts21
-rw-r--r--gui/src/renderer/redux/connection/reducers.ts12
-rw-r--r--gui/src/shared/daemon-rpc-types.ts48
-rw-r--r--gui/src/shared/notifications/error.ts6
-rw-r--r--gui/test/e2e/mocked/tunnel-state.spec.ts4
-rw-r--r--gui/test/unit/notification-evaluation.spec.ts2
9 files changed, 127 insertions, 24 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 65e3c6b1c5..f328329d81 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -29,8 +29,9 @@ import {
DeviceEvent,
DeviceState,
DirectMethod,
- ErrorState,
ErrorStateCause,
+ ErrorStateDetails,
+ FeatureIndicator,
FirewallPolicyError,
FirewallPolicyErrorType,
IAppVersionInfo,
@@ -980,6 +981,9 @@ function convertFromTunnelState(tunnelState: grpcTypes.TunnelState): TunnelState
details:
tunnelStateObject.connecting?.relayInfo &&
convertFromTunnelStateRelayInfo(tunnelStateObject.connecting.relayInfo),
+ featureIndicators: convertFromFeatureIndicators(
+ tunnelStateObject.connecting?.featureIndicators?.activeFeaturesList,
+ ),
};
case grpcTypes.TunnelState.StateCase.CONNECTED: {
const relayInfo =
@@ -989,13 +993,16 @@ function convertFromTunnelState(tunnelState: grpcTypes.TunnelState): TunnelState
relayInfo && {
state: 'connected',
details: relayInfo,
+ featureIndicators: convertFromFeatureIndicators(
+ tunnelStateObject.connected?.featureIndicators?.activeFeaturesList,
+ ),
}
);
}
}
}
-function convertFromTunnelStateError(state: grpcTypes.ErrorState.AsObject): ErrorState {
+function convertFromTunnelStateError(state: grpcTypes.ErrorState.AsObject): ErrorStateDetails {
const baseError = {
blockingError: state.blockingError && convertFromBlockingError(state.blockingError),
};
@@ -1127,6 +1134,47 @@ function convertFromTunnelStateRelayInfo(
return undefined;
}
+function convertFromFeatureIndicators(
+ featureIndicators?: Array<grpcTypes.FeatureIndicator>,
+): Array<FeatureIndicator> | undefined {
+ return featureIndicators?.map(convertFromFeatureIndicator);
+}
+
+function convertFromFeatureIndicator(
+ featureIndicator: grpcTypes.FeatureIndicator,
+): FeatureIndicator {
+ switch (featureIndicator) {
+ case grpcTypes.FeatureIndicator.QUANTUM_RESISTANCE:
+ return FeatureIndicator.quantumResistance;
+ case grpcTypes.FeatureIndicator.MULTIHOP:
+ return FeatureIndicator.multihop;
+ case grpcTypes.FeatureIndicator.BRIDGE_MODE:
+ return FeatureIndicator.bridgeMode;
+ case grpcTypes.FeatureIndicator.SPLIT_TUNNELING:
+ return FeatureIndicator.splitTunneling;
+ case grpcTypes.FeatureIndicator.LOCKDOWN_MODE:
+ return FeatureIndicator.lockdownMode;
+ case grpcTypes.FeatureIndicator.UDP_2_TCP:
+ return FeatureIndicator.udp2tcp;
+ case grpcTypes.FeatureIndicator.LAN_SHARING:
+ return FeatureIndicator.lanSharing;
+ case grpcTypes.FeatureIndicator.DNS_CONTENT_BLOCKERS:
+ return FeatureIndicator.dnsContentBlockers;
+ case grpcTypes.FeatureIndicator.CUSTOM_DNS:
+ return FeatureIndicator.customDns;
+ case grpcTypes.FeatureIndicator.SERVER_IP_OVERRIDE:
+ return FeatureIndicator.serverIpOverride;
+ case grpcTypes.FeatureIndicator.CUSTOM_MTU:
+ return FeatureIndicator.customMtu;
+ case grpcTypes.FeatureIndicator.CUSTOM_MSS_FIX:
+ return FeatureIndicator.customMssFix;
+ case grpcTypes.FeatureIndicator.DAITA:
+ return FeatureIndicator.daita;
+ case grpcTypes.FeatureIndicator.SHADOWSOCKS:
+ return FeatureIndicator.shadowsocks;
+ }
+}
+
function convertFromTunnelType(tunnelType: grpcTypes.TunnelType): TunnelType {
const tunnelTypeMap: Record<grpcTypes.TunnelType, TunnelType> = {
[grpcTypes.TunnelType.WIREGUARD]: 'wireguard',
diff --git a/gui/src/main/tunnel-state.ts b/gui/src/main/tunnel-state.ts
index 297a7e481e..43ebe97ad6 100644
--- a/gui/src/main/tunnel-state.ts
+++ b/gui/src/main/tunnel-state.ts
@@ -46,7 +46,7 @@ export default class TunnelStateHandler {
this.setTunnelState(
state === 'disconnecting'
? { state, details: 'nothing' as const, location: this.lastKnownDisconnectedLocation }
- : { state },
+ : { state, featureIndicators: undefined },
);
this.tunnelStateFallbackScheduler.schedule(() => {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index e0655707c2..a4c76aa2d1 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -783,11 +783,11 @@ export default class AppRenderer {
batch(() => {
switch (tunnelState.state) {
case 'connecting':
- actions.connection.connecting(tunnelState.details);
+ actions.connection.connecting(tunnelState.details, tunnelState.featureIndicators);
break;
case 'connected':
- actions.connection.connected(tunnelState.details);
+ actions.connection.connected(tunnelState.details, tunnelState.featureIndicators);
break;
case 'disconnecting':
diff --git a/gui/src/renderer/redux/connection/actions.ts b/gui/src/renderer/redux/connection/actions.ts
index 35af568fb2..8a3f98efe5 100644
--- a/gui/src/renderer/redux/connection/actions.ts
+++ b/gui/src/renderer/redux/connection/actions.ts
@@ -1,6 +1,7 @@
import {
AfterDisconnect,
- ErrorState,
+ ErrorStateDetails,
+ FeatureIndicator,
ILocation,
ITunnelStateRelayInfo,
} from '../../../shared/daemon-rpc-types';
@@ -8,11 +9,13 @@ import {
interface IConnectingAction {
type: 'CONNECTING';
details?: ITunnelStateRelayInfo;
+ featureIndicators?: Array<FeatureIndicator>;
}
interface IConnectedAction {
type: 'CONNECTED';
details: ITunnelStateRelayInfo;
+ featureIndicators?: Array<FeatureIndicator>;
}
interface IDisconnectedAction {
@@ -26,7 +29,7 @@ interface IDisconnectingAction {
interface IBlockedAction {
type: 'TUNNEL_ERROR';
- errorState: ErrorState;
+ errorState: ErrorStateDetails;
}
interface INewLocationAction {
@@ -48,17 +51,25 @@ export type ConnectionAction =
| IBlockedAction
| IUpdateBlockStateAction;
-function connecting(details?: ITunnelStateRelayInfo): IConnectingAction {
+function connecting(
+ details?: ITunnelStateRelayInfo,
+ featureIndicators?: Array<FeatureIndicator>,
+): IConnectingAction {
return {
type: 'CONNECTING',
details,
+ featureIndicators,
};
}
-function connected(details: ITunnelStateRelayInfo): IConnectedAction {
+function connected(
+ details: ITunnelStateRelayInfo,
+ featureIndicators?: Array<FeatureIndicator>,
+): IConnectedAction {
return {
type: 'CONNECTED',
details,
+ featureIndicators,
};
}
@@ -75,7 +86,7 @@ function disconnecting(afterDisconnect: AfterDisconnect): IDisconnectingAction {
};
}
-function blocked(errorState: ErrorState): IBlockedAction {
+function blocked(errorState: ErrorStateDetails): IBlockedAction {
return {
type: 'TUNNEL_ERROR',
errorState,
diff --git a/gui/src/renderer/redux/connection/reducers.ts b/gui/src/renderer/redux/connection/reducers.ts
index ffb0fd1d84..b597d29a64 100644
--- a/gui/src/renderer/redux/connection/reducers.ts
+++ b/gui/src/renderer/redux/connection/reducers.ts
@@ -54,13 +54,21 @@ export default function (
case 'CONNECTING':
return {
...state,
- status: { state: 'connecting', details: action.details },
+ status: {
+ state: 'connecting',
+ details: action.details,
+ featureIndicators: action.featureIndicators,
+ },
};
case 'CONNECTED':
return {
...state,
- status: { state: 'connected', details: action.details },
+ status: {
+ state: 'connected',
+ details: action.details,
+ featureIndicators: action.featureIndicators,
+ },
};
case 'DISCONNECTED':
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 295db8f323..ed588ff811 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -65,7 +65,7 @@ export enum TunnelParameterError {
customTunnelHostResolutionError,
}
-export type ErrorState =
+export type ErrorStateDetails =
| {
cause:
| ErrorStateCause.ipv6Unavailable
@@ -180,12 +180,48 @@ export interface ITunnelStateRelayInfo {
location?: ILocation;
}
+// The order of the variants match the priority order and can be sorted on.
+export enum FeatureIndicator {
+ daita,
+ quantumResistance,
+ multihop,
+ bridgeMode,
+ splitTunneling,
+ lockdownMode,
+ udp2tcp,
+ shadowsocks,
+ lanSharing,
+ dnsContentBlockers,
+ customDns,
+ serverIpOverride,
+ customMtu,
+ customMssFix,
+}
+
+export type DisconnectedState = { state: 'disconnected'; location?: Partial<ILocation> };
+export type ConnectingState = {
+ state: 'connecting';
+ details?: ITunnelStateRelayInfo;
+ featureIndicators?: Array<FeatureIndicator>;
+};
+export type ConnectedState = {
+ state: 'connected';
+ details: ITunnelStateRelayInfo;
+ featureIndicators?: Array<FeatureIndicator>;
+};
+export type DisconnectingState = {
+ state: 'disconnecting';
+ details: AfterDisconnect;
+ location?: Partial<ILocation>;
+};
+export type ErrorState = { state: 'error'; details: ErrorStateDetails };
+
export type TunnelState =
- | { state: 'disconnected'; location?: Partial<ILocation> }
- | { state: 'connecting'; details?: ITunnelStateRelayInfo }
- | { state: 'connected'; details: ITunnelStateRelayInfo }
- | { state: 'disconnecting'; details: AfterDisconnect; location?: Partial<ILocation> }
- | { state: 'error'; details: ErrorState };
+ | DisconnectedState
+ | ConnectingState
+ | ConnectedState
+ | DisconnectingState
+ | ErrorState;
export interface RelayLocationCountry extends Partial<RelayLocationCustomList> {
country: string;
diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts
index dae080bb29..2871fea4d1 100644
--- a/gui/src/shared/notifications/error.ts
+++ b/gui/src/shared/notifications/error.ts
@@ -3,7 +3,7 @@ import { sprintf } from 'sprintf-js';
import { strings } from '../../config.json';
import {
AuthFailedError,
- ErrorState,
+ ErrorStateDetails,
ErrorStateCause,
TunnelParameterError,
TunnelState,
@@ -87,7 +87,7 @@ export class ErrorNotificationProvider
}
}
- private getMessage(errorState: ErrorState): string {
+ private getMessage(errorState: ErrorStateDetails): string {
if (errorState.blockingError) {
if (errorState.cause === ErrorStateCause.setFirewallPolicyError) {
switch (process.platform ?? window.env.platform) {
@@ -229,7 +229,7 @@ export class ErrorNotificationProvider
}
}
- private getActions(errorState: ErrorState): InAppNotificationAction | void {
+ private getActions(errorState: ErrorStateDetails): InAppNotificationAction | void {
const platform = process.platform ?? window.env.platform;
if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') {
diff --git a/gui/test/e2e/mocked/tunnel-state.spec.ts b/gui/test/e2e/mocked/tunnel-state.spec.ts
index b4de405841..3e20dab6ec 100644
--- a/gui/test/e2e/mocked/tunnel-state.spec.ts
+++ b/gui/test/e2e/mocked/tunnel-state.spec.ts
@@ -49,7 +49,7 @@ test('App should show connecting tunnel state', async () => {
});
await util.sendMockIpcResponse<TunnelState>({
channel: 'tunnel-',
- response: { state: 'connecting' },
+ response: { state: 'connecting', featureIndicators: undefined },
});
await expectConnecting(page);
});
@@ -73,7 +73,7 @@ test('App should show connected tunnel state', async () => {
};
await util.sendMockIpcResponse<TunnelState>({
channel: 'tunnel-',
- response: { state: 'connected', details: { endpoint, location } },
+ response: { state: 'connected', details: { endpoint, location }, featureIndicators: undefined },
});
await expectConnected(page);
diff --git a/gui/test/unit/notification-evaluation.spec.ts b/gui/test/unit/notification-evaluation.spec.ts
index d7d5e1fea6..27de83cf29 100644
--- a/gui/test/unit/notification-evaluation.spec.ts
+++ b/gui/test/unit/notification-evaluation.spec.ts
@@ -107,7 +107,7 @@ describe('System notifications', () => {
const controller = createController();
const disconnectedState: TunnelState = { state: 'disconnected' };
- const connectingState: TunnelState = { state: 'connecting' };
+ const connectingState: TunnelState = { state: 'connecting', featureIndicators: undefined };
const result1 = controller.notifyTunnelState(disconnectedState, false, false, false, true);
const result2 = controller.notifyTunnelState(disconnectedState, false, false, false, false);
const result3 = controller.notifyTunnelState(connectingState, false, false, false, true);