Skip to content

Instantly share code, notes, and snippets.

@jasonacox
Last active January 6, 2025 06:56
Show Gist options
  • Save jasonacox/91479957d0605248d7eadb919585616c to your computer and use it in GitHub Desktop.
Save jasonacox/91479957d0605248d7eadb919585616c to your computer and use it in GitHub Desktop.
Set up RaspberryPi as Network Router to Powerwall Gateway

RaspberryPi - Powerwall Router

This will set up a Raspberry Pi to connect to a Tesla Powerwall Gateway (TEG) and bridge that connection to the ethernet connected LAN.

Network Configuration

 ___________________          __________________________           _______________
[ Powerwall Gateway ]        [      Raspberry Pi        ]         [      Host     ]
[       TEG         ]  WiFi  [__________________________]   LAN   [ Linux/Mac/Win ]
[   WiFi: TEG-xxx   ] <----  [ 192.168.91.x | 10.0.1.55 ] <-----> [   10.0.1.65   ]
[   192.168.91.1    ]        [  WiFi (dhcp) |  Ethernet ]         [      LAN      ]
 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾          ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾           ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 

Raspberry Pi

  1. Create or edit /etc/wpa_supplicant/wpa_supplicant.conf:
network={
        ssid="TEG-xxx"
        psk="password"
}
  1. Restart Networking and Test
sudo systemctl restart networking

# Test
ifconfig wlan0
ping -c 1 192.168.91.1
  1. Set up IPv4 Routing and Reboot
# Add IP Forwarding - Uncomment net.ipv4.ip_forward=1
sudo sed -i -e '/^#net\.ipv4\.ip_forward=1/s/^#//' /etc/sysctl.conf
sudo sysctl -w net.ipv4.ip_forward=1

# Restart
sudo reboot

Host

On the host, you need to add a route to use the Raspberry Pi as a gateway to get to the Powerwall Gateway.

# Linux
sudo ip r add 192.168.91.0/24 via 10.0.1.55

# MacOS
sudo route add -host 192.168.91.1 10.0.1.55

# Test
ping -c 1 192.168.91.1
curl -ik https://192.168.91.1
@Nexarian
Copy link

Nexarian commented Dec 25, 2024

Any idea how to get this to work for TWO inverters? I've tried many things with NAT, but no luck.

Obviously, the dumb answer is:

user@raspberrypi:~ $ sudo ip route add 192.168.91.0/24 via 192.168.1.250 dev eth0
user@raspberrypi:~ $ sudo ip route add 192.168.91.0/24 via 192.168.1.67 dev eth0
RTNETLINK answers: File exists

This is the script I'm attempting:

user@raspberrypi:~ $ ./ip-routing.sh 
++ INVERTER1_VIRTUAL_IP=192.168.92.100
++ INVERTER2_VIRTUAL_IP=192.168.92.101
++ INVERTER1_REAL_IP=192.168.91.1
++ INVERTER2_REAL_IP=192.168.91.1
++ INVERTER1_GATEWAY=192.168.1.250
++ INVERTER2_GATEWAY=192.168.1.67
+++ ip route
+++ grep default
+++ awk '{print $5}'
++ INTERFACE=eth0
++ TABLE1=inverter1
++ TABLE2=inverter2
++ TABLE1_NUM=101
++ TABLE2_NUM=102
++ sudo iptables -t nat -F OUTPUT
++ sudo iptables -t nat -F PREROUTING
++ sudo iptables -t nat -F POSTROUTING
++ sudo iptables -t mangle -F OUTPUT
++ sudo iptables -t mangle -F PREROUTING
++ sudo iptables -t mangle -F POSTROUTING
++ echo 'Removing existing static routes and policy rules...'
Removing existing static routes and policy rules...
++ sudo ip route flush table 101
Error: ipv4: FIB table does not exist.
Flush terminated
++ true
++ sudo ip route flush table 102
Error: ipv4: FIB table does not exist.
Flush terminated
++ true
++ sudo ip rule del fwmark 101 lookup 101
++ true
++ sudo ip rule del fwmark 102 lookup 102
++ true
++ sudo ip rule del from 192.168.92.100 lookup inverter1
++ echo 'No policy rule for inverter1.'
No policy rule for inverter1.
++ sudo ip rule del from 192.168.92.101 lookup inverter2
++ echo 'No policy rule for inverter2.'
No policy rule for inverter2.
++ sudo ip addr del 192.168.92.100/32 dev eth0
Error: ipv4: Address not found.
++ true
++ sudo ip addr del 192.168.92.101/32 dev eth0
Error: ipv4: Address not found.
++ true
++ sudo sed -i /inverter1/d /etc/iproute2/rt_tables
++ sudo sed -i /inverter2/d /etc/iproute2/rt_tables
++ sudo ip addr add 192.168.92.100/32 dev eth0
++ sudo ip addr add 192.168.92.101/32 dev eth0
++ echo 'Configuring custom routing tables...'
Configuring custom routing tables...
++ echo '101 inverter1'
++ sudo tee -a /etc/iproute2/rt_tables
101 inverter1
++ echo '102 inverter2'
++ sudo tee -a /etc/iproute2/rt_tables
102 inverter2
++ echo 'Adding static routes for each inverter...'
Adding static routes for each inverter...
++ sudo ip route add 192.168.91.0/24 via 192.168.1.250 dev eth0 onlink table 101
++ sudo ip route add 192.168.91.0/24 via 192.168.1.67 dev eth0 onlink table 102
++ echo 'Setting up policy routing...'
Setting up policy routing...
++ sudo ip rule add from 192.168.92.100 lookup 101
++ sudo ip rule add from 192.168.92.101 lookup 102
++ echo 'Setting up iptables packet marking rules...'
Setting up iptables packet marking rules...
++ sudo iptables -t mangle -A PREROUTING -d 192.168.92.100 -j MARK --set-mark 101
++ sudo iptables -t mangle -A PREROUTING -d 192.168.92.101 -j MARK --set-mark 102
++ sudo iptables -t mangle -A OUTPUT -d 192.168.92.100 -j MARK --set-mark 101
++ sudo iptables -t mangle -A OUTPUT -d 192.168.92.101 -j MARK --set-mark 102
++ echo 'Setting up policy routing rules...'
Setting up policy routing rules...
++ sudo ip rule add fwmark 101 lookup 101
++ sudo ip rule add fwmark 102 lookup 102
++ echo 'Setting up DNAT rules...'
Setting up DNAT rules...
++ sudo iptables -t nat -A OUTPUT -m mark --mark 101 -d 192.168.92.100 -p tcp --dport 443 -j DNAT --to-destination 192.168.91.1
++ sudo iptables -t nat -A OUTPUT -m mark --mark 102 -d 192.168.92.101 -p tcp --dport 443 -j DNAT --to-destination 192.168.91.1
++ sudo iptables -t nat -A OUTPUT -m mark --mark 101 -d 192.168.92.100 -p tcp --dport 80 -j DNAT --to-destination 192.168.91.1
++ sudo iptables -t nat -A OUTPUT -m mark --mark 102 -d 192.168.92.101 -p tcp --dport 80 -j DNAT --to-destination 192.168.91.1
++ echo 'Setting up SNAT rules...'
Setting up SNAT rules...
++ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
++ sudo sysctl -w net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.all.rp_filter = 0
++ sudo sysctl -w net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.default.rp_filter = 0
++ sudo sysctl -w net.ipv4.conf.eth0.rp_filter=0
net.ipv4.conf.eth0.rp_filter = 0
++ echo 'Displaying current configuration for verification...'
Displaying current configuration for verification...
++ sudo iptables -t nat -L -n -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DNAT       6    --  *      *       0.0.0.0/0            192.168.92.100       mark match 0x65 tcp dpt:443 to:192.168.91.1
    0     0 DNAT       6    --  *      *       0.0.0.0/0            192.168.92.101       mark match 0x66 tcp dpt:443 to:192.168.91.1
    0     0 DNAT       6    --  *      *       0.0.0.0/0            192.168.92.100       mark match 0x65 tcp dpt:80 to:192.168.91.1
    0     0 DNAT       6    --  *      *       0.0.0.0/0            192.168.92.101       mark match 0x66 tcp dpt:80 to:192.168.91.1

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
++ sudo iptables -t mangle -L -n -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MARK       0    --  *      *       0.0.0.0/0            192.168.92.100       MARK set 0x65
    0     0 MARK       0    --  *      *       0.0.0.0/0            192.168.92.101       MARK set 0x66

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MARK       0    --  *      *       0.0.0.0/0            192.168.92.100       MARK set 0x65
    0     0 MARK       0    --  *      *       0.0.0.0/0            192.168.92.101       MARK set 0x66

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
++ sudo ip rule show
0:      from all lookup local
32762:  from all fwmark 0x66 lookup inverter2
32763:  from all fwmark 0x65 lookup inverter1
32764:  from 192.168.92.101 lookup inverter2
32765:  from 192.168.92.100 lookup inverter1
32766:  from all lookup main
32767:  from all lookup default
++ sudo ip route show table 101
192.168.91.0/24 via 192.168.1.250 dev eth0 onlink 
++ sudo ip route show table 102
192.168.91.0/24 via 192.168.1.67 dev eth0 onlin

@jasonacox
Copy link
Author

I wonder if there is a way to do this with virtual interfaces (e.g. eth0:1 to one and eth0:2 to the other)? I'm not anywhere close to a network guru, but hopefully someone in the community is.

@Nexarian
Copy link

@jasonacox Virtual interfaces might work, but I was trying to avoid needing to trigger a large-scale refactor of every use of requests.get or requests.post in all of pypowerwall/dashboard. Ergo: I was simply hoping I could create a virtual IP (like 192.168.92.100/101) and then simply redirect pypowerwall to that using the host parameter.

If you're open to that (adding the parameters that set the interface and/or the source address to the system) that is probably MORE likely to work, but it's a much bigger refactor than the simple ones I've done thus far.

@Nexarian
Copy link

Nexarian commented Jan 5, 2025

@jasonacox I have a fix for this.

Status

The script is still a bit rough/immature. I'm working on figuring out how to make it better and/or integrate it into the server. I also have other variations that work using network namespaces and virtual interfaces, but those seem unnecessary.

Summary

Essentially: Instead of calling 192.168.91.1 directly, set the host in pypowerwall to one of [192.168.92.100, 192.168.92.101] (Subnet choice is arbitrary). Currently my inverters are at [192.168.1.67 , 192.168.92.250] and my raspberry pi is at 192.168.1.225

Steps

  1. Create virtual IP address
  2. Add static route in a unique table
  3. Add DNAT destination rerouting to convert target IP to Tesla internal IP in the OUTPUT (NOT PREROUTING) table.
  4. Add a mangle table entry to MARK anything coming from the virtual IP
  5. Add a rule that anything marked should use the unique table above.
  6. Finally, add a postrouting rule so that this crazy request can find its way back.

This is the reason for this fix that I posted earlier today. Most of TEDAPI worked except firmware version retrieval!

I will update my other PR with this.

Code

Routing Configuration
#!/bin/bash

set -ex

# Function to check if a command succeeded
check_command() {
    if [ $? -ne 0 ]; then
        echo "Error: $1"
        exit 1
    fi
}

# Function to clean up existing rules and configurations
cleanup() {
    echo "Cleaning up existing rules and configurations..."
    
    # Remove virtual IP addresses
    ip addr del 192.168.92.100/24 dev eth0 2>/dev/null || true
    ip addr del 192.168.92.101/24 dev eth0 2>/dev/null || true

    # Flush NAT table
    iptables -t nat -F
    check_command "Failed to flush NAT table"

    # Flush mangle table
    iptables -t mangle -F
    check_command "Failed to flush mangle table"

    # Remove all rules in filter table
    iptables -F
    check_command "Failed to flush filter table"

    # Remove non-default chains
    iptables -X
    check_command "Failed to delete non-default chains"

    # Remove all ip rules (except default)
    ip rule show | grep -v "from all lookup" | cut -d: -f1 | xargs -r -n1 ip rule del prio
    check_command "Failed to remove ip rules"

    # Remove routing tables
    ip route flush table 100 || true
    ip route flush table 101 || true
    check_command "Failed to flush routing tables"

    echo "Cleanup completed successfully."
}

# Main setup function
setup() {
    echo "Setting up routing and NAT rules..."

    # Enable IP forwarding
    echo 1 > /proc/sys/net/ipv4/ip_forward
    check_command "Failed to enable IP forwarding"

    # Add virtual IP addresses
    ip addr add 192.168.92.100/24 dev eth0
    check_command "Failed to add virtual IP 192.168.92.100"
    ip addr add 192.168.92.101/24 dev eth0
    check_command "Failed to add virtual IP 192.168.92.101"

    # Set up routing tables
    ip route add 192.168.91.0/24 via 192.168.1.67 dev eth0 onlink table 100
    check_command "Failed to add route to table 100"
    ip route add 192.168.91.0/24 via 192.168.1.250 dev eth0 onlink table 101
    check_command "Failed to add route to table 101"

    # Set up NAT rules
    iptables -t nat -A OUTPUT -d 192.168.92.100 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.100"
    iptables -t nat -A OUTPUT -d 192.168.92.101 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.101"

    iptables -t nat -A PREROUTING -d 192.168.92.100 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.100"
    iptables -t nat -A PREROUTING -d 192.168.92.101 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.101"

    # Set up packet marking
    iptables -t mangle -A OUTPUT -d 192.168.92.100 -j MARK --set-mark 1
    check_command "Failed to add mark for 192.168.92.100"
    iptables -t mangle -A OUTPUT -d 192.168.92.101 -j MARK --set-mark 2
    check_command "Failed to add mark for 192.168.92.101"

    # Set up ip rules
    ip rule add fwmark 1 table 100
    check_command "Failed to add ip rule for mark 1"
    ip rule add fwmark 2 table 101
    check_command "Failed to add ip rule for mark 2"

    # Set up return traffic NAT
    iptables -t nat -A POSTROUTING -s 192.168.92.100 -d 192.168.91.1 -j SNAT --to-source 192.168.1.225
    check_command "Failed to add return SNAT rule for 192.168.92.100"
    iptables -t nat -A POSTROUTING -s 192.168.92.101 -d 192.168.91.1 -j SNAT --to-source 192.168.1.225
    check_command "Failed to add return SNAT rule for 192.168.92.101"

    echo "Setup completed successfully."
}

# Main execution
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 
   exit 1
fi

# Cleanup first
cleanup

# Then setup
setup

echo "All operations completed successfully."

@jasonacox
Copy link
Author

Thanks @Nexarian !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment