Skip to content

fetch: HTTP redirect to localhost for guest #1302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

SuperMaxusa
Copy link
Contributor

This PR adds redirect to the host's localhost from the guest for fetch networking.
To use it, add local_http: true (disabled by default) into net_device:

var emulator = V86({
    net_device: {
        relay_url: "fetch",
        local_http: true
    }
})

Then go to <port>.v86local.http in your guest OS (note that this also requires CORS headers on the server).


Example: Links with opened 1234.v86local.http that redirects to localhost:1234

local_http_test

Also added a test and benchmark with HTTP local server.

P. S. If you have an idea how to make an embed for it (see e0e8077), let me know.

@chschnell
Copy link
Contributor

chschnell commented Apr 8, 2025

Ah, you're introducing a special DNS name for HTTP access to v86's localhost. Nice!

I installed your branch and played around with it, a bit of feedback:

I think that the DNS name pattern:

<port>.v86local.http

is specific enough to drop the explicit net_device.local_http flag to activate this new feature. In my opinion this could always be active (http is no valid TLD, so it's fine), the backend URL would then simply remain fetch.

I want to suggest an even simpler DNS name pattern:

v86host[:<port>]

with <port> defaulting to 80. The name v86host is leaned from localhost.

@SuperMaxusa
Copy link
Contributor Author

Thanks for your interest!

I think that the DNS name pattern: <port>.v86local.http is specific enough to drop the explicit net_device.local_http flag to activate this new feature.

Mostly yes. To be honest, I wanted to make this option toggleable for security reasons, i. e. avoiding situations where VM has access to your local HTTP ports "with no reasons", like a https://security.stackexchange.com/questions/232345/ebay-web-site-tries-to-connect-to-wss-localhostxxxxx-is-this-legit-or-they. On the other hand, we have CORS in browsers and (maybe) this security restrictions are not needed.

I want to suggest an even simpler DNS name pattern: v86host[:<port>] with defaulting to 80. The name v86host is leaned from localhost.

If I understand correctly, this will be omits the HTTP gateway, if port is not 80 in the current code:

FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
if(packet.tcp.dport === 80) {
let conn = new TCPConnection();
conn.state = TCP_STATE_SYN_RECEIVED;
conn.net = this;
conn.on("data", on_data_http);

To fix that, we will need to create a gateway with a different IP and add that IP to the DNS handler that a bit of difficult.

Feel free to correct me if I'm wrong.

@chschnell
Copy link
Contributor

chschnell commented Apr 8, 2025

VM has access to your local HTTP ports "with no reasons", like a https://security.stackexchange.com/questions/232345/ebay-web-site-tries-to-connect-to-wss-localhostxxxxx-is-this-legit-or-they

I see, and even though the overall risk seems pretty low here, it's better to be safe than sorry.

If I understand correctly, this will be omits the HTTP gateway, if port is not 80

You're right of course, I overlooked that. So maybe like this:

v86host[.<port>]

Or is there a risk that HTTP clients (in the guests) perhaps reject these domain names?

@SuperMaxusa
Copy link
Contributor Author

Or is there a risk that HTTP clients (in the guests) perhaps reject these domain names?

Hmm, technically TLDs with only numbers are not allowed (https://stackoverflow.com/a/53875771), because we can't have the first character as a non-alphabetic, so we can use something like v86host.p<port>, for example: v86host.p8080.

According to RFC 2606, the .localhost TLD is reserved. Maybe we can use <port>.localhost (e.g. 8080.localhost)? At least some servers, such as Caddy, uses this TLD and it works with browsers and other HTTP clients.

@chschnell
Copy link
Contributor

Maybe we can use <port>.localhost (e.g. 8080.localhost)?

That looks fine!

@SuperMaxusa
Copy link
Contributor Author

I'm not against about changing the URL pattern, .localhost doesn't interfere with loopback on Linux, BeOS and Windows with a fetch backend.

Gently pinging @copy: which option looks better to you: <port>.localhost or <port>.v86local.http?

console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2));
process.exit(0);
}
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bench currently fails for me:

Booting now, please stand by
mount: mounting host9p on /mnt failed: No such file or directory

Enable networking using 'udhcpc'
Files send via emulator appear in /mnt/
~% udhcpc;curl -o /dev/null -w '<%{speed_download}>%{exitcode}' 8686.v86local.ht
tp/bench
udhcpc: started, v1.36.1
udhcpc: broadcasting discover
udhcpc: broadcasting select for 192.168.86.100, server 192.168.86.1
udhcpc: lease of 192.168.86.100 obtained from 192.168.86.1, lease time 134217728
deleting routers
adding dns 192.168.86.1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0Fetch Failed: http://8686.v86local.http/bench
TypeError: fetch failed
100   202  100   202    0     0   2595      0 --:--:-- --:--:-- --:--:--  2805
<2595>0
Average download speed: 2.53 kB/s

(could you also have a quick look why curl returns 0 even though the header should be 502?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(could you also have a quick look why curl returns 0 even though the header should be 502?)

I added --fail, so curl should react on "Fetch failed".

This bench currently fails for me

Hmm, have you tried using a different HTTP port? Most likely, some app uses this port (8686) and fetch() cannot connect to the test server.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't think that was the problem (if the port is already used, I get EADDRINUSE). I can't reproduce the problem now, so maybe it was spurious.

That said, I think we should change the port to a random high port, and also pick different ports for the test and the benchmark.

@copy
Copy link
Owner

copy commented Apr 15, 2025

Gently pinging @copy: which option looks better to you: .localhost or .v86local.http?

How about <port>.v86.internal?

It would also make sense to have a "magic" ip address (like qemu's 10.0.2.2). That might be useful with wisp networking or with user mode networking in the future.

Comment on lines +63 to +67
const regex = /<(\d+)><(\d+)>\t<DONE>/.exec(serial_text);
const exitcode = parseInt(regex[1], 10);
const speed = parseInt(regex[2], 10); // in bytes

if(isNaN(exitcode))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.exec returns null when the string can't be found. If it is found, the isNaN calls shouldn't be necessary. So it'd be better to write something like:

const regex = /<(\d+)><(\d+)>\t<DONE>/.exec(serial_text);

if(!regex)
{
    console.error("...");
    process.exit(1);
}

const exitcode = parseInt(regex[1], 10);
const speed = parseInt(regex[2], 10); // in bytes

if(exitcode !== 0)
{
    console.error("Bench failed, curl returned non-zero exit code %s", exitcode);
    process.exit(exitcode);
}

console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2));

console.log("Average download speed: %s kB/s", (speed / 1024).toFixed(2));
process.exit(0);
}
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't think that was the problem (if the port is already used, I get EADDRINUSE). I can't reproduce the problem now, so maybe it was spurious.

That said, I think we should change the port to a random high port, and also pick different ports for the test and the benchmark.

} else {
response.writeHead(404);
response.write("Unknown endpoint");
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use response.end instead of response.write in here. And also add a response.end() after response.writeHead

@SuperMaxusa
Copy link
Contributor Author

How about <port>.v86.internal?

Sounds good

It would also make sense to have a "magic" ip address (like qemu's 10.0.2.2). That might be useful with wisp networking or with user mode networking in the future.

I agree, but not sure about wisp.
If I correctly remember, all TCP connections go through relay, so we need an HTTP handler from fetch and make the same thing that I wrote above.

@copy
Copy link
Owner

copy commented Apr 16, 2025

If I correctly remember, all TCP connections go through relay, so we need an HTTP handler from fetch and make the same thing that I wrote #1302 (comment).

At the moment, yes. But we already have tcp/ip packet parsing code, so we could redirect those connections. But that's not really related to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants