From e047e76b226b335698ac1f56527558e5c9841114 Mon Sep 17 00:00:00 2001 From: Matteo Luppi <100372313+lups2000@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:07:27 +0200 Subject: [PATCH] Feature/wireguard mode (#6959) * define first components * Update CHANGELOG.md * Autofix generated JS files and do not patch them in tests (#6910) * autofix generated JS files and do not patch them in tests * autofix: setup python * [autofix.ci] apply automated fixes * autofix: setup node * add missing newline * fixup --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * Update package-lock.json, bump esbuild (#6915) update node version, bump esbuild * Fix clipboard handling in safari (#6917) * fix clipboard handling in safari closes #6911, #6909 Co-authored-by: Can Yesilyurt <36952967+canyesilyurt@users.noreply.github.com> * [autofix.ci] apply automated fixes * update dependencies --------- Co-authored-by: Can Yesilyurt <36952967+canyesilyurt@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * define first modes components * adapt code to reuse checkbox component * mitmproxy 10.3.1 * reopen main for development * [autofix.ci] apply automated fixes * improve mode toggle design * remove inline style * release script: add one less newline * fix zstd decompression (#6921) * fix zstd decompression (issue #6914) * add our fix to CHANGELOG * add explicit read_across_frames=True + move zstd test to test_encoding.py --------- Co-authored-by: Maximilian Hils * first attempt to make the modes functional * [autofix.ci] apply automated fixes * Update CHANGELOG.md * web: Upgrade Redux (#6926) * update redux and fix resulting test and type failures * update prettier * refactor code according to review * [autofix.ci] apply automated fixes * first prototype regular mode * [autofix.ci] apply automated fixes * Use upstream urwid again (#6929) use upstream urwid again * change name regular duck * Add `HttpConnectedHook` and `HttpConnectErrorHook` (#6930) * Add HttpConnectedHook and HttpConnectErrorHook * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * rewrite updateMode function and error handling regular mode * remove label component * add input field to local mode and fix onKeyDown issue * add dropdown to reverse mode * [autofix.ci] apply automated fixes * change defualt string reverse dropdown * add new logic to handle modes in the duck * add local mode with no applications * [autofix.ci] apply automated fixes * regular mode is now persistent * forgot recieve event in regular mode * join receive and update event * make local mode functional * make wireguard mode functional * [autofix.ci] apply automated fixes * make reverse mode functional * [autofix.ci] apply automated fixes * fix bug reverse mode when refreshing * fix bug local mode * [autofix.ci] apply automated fixes * fix old test * implement first tests * [autofix.ci] apply automated fixes * make DNS mode listen for both UDP and TCP (#6912) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * remove wireguard and reverse modes * change name function to set local applications * remove error handling * remove left over * add some other review changes * [autofix.ci] apply automated fixes * release ci: strip "v" prefix from sigstore file * fix addListen Addr function * adjust reverse protocol attribute * create proper method to parse the mode * adjust passing applications to local mode * fix onKeyDown listener and add new input field to local mode * [autofix.ci] apply automated fixes * remove leftover * update tests * [autofix.ci] apply automated fixes * remove wireguard and reverse files * remove additional state local mode * adjust tests local mode * update tests local mode * update last tests modes * [autofix.ci] apply automated fixes * fix: OSError raised when ipv6 is disabled (#6942) the function should return None in this case * use fetchMock instead of jest.mock this is slightly nicer because we are testing at the application boundary * duck tests should use action creators and not manually construct actions * move toggleLocal to fetchMock * move updateMode and ModeState into utils this avoids circular imports. utils may not be the perfect place, but much better than the circular imports * nits * [autofix.ci] apply automated fixes * update tests regular and local mode * [autofix.ci] apply automated fixes * review changes tests * [autofix.ci] apply automated fixes * adjust parseMode issue * [autofix.ci] apply automated fixes * add first components for wireguard mode * fix CI * fix typo * add tests to wireguard mode * [autofix.ci] apply automated fixes --------- Co-authored-by: Maximilian Hils Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Can Yesilyurt <36952967+canyesilyurt@users.noreply.github.com> Co-authored-by: mitmproxy release bot Co-authored-by: Andras Spitzer Co-authored-by: Gaurav Jain <64748057+errorxyz@users.noreply.github.com> Co-authored-by: Walt Chen --- .../__tests__/ducks/modes/wireguardSpec.tsx | 110 ++++++++++++++++++ web/src/js/__tests__/ducks/tutils.ts | 3 + web/src/js/components/Modes.tsx | 2 + web/src/js/components/Modes/Wireguard.tsx | 27 +++++ web/src/js/ducks/modes.ts | 2 + web/src/js/ducks/modes/utils.ts | 2 + web/src/js/ducks/modes/wireguard.ts | 73 ++++++++++++ 7 files changed, 219 insertions(+) create mode 100644 web/src/js/__tests__/ducks/modes/wireguardSpec.tsx create mode 100644 web/src/js/components/Modes/Wireguard.tsx create mode 100644 web/src/js/ducks/modes/wireguard.ts diff --git a/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx b/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx new file mode 100644 index 000000000..e6fea6bdf --- /dev/null +++ b/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx @@ -0,0 +1,110 @@ +import { enableFetchMocks } from "jest-fetch-mock"; +import wireguardReducer, { + getMode, + initialState, + toggleWireguard, +} from "../../../ducks/modes/wireguard"; +import { TStore } from "../tutils"; +import * as options from "../../../ducks/options"; + +describe("wireguardReducer", () => { + it("should return the initial state", () => { + const state = wireguardReducer(undefined, {}); + expect(state).toEqual(initialState); + }); + + it("should dispatch MODE_WIREGUARD_TOGGLE and updateMode", async () => { + enableFetchMocks(); + + const store = TStore(); + + expect(store.getState().modes.wireguard.active).toBe(false); + await store.dispatch(toggleWireguard()); + expect(store.getState().modes.wireguard.active).toBe(true); + expect(fetchMock).toHaveBeenCalled(); + }); + + it('should handle RECEIVE_OPTIONS action with data.mode containing "wireguard", an host and a port', () => { + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["wireguard:/path_example@localhost:8081"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(true); + expect(newState.listen_host).toBe("localhost"); + expect(newState.listen_port).toBe(8081); + expect(newState.path).toBe("/path_example"); + }); + + it('should handle RECEIVE_OPTIONS action with data.mode containing just "wireguard"', () => { + const initialState = { + active: false, + listen_host: "localhost", + listen_port: 8080, + }; + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["wireguard"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(true); + expect(newState.listen_host).toBe(""); + expect(newState.listen_port).toBe(""); + expect(newState.path).toBe(""); + }); + + it("should handle RECEIVE_OPTIONS action with data.mode containing another mode", () => { + const initialState = { + active: false, + listen_host: "localhost", + listen_port: 8080, + path: "/path_example", + }; + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["local"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(false); + expect(newState.listen_host).toBe(initialState.listen_host); + expect(newState.listen_port).toBe(initialState.listen_port); + expect(newState.path).toBe(initialState.path); + }); +}); + +describe("getMode", () => { + it("should return the correct mode string when active", () => { + const modes = { + wireguard: { + active: true, + }, + }; + const mode = getMode(modes); + expect(JSON.stringify(mode)).toBe(JSON.stringify(["wireguard"])); + }); + + it("should return an empty string when not active", () => { + const modes = { + wireguard: { + active: false, + path: "/path_example", + listen_host: "localhost", + listen_port: 8080, + }, + }; + const mode = getMode(modes); + expect(JSON.stringify(mode)).toBe(JSON.stringify([])); + }); +}); diff --git a/web/src/js/__tests__/ducks/tutils.ts b/web/src/js/__tests__/ducks/tutils.ts index fb93c81cd..592f34f32 100644 --- a/web/src/js/__tests__/ducks/tutils.ts +++ b/web/src/js/__tests__/ducks/tutils.ts @@ -132,6 +132,9 @@ export const testState: RootState = { active: false, applications: "", }, + wireguard: { + active: false, + }, }, }; diff --git a/web/src/js/components/Modes.tsx b/web/src/js/components/Modes.tsx index d1d236c9e..d3f055548 100644 --- a/web/src/js/components/Modes.tsx +++ b/web/src/js/components/Modes.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import Local from "./Modes/Local"; import Regular from "./Modes/Regular"; +import Wireguard from "./Modes/Wireguard"; export default function Modes() { return ( @@ -12,6 +13,7 @@ export default function Modes() {
+
); diff --git a/web/src/js/components/Modes/Wireguard.tsx b/web/src/js/components/Modes/Wireguard.tsx new file mode 100644 index 000000000..ec8ea7cb9 --- /dev/null +++ b/web/src/js/components/Modes/Wireguard.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import { ModeToggle } from "./ModeToggle"; +import { useAppDispatch, useAppSelector } from "../../ducks"; +import { toggleWireguard } from "../../ducks/modes/wireguard"; + +export default function Wireguard() { + const dispatch = useAppDispatch(); + + const { active, error } = useAppSelector((state) => state.modes.wireguard); + + return ( +
+

WireGuard Server

+

+ Start a WireGuard™ server and connect an external device for + transparent proxying. +

+ dispatch(toggleWireguard())} + > + Run WireGuard Server + + {error &&
{error}
} +
+ ); +} diff --git a/web/src/js/ducks/modes.ts b/web/src/js/ducks/modes.ts index f4797fd56..5e88ecce1 100644 --- a/web/src/js/ducks/modes.ts +++ b/web/src/js/ducks/modes.ts @@ -1,10 +1,12 @@ import { combineReducers } from "redux"; import regularReducer from "./modes/regular"; import localReducer from "./modes/local"; +import wireguardReducer from "./modes/wireguard"; const modes = combineReducers({ regular: regularReducer, local: localReducer, + wireguard: wireguardReducer, //add new modes here }); diff --git a/web/src/js/ducks/modes/utils.ts b/web/src/js/ducks/modes/utils.ts index e62b5698e..27de54ae9 100644 --- a/web/src/js/ducks/modes/utils.ts +++ b/web/src/js/ducks/modes/utils.ts @@ -1,5 +1,6 @@ import { getMode as getRegularModeConfig } from "./regular"; import { getMode as getLocalModeConfig } from "./local"; +import { getMode as getWireguardModeConfig } from "./wireguard"; import { fetchApi, rpartition } from "../../utils"; export interface ModeState { @@ -17,6 +18,7 @@ export const updateMode = () => { const activeModes: string[] = [ ...getRegularModeConfig(modes), ...getLocalModeConfig(modes), + ...getWireguardModeConfig(modes), //add new modes here ]; const response = await fetchApi.put("/options", { diff --git a/web/src/js/ducks/modes/wireguard.ts b/web/src/js/ducks/modes/wireguard.ts new file mode 100644 index 000000000..a81885d9b --- /dev/null +++ b/web/src/js/ducks/modes/wireguard.ts @@ -0,0 +1,73 @@ +import { + RECEIVE as RECEIVE_OPTIONS, + UPDATE as UPDATE_OPTIONS, +} from "../options"; +import { + ModeState, + includeModeState, + updateMode, + getModesOfType, +} from "./utils"; + +export const MODE_WIREGUARD_TOGGLE = "MODE_WIREGUARD_TOGGLE"; + +interface WireguardState extends ModeState { + path?: string; +} + +export const initialState: WireguardState = { + active: false, + path: "", +}; + +export const getMode = (modes) => { + const wireguardMode = modes.wireguard; + return includeModeState("wireguard", wireguardMode); +}; + +export const toggleWireguard = () => { + return async (dispatch) => { + dispatch({ type: MODE_WIREGUARD_TOGGLE }); + + const result = await dispatch(updateMode()); + + if (!result.success) { + //TODO: handle error + } + }; +}; + +const wireguardReducer = (state = initialState, action): WireguardState => { + switch (action.type) { + case MODE_WIREGUARD_TOGGLE: + return { + ...state, + active: !state.active, + }; + case UPDATE_OPTIONS: + case RECEIVE_OPTIONS: + if (action.data && action.data.mode) { + const currentModeConfig = getModesOfType( + "wireguard", + action.data.mode.value, + )[0]; + const isActive = currentModeConfig !== undefined; + return { + ...state, + active: isActive, + path: isActive ? currentModeConfig.data : state.path, + listen_host: isActive + ? currentModeConfig.listen_host + : state.listen_host, + listen_port: isActive + ? (currentModeConfig.listen_port as number) + : state.listen_port, + }; + } + return state; + default: + return state; + } +}; + +export default wireguardReducer;