diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 44a3f84e..5c9562c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -205,7 +205,7 @@ jobs: if-no-files-found: error build_rootshell: - if: needs.files_changed.outputs.rootshell_changed != '0' + if: needs.files_changed.outputs.rootshell_changed != '0' || needs.files_changed.outputs.installer_changed != '0' needs: - check_and_test - files_changed diff --git a/installer/src/tplink.rs b/installer/src/tplink.rs index 7046a229..66bf5e13 100644 --- a/installer/src/tplink.rs +++ b/installer/src/tplink.rs @@ -265,6 +265,14 @@ async fn handler(state: State, mut req: Request) -> Result, mut req: Request) -> Result { - // Intentionally register rayhunter-daemon before rayhunter-root so that we are less - // likely to run into race conditions where rayhunter-root is launched, and the - // installer kills the server. In practice both HTTP requests may execute concurrently - // anyway. - Globals.models.PTModel.add({applicationName: "rayhunter-daemon", enableState: 1, entryId: 2, openPort: "2400-2500", openProtocol: "TCP", triggerPort: "$(/etc/init.d/rayhunter_daemon start)", triggerProtocol: "TCP"}); - Globals.models.PTModel.add({applicationName: "rayhunter-root", enableState: 1, entryId: 1, openPort: "2300-2400", openProtocol: "TCP", triggerPort: "$(busybox telnetd -l /bin/sh)", triggerProtocol: "TCP"}); + data.extend(br#";document.addEventListener("DOMContentLoaded", () => { + console.log("rayhunter: start polling"); + + var rayhunterSleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + + var rayhunterPoll = window.setInterval(async () => { + Globals.models.PTModel.add({applicationName: "rayhunter-daemon", enableState: 1, entryId: 1, openPort: "2401", openProtocol: "TCP", triggerPort: "$(/etc/init.d/rayhunter_daemon start &)", triggerProtocol: "TCP"}); + console.log("rayhunter: first request succeeded, stopping rayhunter poll loop"); + window.clearInterval(rayhunterPoll); + + // PTModel.add actually does not wait for the request to finsh. + // Wait 1 second for the request to finish. + // Running both requests concurrently can get one of the two requests rejected, as + // sending a request with entryId: 2 is invalid if entryId 1 does not exist (yet) + // This only happens starting with firmware M7350(EU)_V9_9.0.2 Build 241021, earlier + // versions are not affected. + await rayhunterSleep(1000); + + console.log("rayhunter: running second request"); + Globals.models.PTModel.add({applicationName: "rayhunter-root", enableState: 1, entryId: 2, openPort: "2402", openProtocol: "TCP", triggerPort: "$(busybox telnetd -l /bin/sh &)", triggerProtocol: "TCP"}); // Do not use alert(), instead replace page with success message. Using alert() will // block the event loop in such a way that any background promises are blocked from // progress too. For example: The HTTP requests to register our port triggers! document.body.innerHTML = "

Success! You can go back to the rayhunter installer.

"; - - // We can stop polling now, presumably both requests are already inflight. - window.clearInterval(window.rayhunterPoll); - }, 1000);"#); + }, 1000); + });"#); response = Response::from_parts(parts, Body::from(Bytes::from(data))); response.headers_mut().remove("Content-Length"); } diff --git a/installer/src/util.rs b/installer/src/util.rs index d0f04a9e..0ded9525 100644 --- a/installer/src/util.rs +++ b/installer/src/util.rs @@ -91,7 +91,7 @@ pub async fn telnet_send_file( payload: &[u8], wait_for_prompt: bool, ) -> Result<()> { - echo!("Sending file {filename} ... "); + echo!("Sending file {filename}... "); let nc_output = { let filename = filename.to_owned(); let handle = tokio::spawn(async move { @@ -102,14 +102,31 @@ pub async fn telnet_send_file( ) .await }); - // wait for nc to become available. if the installer fails with connection refused, this - // likely is not high enough. - sleep(Duration::from_millis(100)).await; + let mut addr = addr; addr.set_port(8081); + let mut stream; + let mut attempts = 0; + + loop { + // wait for nc to become available, with exponential backoff. + // + // if the installer fails with connection refused, this + // likely is not high enough. + sleep(Duration::from_millis(100 * (1 << attempts))).await; + + stream = TcpStream::connect(addr).await; + attempts += 1; + if stream.is_ok() || attempts > 3 { + break; + } + + echo!("attempt {attempts}... "); + } + { - let mut stream = TcpStream::connect(addr).await?; + let mut stream = stream?; stream.write_all(payload).await?; // if the orbic is sluggish, we need for nc to write the data to disk before @@ -122,6 +139,7 @@ pub async fn telnet_send_file( sleep(Duration::from_millis(1000)).await; // ensure that stream is dropped before we wait for nc to terminate. + drop(stream); } handle.await??