Skip to content
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

Routing traffic between netifs #537

Open
indexds opened this issue Dec 27, 2024 · 62 comments
Open

Routing traffic between netifs #537

indexds opened this issue Dec 27, 2024 · 62 comments

Comments

@indexds
Copy link
Contributor

indexds commented Dec 27, 2024

Hi,

I'm trying to wrap my head around how to route traffic from this netif to another? Since it's set as a dhcp router it doesn't seem like it's at all possible. I'd need to set the gateway of this netif to the ip of the other netif I want to route traffic through but that doesn't seem possible? The gateway here is only to set the ip of the netif itself inside its subnet from what I understand.

Is there a way to make the DHCP server run independently and have this configuration be a client instead?

    let mut eth_netif = EspEth::wrap_all(
        eth_driver,
        EspNetif::new_with_conf(&NetifConfiguration {
            ip_configuration: Some(IpConfiguration::Router(IpRouterConfiguration {
                subnet: Subnet {
                    gateway: Ipv4Addr::new(192, 168, 100, 1),
                    mask: Mask(30),
                },
                dhcp_enabled: true,
                dns: None,
                secondary_dns: None,
            })),
            stack: NetifStack::Eth,
            flags: 0,
            ..NetifConfiguration::eth_default_router()
        })?,
    )?;
@ivmarkov
Copy link
Collaborator

Do you want to route (L3; i.e. on IP level?; still might be possible, will try to dig it out) or is L2 bridging also good? For the latter case, see this issue: #508

@ivmarkov
Copy link
Collaborator

ivmarkov commented Dec 27, 2024

Ah unfortunately bridging seems to work only when the wifi is in AP mode though, probably not your use-case...

@indexds
Copy link
Contributor Author

indexds commented Dec 27, 2024

Do you want to route (L3; i.e. on IP level?; still might be possible, will try to dig it out) or is L2 bridging also good? For the latter case, see this issue: #508

Ideally on L3? But really I don't care so long as the receiving netif knows to automatically take L2 packets and forward them to its IP layer? I'm not sure about that. I'll look at everything you sent. On this issue and the other

@indexds
Copy link
Contributor Author

indexds commented Dec 27, 2024

What I need is to be able to route/bridge my wireguard netif that's been created in the C layer with the ethernet "router" netif I created in rust. Ideally this would be done in rust but I'm starting to believe I'm going to have to make my own wrappers here. I would just like to know what functions I should be using for this, or if it's a lost cause, just a nudge really.

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

    let mut eth_netif = EspEth::wrap_all(
        eth_driver,
        EspNetif::new_with_conf(&NetifConfiguration {
            flags: esp_netif_flags_ESP_NETIF_DHCP_SERVER | esp_netif_flags_ESP_NETIF_FLAG_AUTOUP,
            got_ip_event_id: NonZeroU32::new(ip_event_t_IP_EVENT_ETH_GOT_IP as _),
            lost_ip_event_id: NonZeroU32::new(ip_event_t_IP_EVENT_ETH_LOST_IP as _),
            key: "ETH_DEF".try_into().unwrap(),
            description: "eth".try_into().unwrap(),
            route_priority: 50, // Higher is better
            ip_configuration: Some(IpConfiguration::Client(IpClientConfiguration::Fixed(IpClientSettings {
                ip: Ipv4Addr::new(10, 10, 10, 1),
                subnet: Subnet {
                    gateway: Ipv4Addr::new(10, 10, 10, 1), // This is the gateway advertised by the dhcp server
                    // real_gateway: IpV4Addr::new(69, 69, 69, 69) // This would be the gateway this if routes its packets to
                    mask: Mask(30),
                },
                dns: None,
                secondary_dns: None,
            }))),
            stack: NetifStack::Eth,
            custom_mac: None,
        })?,
    )?;

To recenter the problem, I need a way to differentiate the gateway that the dhcp server advertises to the network, and the gateway that the netif itself will use to connect to the internet. Here, that would be the ip of my wireguard netif. Otherwise I'm a bit stuck.

@ivmarkov
Copy link
Collaborator

    let mut eth_netif = EspEth::wrap_all(
        eth_driver,
        EspNetif::new_with_conf(&NetifConfiguration {
            flags: esp_netif_flags_ESP_NETIF_DHCP_SERVER | esp_netif_flags_ESP_NETIF_FLAG_AUTOUP,
            got_ip_event_id: NonZeroU32::new(ip_event_t_IP_EVENT_ETH_GOT_IP as _),
            lost_ip_event_id: NonZeroU32::new(ip_event_t_IP_EVENT_ETH_LOST_IP as _),
            key: "ETH_DEF".try_into().unwrap(),
            description: "eth".try_into().unwrap(),
            route_priority: 50, // Higher is better
            ip_configuration: Some(IpConfiguration::Client(IpClientConfiguration::Fixed(IpClientSettings {
                ip: Ipv4Addr::new(10, 10, 10, 1),
                subnet: Subnet {
                    gateway: Ipv4Addr::new(10, 10, 10, 1), // This is the gateway advertised by the dhcp server
                    // real_gateway: IpV4Addr::new(69, 69, 69, 69) // This would be the gateway this if routes its packets to
                    mask: Mask(30),
                },
                dns: None,
                secondary_dns: None,
            }))),
            stack: NetifStack::Eth,
            custom_mac: None,
        })?,
    )?;

To recenter the problem, I need a way to differentiate the gateway that the dhcp server advertises to the network, and the gateway that the netif itself will use to connect to the internet. Here, that would be the ip of my wireguard netif. Otherwise I'm a bit stuck.

OK now clear. But then your use case is definitely then routing and not bridging, in that you are creating a mini router (+ quite possibly, NAT).

Also, I think for a proper router you absolutely need two netifs, and not one:

  • One netif would be the one you have currently, which is the gateway for your "mini-LAN"
  • Then you need another netif, "WAN" of sorts, which would be a regular DHCP (or a fixed-IP) client in the 69.69.69.69 network
  • Finally, you need logic that would route packets from one of the networks to the other. Also, since your LAN network is using internal IPs (10.10.10.0/30) you probably want to also have a "mini-NAT" that re-writes the IP packets before they hit the 69.69.69.69 network or the other way around (assuming that one is external, but even if it isn't you likely want NAT?)

Now, honestly I'm not sure how to do the routing & NAT excercise. But there is something in ESP-IDF called "NAPT" which is badly documented, but used to work back in time in a now-archived old project of mine. I think this was a simplistic "NAT+router", but you need to google a bit on the Internet as to exactly what it was as I am actually having a hard time recollecting my memories. For one, I'm not sure why I don't have any code that connects two netifs together (the LAN and WAN one) when enabling NAPT. It could well be, that the two netifs in question are the Wifi STA and AP netifs, hence no need for any explicitness. But you probably want instead eth as LAN and Wifi STA as WAN (or another eth as WAN).

@ivmarkov
Copy link
Collaborator

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

Thanks i'll look all this up. I had another idea elsewise, though I don't know what's it's worth. What if i create the eth netif with gateway=ip as I did before, advertise that to the pc, then sneakily replace the netif with another that's got the gateway of what I need? So long as we don't say anything, the pc should be none the wiser, no? And have the right gateway to boot. The only issue I can think of is when the lease expires we're kinda screwed. And lwip only allows the lease to go up to an hour.

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

The other idea was to go to L2 and try to bridge the two netifs there to bypass the whole DHCP+routing problem but I don't know how feasible that is.

@ivmarkov
Copy link
Collaborator

The other idea was to go to L2 and try to bridge the two netifs there to bypass the whole DHCP+routing problem but I don't know how feasible that is.

Bridging means a single ip for all netifs in the bridge which is not what you want?

I don't think either this or the earlier hack would work. After all, you want to correctly pass packets between two different networks so you have to properly do routing. And moreover - likely nat too.

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

The other idea was to go to L2 and try to bridge the two netifs there to bypass the whole DHCP+routing problem but I don't know how feasible that is.

Bridging means a single ip for all netifs in the bridge which is not what you want?

I don't think either this or the earlier hack would work. After all, you want to correctly pass packets between two different networks so you have to properly do routing. And moreover - likely nat too.

I'm being stupid with my language, what I meant by "bridge" here is "make callbacks between the two netifs so that they forward packets to each other"

@ivmarkov
Copy link
Collaborator

ivmarkov commented Dec 29, 2024

The other idea was to go to L2 and try to bridge the two netifs there to bypass the whole DHCP+routing problem but I don't know how feasible that is.

Bridging means a single ip for all netifs in the bridge which is not what you want?
I don't think either this or the earlier hack would work. After all, you want to correctly pass packets between two different networks so you have to properly do routing. And moreover - likely nat too.

I'm being stupid with my langage, what I meant by "bridge" here is "make callbacks between the two netifs so that they forward packets to each other"

Sure BUT:

  • you need to only forward some packets? As in from LAN to WAN only packets with dst_ip <> 10.10.10.1/30
  • Similar to the other way around
  • And again: you still need NAT

Above might be possible but I wonder why not just giving NAPT a try?

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

Above might be possible but I wonder why not just giving NAPT a try?

Oh I certainly will, I was just throwing ideas in the wind first.

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

But wait so we have a wrapper to enable napt but not to do anything with it? Or am I blind?

@indexds
Copy link
Contributor Author

indexds commented Dec 29, 2024

@indexds
Copy link
Contributor Author

indexds commented Dec 30, 2024

Well that clearly wasn't it. I'm kinda lost.

I did this:

    log::info!("Enabling napt on eth netif..");

    // Necessary for routing packets between subnets.
    eth_netif.netif_mut().enable_napt(true);

Now what? Nevermind wrappers, i don't even understand what C functions I have to use now that I enabled napt.

@ivmarkov
Copy link
Collaborator

Well that clearly wasn't it. I'm kinda lost.

I did this:

    log::info!("Enabling napt on eth netif..");

    // Necessary for routing packets between subnets.
    eth_netif.netif_mut().enable_napt(true);

Now what? Nevermind wrappers, i don't even understand what C functions I have to use now that I enabled napt.

You don't have to use any extra C functions. Just make sure you have another netif (wifi or eth) which is configured to your 67.67.67.67 network and the routing should just start happening automatically between the two the moment you call enable_napt on the 10.0.0.1 one.

@indexds
Copy link
Contributor Author

indexds commented Dec 31, 2024

I can't clone the eth driver i'm wrapping to make the new ethernet netif.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Dec 31, 2024

I can't clone the eth driver

I do not understand what you are trying to achieve with that. Of course you can't and should not clone. And of course you can't and should not have multiple netifs assig ed to the same eth driver.

If you explain what setup you are actually trying to achieve (say, with terminology as if you were using a regular pc networking) I might be able to help /suggest something, but otherwise I cannot explain to myself why you are not doing two drivers / netifs as I already suggested. As in your rmii eth + wifi sta. Or your rmii eth + an spi eth. Unless you use vlans, you anyway need two physical mediums for routing.

@indexds
Copy link
Contributor Author

indexds commented Dec 31, 2024

Ok so what I have, is a setup like this:

               This works        ????                            This works  This works      This works
PC              <->  EthNetif      |   WgNetif                <-> STA   <-|-> Any router <-> Random Wg endpoint
DHCP                 10.10.10.1    |    Any IP                    DHCP    |     GW                                  
                       /30 GW      |    Any subnet                        |

What I need is to be able to send packets from eth to wg0 and back. I don't really care how so long as they can speak to each other.

@indexds
Copy link
Contributor Author

indexds commented Dec 31, 2024

I will know at runtime what the ip and subnet of the wgnetif is so it being variable should not be a problem, i just dont really know how to forward packets between the two netifs

@ivmarkov
Copy link
Collaborator

What is WgNetif in the PC world please? Tun? Tap? Something else?

@indexds
Copy link
Contributor Author

indexds commented Dec 31, 2024

Wireguard virtual interface, so TUN.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Dec 31, 2024

Then you might need something like this on the esp32 too. But what I don't get it is ... do you plan to run the wireguard vpn layer on the esp32 itself? Isn't it a bit underpowered for that? As in like 300kb ram (not counting psram) and relatively slowish?

@ivmarkov
Copy link
Collaborator

Hm, apparently it is possible, not sure how fast it is though: https://github.com/ciniml/WireGuard-ESP32-Arduino

@ivmarkov
Copy link
Collaborator

Separate from how the whole wireguard networking works (I need to read on that) you might have to tap into mbedtls or else it would be painfully slow. The mbedtls impl for esp idf have some if the algorithms implemented using hardware, so much faster (or to put it another way, not unbearably slow).

@ivmarkov
Copy link
Collaborator

But I think you are at a point where you need to explain your idea in more detail, as I'm at a loss as to what device you are trying to build.

Like, is this some sort of wireguard vpn "device" and if yes, what is the point?

@indexds
Copy link
Contributor Author

indexds commented Dec 31, 2024

The idea is to make a plug and play usb key that encapsulates both connectivity and vpn as transparently as possible for the user. Nothing must run on the computer. I've managed to get the tunnel to work but I don't understand how to make the wg netif speak to the eth netif so that the computer itself can speak to the internet through the vpn.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

@indexds

Hmmm

"something" "netif" by calling its "tx()" method. And the other way around for rx. <== THIS is unclear and this is where I don't think any bridging would help... :(

Actually... I take my words back.
I'm no longer sure this will not work with bridging... Why wouldn't it and maybe you are right? Bridging the wg_netif + eth perhaps does exactly what you want, as this way the wg_netif would see each packet that comes via the eth, and will be able to "inject" new packets to the eth, which is maybe exactly what you want? Speculating. Anyway, on to your question:

        let eth_handle: *mut esp_netif_t = eth_netif.lock().unwrap().netif_mut().handle();
        let wg_handle: *mut netif = (*WG_CTX.lock().unwrap().0).netif;

        esp!(esp_netif_bridge_add_port(bridge_handle, eth_handle))?;
        esp!(esp_netif_bridge_add_port(bridge_handle, wg_handle))?; //Not working

It is not working because wg_handle is a native LwIp netif and not the esp_netif_t type which the bridge wants. Can't remember off the top of my head how a wrapping of a lwIp netif into an Esp esp_netif_t was happening, but it should be possible.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

T.b.h. I'm not really sure where this "67.67.67.67" network you were mentioning earlier even fits into the picture?
You only need two networks:

  • 10.0.0.1/30 - this is what your PC would see. This is where your "Esp" is the router. The "eth"
  • Some other network, couldn't care less what exactly, where you are a DHCP client and this network has internet access - this is what you get from the Wifi STA of the ESP and is possibly your home router network, which is "insecure" and where your PC should not connect (it connects to the 10.0.0.1/30 of the ESP)

That's it?

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

Btw you can do the above purely with Wifi, as the Esp can operate simultaneously in an AP+STA mode. No need for Eth or anything like that.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

This is a very heavyweight way to avoid converting netif to esp_netif_t but why don't you use this code (or similar):

pub struct EspNetifDriver<'d, T>

You just need to create a regular EspNetif, then wrap it with this driver, then bridge the wrapped EspNetif with the Eth, and then via the driver (EspNetifDriver) you have tx() rx() methods, so that you can pull/push packets from/to the bridge?

This means you need to do adaptation of the LwIP Wireguard code, but at least this is solving the "netif"-to-"esp_netif_t" problem?

@indexds
Copy link
Contributor Author

indexds commented Jan 1, 2025

T.b.h. I'm not really sure where this "67.67.67.67" network you were mentioning earlier even fits into the picture? You only need two networks:

  • 10.0.0.1/30 - this is what your PC would see. This is where your "Esp" is the router. The "eth"
  • Some other network, couldn't care less what exactly, where you are a DHCP client and this network has internet access - this is what you get from the Wifi STA of the ESP and is possibly your home router network, which is "insecure" and where your PC should not connect (it connects to the 10.0.0.1/30 of the ESP)

That's it?

Yeah that was merely an exemple i used to illustrate the problem, it has no basis in reality.

@indexds

Hmmm

"something" "netif" by calling its "tx()" method. And the other way around for rx. <== THIS is unclear and this is where I don't think any bridging would help... :(

Actually... I take my words back. I'm no longer sure this will not work with bridging... Why wouldn't it and maybe you are right? Bridging the wg_netif + eth perhaps does exactly what you want, as this way the wg_netif would see each packet that comes via the eth, and will be able to "inject" new packets to the eth, which is maybe exactly what you want? Speculating. Anyway, on to your question:

Should be what I want. I think. The http server on the side would still see the request anyway right? The request would just be duplicated and also sent to the bridge which would then drop it or do whatever, which we don't care about. (I think.)

Btw you can do the above purely with Wifi, as the Esp can operate simultaneously in an AP+STA mode. No need for Eth or anything like that.

I need eth because i'm building an usb key, I have a schematic that converts usb to eth frames that are interpreted by the esp32.

This is a very heavyweight way to avoid converting netif to esp_netif_t but why don't you use this code (or similar):

pub struct EspNetifDriver<'d, T>

You just need to create a regular EspNetif, then wrap it with this driver, then bridge the wrapped EspNetif with the Eth, and then via the driver (EspNetifDriver) you have tx() rx() methods, so that you can pull/push packets from/to the bridge?

This means you need to do adaptation of the LwIP Wireguard code, but at least this is solving the "netif"-to-"esp_netif_t" problem?

I'll look into it, thanks. My main gripe with the whole conversion thing was I don't want to have to go through the thousands of lines of wireguard C code I have in the lib to modify everything to be esp_netif_t. If there's a way to wrap it in the rust layer, i'm all for it, otherwise i'll bite the bullet.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

T.b.h. I'm not really sure where this "67.67.67.67" network you were mentioning earlier even fits into the picture? You only need two networks:

  • 10.0.0.1/30 - this is what your PC would see. This is where your "Esp" is the router. The "eth"
  • Some other network, couldn't care less what exactly, where you are a DHCP client and this network has internet access - this is what you get from the Wifi STA of the ESP and is possibly your home router network, which is "insecure" and where your PC should not connect (it connects to the 10.0.0.1/30 of the ESP)

That's it?

Yeah that was merely an exemple i used to illustrate the problem, it has no basis in reality.

Clear now.

@indexds
Hmmm

"something" "netif" by calling its "tx()" method. And the other way around for rx. <== THIS is unclear and this is where I don't think any bridging would help... :(

Actually... I take my words back. I'm no longer sure this will not work with bridging... Why wouldn't it and maybe you are right? Bridging the wg_netif + eth perhaps does exactly what you want, as this way the wg_netif would see each packet that comes via the eth, and will be able to "inject" new packets to the eth, which is maybe exactly what you want? Speculating. Anyway, on to your question:

Should be what I want. I think. The http server on the side would still see the request anyway right? The request would just be duplicated and also sent to the bridge which would then drop it or do whatever, which we don't care about. (I think.)

That's also exactly how I understand it (or rather - hope it would work).

Btw you can do the above purely with Wifi, as the Esp can operate simultaneously in an AP+STA mode. No need for Eth or anything like that.

I need eth because i'm building an usb key, I have a schematic that converts usb to eth frames that are interpreted by the esp32.

Fair enough. And really a side topic. Risking the derail the conversation even further: there is this s3 chip which I think was able to act as a real USB host. Where I'm going with that is that maybe you can even do it without any schematics, by implementing the USB-to-Ethernet purely in software with that chip. But yes, side topic.

This is a very heavyweight way to avoid converting netif to esp_netif_t but why don't you use this code (or similar):

pub struct EspNetifDriver<'d, T>

You just need to create a regular EspNetif, then wrap it with this driver, then bridge the wrapped EspNetif with the Eth, and then via the driver (EspNetifDriver) you have tx() rx() methods, so that you can pull/push packets from/to the bridge?
This means you need to do adaptation of the LwIP Wireguard code, but at least this is solving the "netif"-to-"esp_netif_t" problem?

I'll look into it, thanks. My main gripe with the whole conversion thing was I don't want to have to go through the thousands of lines of wireguard C code I have in the lib to modify everything to be esp_netif_t. If there's a way to wrap it in the rust layer, i'm all for it, otherwise i'll bite the bullet.

Got it. But then I really don't know how easy it would be to wrap a netif to esp_netif_t. To do it you might really have to dig deep in the ESP netif C code.

Speculating: the other thing is, if you plan to have this device for commercial usage, it might be that this wireguard C code would anyway has to be changed or even completely rewritten (and then better in Rust I guess) if it is not fast enough.
Not sure what encryption algorithms Wireguard uses, but if they are compatible with the encryption hardware offered by the esp32* family, you might want to tap into those anyway and then that means tapping into the mbedtls lib that is distributed with ESP-IDF (as that's the easiest way to get hardware accel I think). Basically what this poster mentions: https://esp32.com/viewtopic.php?t=39520

@indexds
Copy link
Contributor Author

indexds commented Jan 1, 2025

Fair enough. And really a side topic. Risking the derail the conversation even further: there is this s3 chip which I think was able to act as a real USB host. Where I'm going with that is that maybe you can even do it without any schematics, by implementing the USB-to-Ethernet purely in software with that chip. But yes, side topic.

Do you still have the link for that? I'm very interested.

Got it. But then I really don't know how easy it would be to wrap a netif to esp_netif_t. To do it you might really have to dig deep in the ESP netif C code.

Yeah, that sounds like pain. Part of the reason I dread the first option not working.

Speculating: the other thing is, if you plan to have this device for commercial usage, it might be that this wireguard C code would anyway has to be changed or even completely rewritten (and then better in Rust I guess) if it is not fast enough.
Not sure what encryption algorithms Wireguard uses, but if they are compatible with the encryption hardware offered by the esp32* family, you might want to tap into those anyway and then that means tapping into the mbedtls lib that is distributed with ESP-IDF (as that's the easiest way to get hardware accel I think). Basically what this poster mentions: https://esp32.com/viewtopic.php?t=39520

Thanks I'll look at it. I'm a student though, this is all just a torture project thought up by my professor to rate us as either "passable" or blights upon humanity. So this isn't gonna be commercialized anytime soon if ever. And optimizing data flow rates is something I'll look at if I manage to make everything work seamlessly.

Because yeah, iirc, the lib I use for wireguard writes its own implementation of the chacha/poylwhatever crypto functions. So maybe hooking everything to mbedtls instead wouldn't be the worst idea. An idea for later.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 1, 2025

Fair enough. And really a side topic. Risking the derail the conversation even further: there is this s3 chip which I think was able to act as a real USB host. Where I'm going with that is that maybe you can even do it without any schematics, by implementing the USB-to-Ethernet purely in software with that chip. But yes, side topic.

Do you still have the link for that? I'm very interested.

Example with ethernet over usb: https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device/tusb_ncm

Thanks I'll look at it. I'm a student though, this is all just a torture project thought up by my professor to rate us as either "passable" or blights upon humanity. So this isn't gonna be commercialized anytime soon if ever. And optimizing data flow rates is something I'll look at if I manage to make everything work seamlessly.

Because yeah, iirc, the lib I use for wireguard writes its own implementation of the chacha/poylwhatever crypto functions. So maybe hooking everything to mbedtls instead wouldn't be the worst idea. An idea for later.

Fair enough!

@indexds
Copy link
Contributor Author

indexds commented Jan 2, 2025

Ok so this whole thing won't work. Because apparently this works purely on an L2 level (should've known, what with the name and all..) and mac stuff gets beheaded at the subnet boundary.

So back to figuring out how the hell nat works..

@indexds
Copy link
Contributor Author

indexds commented Jan 2, 2025

T.b.h. I'm not really sure where this "67.67.67.67" network you were mentioning earlier even fits into the picture? You only need two networks:

  • 10.0.0.1/30 - this is what your PC would see. This is where your "Esp" is the router. The "eth"
  • Some other network, couldn't care less what exactly, where you are a DHCP client and this network has internet access - this is what you get from the Wifi STA of the ESP and is possibly your home router network, which is "insecure" and where your PC should not connect (it connects to the 10.0.0.1/30 of the ESP)

That's it?

Yeah that was merely an exemple i used to illustrate the problem, it has no basis in reality.

Clear now.

@indexds
Hmmm

"something" "netif" by calling its "tx()" method. And the other way around for rx. <== THIS is unclear and this is where I don't think any bridging would help... :(

Actually... I take my words back. I'm no longer sure this will not work with bridging... Why wouldn't it and maybe you are right? Bridging the wg_netif + eth perhaps does exactly what you want, as this way the wg_netif would see each packet that comes via the eth, and will be able to "inject" new packets to the eth, which is maybe exactly what you want? Speculating. Anyway, on to your question:

Should be what I want. I think. The http server on the side would still see the request anyway right? The request would just be duplicated and also sent to the bridge which would then drop it or do whatever, which we don't care about. (I think.)

That's also exactly how I understand it (or rather - hope it would work).

Btw you can do the above purely with Wifi, as the Esp can operate simultaneously in an AP+STA mode. No need for Eth or anything like that.

I need eth because i'm building an usb key, I have a schematic that converts usb to eth frames that are interpreted by the esp32.

Fair enough. And really a side topic. Risking the derail the conversation even further: there is this s3 chip which I think was able to act as a real USB host. Where I'm going with that is that maybe you can even do it without any schematics, by implementing the USB-to-Ethernet purely in software with that chip. But yes, side topic.

This is a very heavyweight way to avoid converting netif to esp_netif_t but why don't you use this code (or similar):

pub struct EspNetifDriver<'d, T>

You just need to create a regular EspNetif, then wrap it with this driver, then bridge the wrapped EspNetif with the Eth, and then via the driver (EspNetifDriver) you have tx() rx() methods, so that you can pull/push packets from/to the bridge?
This means you need to do adaptation of the LwIP Wireguard code, but at least this is solving the "netif"-to-"esp_netif_t" problem?

I'll look into it, thanks. My main gripe with the whole conversion thing was I don't want to have to go through the thousands of lines of wireguard C code I have in the lib to modify everything to be esp_netif_t. If there's a way to wrap it in the rust layer, i'm all for it, otherwise i'll bite the bullet.

Got it. But then I really don't know how easy it would be to wrap a netif to esp_netif_t. To do it you might really have to dig deep in the ESP netif C code.

Speculating: the other thing is, if you plan to have this device for commercial usage, it might be that this wireguard C code would anyway has to be changed or even completely rewritten (and then better in Rust I guess) if it is not fast enough. Not sure what encryption algorithms Wireguard uses, but if they are compatible with the encryption hardware offered by the esp32* family, you might want to tap into those anyway and then that means tapping into the mbedtls lib that is distributed with ESP-IDF (as that's the easiest way to get hardware accel I think). Basically what this poster mentions: https://esp32.com/viewtopic.php?t=39520

If I can't get the "easy" nat implementation to work I'll give this a try.

@indexds
Copy link
Contributor Author

indexds commented Jan 3, 2025

Can you remind me how and in what context napt is supposed to be used here? You said I just had to enable it and then the netif would magically speak to each other but there has to be some assumed restrictions to this yes? It seems quite magical that they would do this without specific routes being set too..

And isn't napt supposed to be to communicate with the outside? why are we even using this to speak between subnets? I'm so deep into it I don't even remmeber why I started this

@indexds
Copy link
Contributor Author

indexds commented Jan 3, 2025

The problem was I need a way to move traffic from my 10.10.10.0/30 subnet that contains the PC and the esp's ETH netif to the let's say 50.50.50.0/30 subnet (completely random made up number we don't care) that contains my WG netif and the other guy's netif.

  • IEEE 82.1D bridge can't work
  • napt? I don't even know what's going on with this guy, probably won't work, also probably will annoy me with ports
  • your solution to make another netif act as a copy machine for packets between each netif, bypassing the ip routing problem: untested for now, will look at it in the next few days
  • static routing? unsupported it seems. this guy does it, but can I frankenstein my lwip that's being copied in by embuild? https://github.com/martin-ger/esp-open-lwip
  • ip forwarding? The original idea I had, but it seems the implementation does not allow for it. The original idea was to have a "second gateway" that the eth router netif would point to for any packets that reached it. The immediate alternative would be to have that netif be a client instead, which would definitely work, but then I would lose access to dhcp, having to maually set the gateway on my pc. This is not ideal, as the dongle is supposed to be plug&play once finished. Is there no way to do something in the middle? Can an eth "client" be flagged as dhcp server and assign itself and the pc their ips while still pointing its gateway to the wireguard netif?

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 3, 2025

The problem was I need a way to move traffic from my 10.10.10.0/30 subnet that contains the PC and the esp's ETH netif to the let's say 50.50.50.0/30 subnet (completely random made up number we don't care) that contains my WG netif and the other guy's netif.

  • IEEE 82.1D bridge can't work

That's what I thought initially and that's why I was constantly throwing at you the fact that such a bridge is an L2 thing, but I no longer think a bridge will NOT work. I now think it WILL work.

Imagine that you have:

  • Your eth (or whatever usb-based) Netif
  • A netif (and an EspNetifDriver connected to it) which is your "Wg" Netif.

Then you bridge these two ^^^ and assign to the bridge netif to be a Router with IP subnet 10.10.10.0/30 and have the 10.10.10.1 gateway.

What do you achieve this way? A lot, in my opinion:

  • The incoming packets from your PC will arrive in the bridge as it is the "router/gateway" for your PC

  • At the same time - and via the EspNetifDriver - you have a way to (a) pull packets from the bridge and (b) inject packets into the bridge!

  • When pulling packets from the bridge, you'll filter all packets which have dst_ip inside the 10.10.10.0 network, and you'll only care about packets with dst_ip != 10.10.10.0. What you'll do with these IP packets is to take them as a whole (as a &[u8]), encrypt them and then push them as the UDP payload via the UDP socket you have to your wg peer

  • For pushing packets into the bridge: you just poll the UDP socket to your wg peer, decrypt the next UDP packet, check that its content is an IP packet with dst_ip = 10.10.10.0/30 and if so, you inject the packet into the bridge using EspNetifDriver::tx. This way you actually do implement an encrypted routing between your PC and the remote wg peer.

@indexds
Copy link
Contributor Author

indexds commented Jan 3, 2025

So your idea is something like eth <-> bridge <-> wg netif but I don't understand why the bridge has the ip/subnet it does? Currently the idea is that my ethernet netif can serve an ip to the pc, that won't be possible with this design will it?

And so when we say bridge here we don't actually mean the weird IEEE L2 bridge provided by esp idf do we? It's just the term we use for "random netif we hijack to do our bidding" yeah?

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 4, 2025

So your idea is something like eth <-> bridge <-> wg netif but I don't understand why the bridge has the ip/subnet it does? Currently the idea is that my ethernet netif can serve an ip to the pc, that won't be possible with this design will it?

When you put multiple network interfaces in a bridge, they are assigned the same ip address and in fact all netifs in the bridge do get even the same mac. From the pov of the other network peers the bridge is a single entity. That's why bridges are l2.

And so when we say bridge here we don't actually mean the weird IEEE L2 bridge provided by esp idf do we?

That's exactly what I hope you can use. What is so weird about it?

@indexds
Copy link
Contributor Author

indexds commented Jan 4, 2025

Ok leaving that aside for a second, if I am to do any of the above, I need to be able to wrap my (*mut netif) into a (*mut esp_netif_t), and, ideally do better than that, which is to create the netif in rust directly using EspNetif::new_with_conf.
But I need to be able to access the underlying netif still to pass it to the wireguard code while we juggle packets in the rust layer.
Except it doesn't seem to be exported by bindgen at all.

struct esp_netif_obj {
    // default interface addresses
    uint8_t mac[NETIF_MAX_HWADDR_LEN];
    esp_netif_ip_info_t* ip_info;
    esp_netif_ip_info_t* ip_info_old;

    // lwip netif related
    struct netif *lwip_netif;
    err_t (*lwip_init_fn)(struct netif*);
    esp_netif_recv_ret_t (*lwip_input_fn)(void *input_netif_handle, void *buffer, size_t len, void *eb);
    void * netif_handle;    // netif impl context (either vanilla lwip-netif or ppp_pcb)
    netif_related_data_t *related_data; // holds additional data for specific netifs
#if ESP_DHCPS
    dhcps_t *dhcps;
#endif
    // io driver related
    void* driver_handle;
    esp_err_t (*driver_transmit)(void *h, void *buffer, size_t len);
    esp_err_t (*driver_transmit_wrap)(void *h, void *buffer, size_t len, void *pbuf);
    void (*driver_free_rx_buffer)(void *h, void* buffer);

    // dhcp related
    esp_netif_dhcp_status_t dhcpc_status;
    esp_netif_dhcp_status_t dhcps_status;
    bool timer_running;

    // event translation
    ip_event_t get_ip_event;
    ip_event_t lost_ip_event;

    // misc flags, types, keys, priority
    esp_netif_flags_t flags;
    char * hostname;
    char * if_key;
    char * if_desc;
    int route_prio;

#if CONFIG_ESP_NETIF_BRIDGE_EN
    // bridge configuration
    uint16_t max_fdb_dyn_entries;
    uint16_t max_fdb_sta_entries;
    uint8_t max_ports;
#endif // CONFIG_ESP_NETIF_BRIDGE_EN
    // mldv6 timer
    bool mldv6_report_timer_started;

#ifdef CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF
    ip_addr_t dns[DNS_MAX_SERVERS];
#endif
};

This is in components/esp_netif/lwip/esp_netif_lwip_internal.h; except what we see in the bindings.rs file is this:

#[doc = " @brief Type of esp_netif_object server"]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct esp_netif_obj {
    _unused: [u8; 0],
}
pub type esp_netif_t = esp_netif_obj;

That's it. And it gets it from components/esp_netif/include/esp_netif_types.h:

/** @brief Type of esp_netif_object server */
struct esp_netif_obj;

typedef struct esp_netif_obj esp_netif_t;

Is it just not importing any include files that aren't in the include folder of a component? In which case we have a big big problem because this is just not doable.

Or maybe I'm stupid. You tell me. I hope I'm stupid, this is painful enough as it is.

@indexds
Copy link
Contributor Author

indexds commented Jan 4, 2025

Ideally, I'd do something like &(*self.handle).netif and pass it to the unchanged wireguard code.

@indexds
Copy link
Contributor Author

indexds commented Jan 4, 2025

Welp, from what I've seen there's exactly 0 ways to do this except by extending the bindings ourselves, which is stupid, so I guess I'm modifying the entirety of the wireguard implementation so that it takes in esp_netif_t instead of the raw lwip netif, everywhere. That's going to take a while.

@indexds
Copy link
Contributor Author

indexds commented Jan 4, 2025

Actually I don't even know if it's possible. A lot of the functions used depend on having direct access to information that just isn't exposed with the wrapped esp_netif_t

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jan 5, 2025

Ok leaving that aside for a second, if I am to do any of the above, I need to be able to wrap my (*mut netif) into a (*mut esp_netif_t), and, ideally do better than that, which is to create the netif in rust directly using EspNetif::new_with_conf. But I need to be able to access the underlying netif still to pass it to the wireguard code while we juggle packets in the rust layer. Except it doesn't seem to be exported by bindgen at all.

I think I finally found an API to do something similar though the other way around. Read on.

Is it just not importing any include files that aren't in the include folder of a component? In which case we have a big big problem because this is just not doable.

Or maybe I'm stupid. You tell me. I hope I'm stupid, this is painful enough as it is.

The above is by design. Basically what you are observing is a C-style "visibility" trick utilized by ESP-IDF where they want to keep the layout of the esp_netif_t(obj) struct private, and thus they are exposing it as *void. No, you should NOT include the private header! :-)

Here's a link I found that should help get you started.
The API you are looking for is called esp_netif_get_netif_impl and it is in a header that - if not included in esp-idf-sys you can PR to be added: lwip/esp_netif_net_stack.h.

So this way you
(a) First create your custom esp_netif_t instance
(b) Put it in a bridge with eth
(c) With the help of the above magic function, you can get the underlying raw LwIP "netif" struct from your esp_netif_t and hopefully pass it down to a (slightly modified) LwIP wireguard impl.

But I think you need to modify the LwIP Wireguard impl a bit as it thinks it can "manually" create a raw LwIP netif, then wrap it as esp_netif_t and work with both, while it should now accept an already-created raw LwIP netif. However if you look around in the headers I pasted you might even see a way to create an esp_netif_t from raw lwIP netif, that I'm missing.

@indexds
Copy link
Contributor Author

indexds commented Jan 5, 2025

But I think you need to modify the LwIP Wireguard impl a bit as it thinks it can "manually" create a raw LwIP netif, then wrap it as esp_netif_t and work with both, while it should now accept an already-created raw LwIP netif.

Yeah that's what I had in mind as well.

I checked and we don't have that function, only those two

pub fn esp_netif_get_netif_impl_name(esp_netif: *mut esp_netif_t, name: *mut ::core::ffi::c_char) -> esp_err_t;
pub fn esp_netif_get_netif_impl_index(esp_netif: *mut esp_netif_t) -> ::core::ffi::c_int;

I'll try to make a PR for esp-idf-sys, we'll see where this goes. Thanks again for the help, I probably wouldn't be able to figure this out alone.

@indexds
Copy link
Contributor Author

indexds commented Jan 8, 2025

But how do I create a (wrapped) wireguard interface? It requires a stack that I can't specify because the options don't fit having a virtual interface. This wasn't a problem with a raw lwip interface since all I needed was an ip configuration.

pub struct esp_netif_config {
    #[doc = "< base config"]
    pub base: *const esp_netif_inherent_config_t,
    #[doc = "< driver config"]
    pub driver: *const esp_netif_driver_ifconfig_t,
    #[doc = "< stack config"]
    pub stack: *const esp_netif_netstack_config_t,
}
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_eth;
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_br;
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_wifi_sta;
#ifdef CONFIG_ESP_WIFI_SOFTAP_SUPPORT
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_wifi_ap;
#endif
#ifdef CONFIG_ESP_WIFI_NAN_ENABLE
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_wifi_nan;
#endif
#ifdef CONFIG_PPP_SUPPORT
extern const esp_netif_netstack_config_t *_g_esp_netif_netstack_default_ppp;
#endif

Do I need to write the functions myself? That's gonna be a huge pain I have no clue how to do this.

@indexds
Copy link
Contributor Author

indexds commented Jan 8, 2025

Especially because sifting through the bridge code in lwip, I find this:

  if (!(portif->flags & NETIF_FLAG_ETHARP) || !(portif->flags & NETIF_FLAG_ETHERNET)) {
    /* can only add ETHERNET/ETHARP interfaces */
    return ERR_VAL;
  }

So unless I specify Netstack::Eth, I'll be in big big trouble. Thus my question is more the following: Will there be issues if two interfaces (Ethernet and Wireguard) use the same netstack (Eth)?

@indexds
Copy link
Contributor Author

indexds commented Jan 8, 2025

Well. This thread talks about it espressif/esp-idf#13600. I'll look into that.

@indexds
Copy link
Contributor Author

indexds commented Jan 8, 2025

Okay so I was stupid I already have the functions necessary to create the netstack.

In esp_wireguard.c:

    wg_netif = netif_add(
            &wg_netif_struct,
            ip_2_ip4(&ip_addr),
            ip_2_ip4(&netmask),
            ip_2_ip4(&gateway),
            &wg, &wireguardif_init,
            &ip_input);

So if I just do

static const struct esp_netif_netstack_config mysuperduperconfig = {
        .lwip = {
            .init_fn = wireguardif_init,
            .input_fn = ip_input,
        }
};

While not forgetting to expose that config so that the rust layer can see it, then we might have something workable there...

@indexds
Copy link
Contributor Author

indexds commented Jan 8, 2025

So this isn't quite working like I would want it to, as the above mentioned config have these types:

typedef err_t (*init_fn_t)(struct netif*);
typedef esp_netif_recv_ret_t (*input_fn_t)(void *netif, void *buffer, size_t len, void *eb);

struct esp_netif_netstack_lwip_vanilla_config {
    init_fn_t init_fn;
    input_fn_t input_fn;
};

While netif_add uses these function types:

/** Function prototype for netif init functions. Set up flags and output/linkoutput
 * callback functions in this function.
 *
 * @param netif The netif to initialize
 */
typedef err_t (*netif_init_fn)(struct netif *netif);
/** Function prototype for netif->input functions. This function is saved as 'input'
 * callback function in the netif struct. Call it when a packet has been received.
 *
 * @param p The received packet, copied into a pbuf
 * @param inp The netif which received the packet
 * @return ERR_OK if the packet was handled
 *         != ERR_OK is the packet was NOT handled, in this case, the caller has
 *                   to free the pbuf
 */
typedef err_t (*netif_input_fn)(struct pbuf *p, struct netif *inp);

The init function seems transparent enough, so I think I can use wireguardif_init without problem, but what about my ip_input?

@indexds
Copy link
Contributor Author

indexds commented Jan 9, 2025

Isn't there a way to make this easier for myself? The wiki says this:

If you add a netif that should be considered the "default" interface, then you should next call netif_set_default for that netif. When the IP stack tries to route the packet, if it cannot determine the right interface to use, it will send it to the default interface.

The wireguard interface sets itself as the default interface, so shouldn't all traffic from ethernet naturally be forwarded to the wg netif by default without any intervention on my part?

Yet,

D (17:59:47.350) lwip: ip4_input: UDP packet to DHCP client port 53

D (17:59:47.356) lwip: ip4_input: packet not for us.

D (17:59:47.361) lwip: ip4_route: No route to 1.0.0.1

D (17:59:47.367) lwip: ip4_forward: no forwarding route for 1.0.0.1 found

So either there is something I fundamentally misunderstand about forwarding or there's a bug somewhere.

@indexds
Copy link
Contributor Author

indexds commented Jan 12, 2025

Isn't there a way to make this easier for myself? The wiki says this:

If you add a netif that should be considered the "default" interface, then you should next call netif_set_default for that netif. When the IP stack tries to route the packet, if it cannot determine the right interface to use, it will send it to the default interface.

The wireguard interface sets itself as the default interface, so shouldn't all traffic from ethernet naturally be forwarded to the wg netif by default without any intervention on my part?

Yet,

D (17:59:47.350) lwip: ip4_input: UDP packet to DHCP client port 53

D (17:59:47.356) lwip: ip4_input: packet not for us.

D (17:59:47.361) lwip: ip4_route: No route to 1.0.0.1

D (17:59:47.367) lwip: ip4_forward: no forwarding route for 1.0.0.1 found

So either there is something I fundamentally misunderstand about forwarding or there's a bug somewhere.

Ok so, having abandoned everything else as too complicated, I managed to get this to work somewhat.

The problem was I was setting the wg_netif's ip to 0.0.0.0 and ip_forward checks that the ip is not 0.0.0.0 to send the packets to the default_netif. So going from PC -> ETH NETIF -> WG NETIF -> STA -> WG ENDPOINT works. But the other way doesn't because it stops at the WG NETIF. The packet gets decrypted and then doesn't know where to go so it's dropped. I'm having the exact same problem in reverse except this time I can't use the default netif stratagem.

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

No branches or pull requests

2 participants