gtxaspec / wz_mini_hacks

wz camera mods... make your camera better.
1.2k stars 101 forks source link

Ability to set a static IP address on device (not DHCP reservation) #768

Open sfrappier opened 3 weeks ago

sfrappier commented 3 weeks ago

Thank you for the awesome solution - I really appreciate all the work that has been tackled.

I'd like to understand the ability or manner in which you configure a camera to use a static IP address set or assigned upon boot and persisted over time. I have an environment in which I do not allow for DHCP leases for my cameras. There are a multitude of reasons as to why someone would like to do this, but I have never seen a documented way to tackle this with mini hacks.

I have tried the script path in rc.local.d to run ifconfig to manually assign at address. This works for a time as eventually DHCP runs and a lease is found (DHCP does run but only generally with phones and other devices) and it resets the IP address config and I loose the static assignment.

Does someone here understand the DHCP startup process, and what config files may be needed (and startup scripts) in order to add a small feature to conditionally set a static IP if defined in the config file? Basically, if static IP set to true then run ifconfig to assign along with route add for the default gateway if defined...

I'd be more than happy to port this back but just need some guidance and understanding as to why it hasn't been implemented...Dafang and most other cameras I've worked with have had this ability and it's always a shortcoming of the Wyze products that I was hoping mini would address.

Let me know if there is a better forum for this request.

wes58 commented 3 weeks ago

Why don't you assign static IP address for your cameras in the configuration in your modem/router - set static address for a specific mac address. That's where IP addresses are assigned when the cameras connect to WiFi.

gtxaspec commented 3 weeks ago

the issue with dhcp is that the camera streamer, iCamera, still handles networking. if we set a static ip, it will kill and restart the dhcp client.

wes58 commented 3 weeks ago

I am not sure what iCamera is. Is it IOS app? I am using Android Tiny Cam Pro on the phone and AnyCam on the PC for RTSP stream and don't have any issues. I don't understand how you have your camera's connected. I connect mine via WiFi to the modem/router. Nothing can change the static IP address that is set there. DHCP client is on the modem. I also use no-ip to get access to the cameras from the outside of the house. So I am not why do you have any issues.

virmaior commented 3 weeks ago

@wes58 iCamera is an application running on the WyzeCamera -- it's the center of how the wyze camera works.

setting static IPs on the device is bad design (but that doesn't mean there's never a reason to do so). given what gtxaspec is saying, you would need to trick the device by capturing the DHCP request and giving the response locally.

I'm not really sure how you would effectively do that in busybox

wes58 commented 3 weeks ago

Sorry, I remembered now. I thought that it was a IOS app because there is an IOS app that is called iCamera - i think. Your topic title is - Ability to set static ip address. And that's what I have. Static IP address assigned to a camera mac address in the router. If you camera connects to the modem/router via WiFi, what assigns IP address to the camera? The DHCP on the router. Anyway, if it is not what you are looking for, I can't help you.

gtxaspec commented 3 weeks ago

@sfrappier the right solution would be modifying libcallback, to intercept wherever udhcpc is called via the libary & LD_PRELOAD, and stop those calls. you'd have to look at the decompiled code closely as udhcpc is called a lot during a live system for some reason. I don't remember if iCamera calls it specifically, or if sinker, or some other program, or a combination of them do.

or, recompile busybox without udhcpc, create a dummy script in it's place, and hope iCamera likes it =D

take your pick!

virmaior commented 3 weeks ago

@wes58 I'm not the OP; I confusedly thought you were re: your question about iCamera...

I think what OP wants is to set it statically on the device itself -- whereas what you're saying is the generally better way to do this (assigning statically at the DHCP server).

sfrappier commented 3 weeks ago

Why don't you assign static IP address for your cameras in the configuration in your modem/router - set static address for a specific mac address. That's where IP addresses are assigned when the cameras connect to WiFi.

Because I need more granular control of my cameras. They run on a different IP address range and on a separate VLAN. All home automation devices on my network are statically set (I have over 200 wifi home automation devices - all with static IPs).

The primary reason - home bootup and maintenance. DHCP introduces a complexity that I don't need for home devices. If a camera, switch, or other home device has power I want to also guarantee it has an IP address. It's really as simple as that. I get that some like to manage via DHCP but if you've ever had a power outage with a large amount of devices, you'll find out very quickly that the best way to control order of operations is to eliminate DHCP as it pretty much guarantees address assignment. I tried the DHCP route and ultimately only now use DHCP for tablets, phones, and computers, but I would never want to do this with my furnace or cameras or switches.

There's also the Wyze bug in which they mask the hardware MAC and then use a software MAC and occasionally the mask is dropped. Try putting in 2 MAC addresses for one IP address in your DHCP server assignment...it's not fun.

I was hoping that since there is startup control that we'd be able to set that address via ifconfig. There are network startup scripts that I see when I SSH to the camera. Are these used to start the DHCP client on the camera? I saw some comments regarding some decompilation needs, but I guess I didn't think this was hard coded into the iCamera executable.

I can manually almost get my result - if I SSH to the camera I can use ifconfig and route to add a static IP and gateway. I cannot persist it though as it seems to reset at some point.

I hope this makes sense - I want to set it statically on the camera - not in a DHCP server config. I can almost accomplish it via ifconfig but I thought that maybe a startup scripts in rc.d needed to be updated...but it sounds like this is not possible?

sfrappier commented 3 weeks ago

Sorry, I remembered now. I thought that it was a IOS app because there is an IOS app that is called iCamera - i think. Your topic title is - Ability to set static ip address. And that's what I have. Static IP address assigned to a camera mac address in the router. If you camera connects to the modem/router via WiFi, what assigns IP address to the camera? The DHCP on the router. Anyway, if it is not what you are looking for, I can't help you.

I appreciate the reply - I added some more insight in my prior post. I would like to set this at the device and not at the DHCP server per reasons listed above.

endertable commented 3 weeks ago

@sfrappier you are on the right track of assigning using a script at startup but then continue as a cron or rather a daemon to monitor the change of IP address and re-run ifconfig with your settings. As @virmaior and @gtxaspec have both noted, these cameras are aggressive on maintaining network connectivity, after all, they are cloud cams. With that, if assis or iCamera sense any issues with the net or minimum requirements of a healthy cam, restarting the network is in all/most of the troubleshooting functions or at least in the 4054 FW version. I would guess all the older FW, including 139, had this as well. That network reset function kills all current networking daemons, then restarts them. It monitors and continues to reset until all its minimum stability requirements are satisfied. A bogus clinched udhcpd, as @gtxaspec mentioned, will probably need to be in your solution. Maybe even use that as the script that runs your static IP routines instead of a daemon since its going to get called on every reset along with wpa_client.

Rahzadan commented 2 weeks ago

Would also love this feature. I statically assign the IPs of all my cameras as well. The only network device the cameras can see is my NVR (which has 2 nics.. 1 on camera VLAN, other on my main VLAN so I can monitor!) they are on a completely separate VLAN (just like @sfrappier)

sfrappier commented 1 week ago

I took my first stab at creating a monitoring script that resides in rc.local.d - I will refine this over time (there are some udhcp configs that I want to look at to see if there is a way to add an escape so that udhcp doesn't update the interface - it's in /usr/share and it looks like there's some type of control script there...?).

Here's the rc.local.d script:

#!/bin/sh

# Path to the configuration file
CONFIG_FILE="/opt/wz_mini/etc/staticip.conf"

# Log file path
LOG_FILE="/var/log/staticip_monitor.log"

# Function to write log entries only if logging is enabled
log_event() {
    if [ "$STATIC_IP_LOGGING" = "true" ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
    fi
}

# Function to read the configuration file
read_config() {
    . "$CONFIG_FILE"
    IP_ADDR="$IP_ADDRESS"
    SUBNET_MASK="$SUBNET"
    GATEWAY="$DEFAULT_GATEWAY"
    ENABLE_STATIC_IP="$ENABLE_STATIC_IP"
    STATIC_IP_LOGGING="$STATIC_IP_LOGGING"
}

# Function to log unforeseen errors
error_handler() {
    local error_message="$1"
    log_event "ERROR: $error_message"
}

# Trap errors and signals
trap 'error_handler "An unforeseen error occurred. Exiting."' ERR INT TERM

# Initialize logging
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"

# Read the configuration file early
read_config

# Exit if static IP assignment is disabled
if [ "$ENABLE_STATIC_IP" = "false" ]; then
    log_event "Static IP assignment is disabled. Exiting script."
    exit 0
fi

# Reset the network interface by flushing all IP address details
reset_interface() {
    INTERFACE="wlan0"  # Replace wlan0 with your network interface
    FLUSH_OUTPUT=$(busybox ip addr flush dev "$INTERFACE" 2>&1)
    if [ $? -ne 0 ]; then
        error_handler "Failed to clear IP address details for interface $INTERFACE: $FLUSH_OUTPUT"
        return 1
    fi
    log_event "Cleared all IP address details for interface $INTERFACE."
}

# Configure network settings based on the configuration file
configure_network() {
    read_config
    INTERFACE="wlan0"  # Replace with your specific network interface
    if ! reset_interface; then
        error_handler "Reset interface $INTERFACE failed."
        return 1
    fi
    if ! ifconfig "$INTERFACE" "$IP_ADDR" netmask "$SUBNET_MASK" up; then
        error_handler "Failed to configure IP $IP_ADDR on interface $INTERFACE."
        return 1
    fi
    if ! route add default gw "$GATEWAY" "$INTERFACE"; then
        error_handler "Failed to set default gateway $GATEWAY on interface $INTERFACE."
        return 1
    fi
    log_event "Network configured with IP $IP_ADDR, Subnet $SUBNET_MASK, and Gateway $GATEWAY."
}

# Get the current IP address of the network interface
get_current_ip() {
    busybox ip -4 addr show dev "$INTERFACE" | grep -o 'inet [0-9.]\+' | awk '{print $2}'
}

# Monitor and maintain the configured IP address
monitor_ip() {
    while true; do
        CURRENT_IP=$(get_current_ip)
        if [ "$CURRENT_IP" != "$IP_ADDR" ]; then
            log_event "Current IP ($CURRENT_IP) does not match configured IP ($IP_ADDR). Reconfiguring network."
            configure_network
        #else
        #    log_event "Current IP ($CURRENT_IP) matches configured IP ($IP_ADDR)."
        fi
        sleep 1  # Check every second
    done
}

# Start monitoring
monitor_ip

And here is the configuration file - I created a separate one for debugging but adding it to the existing master config would be fairly easy:

# Enable or disable static IP monitoring
ENABLE_STATIC_IP=true  # Set to "false" to disable the script

# Enable or disable logging of events
STATIC_IP_LOGGING=true  # Set to "false" to disable logging

# Static IP configuration details
IP_ADDRESS="192.168.242.150"
SUBNET="255.255.0.0"
DEFAULT_GATEWAY="192.168.1.1"

I'm running this on one of my cameras already and it seems to be working - I'm going to test it a bit further and see if there's something in the udhcp control script that I can update as there is lease management that I'm seeing in the script - does anyone know if the "default.script" is called in the /usr/share/udhcpc/ by udhcp or by iCamera?

Either way, small steps...

endertable commented 1 week ago

Hi @sfrappier , that "default.script" belongs to and is called by udhcpc. "There are a million ways to skin a cat". Thanks for providing one of the ways to the community for anyone else that may need a specific/custom function for a static IP. Out of curiosity more than anything, have you checked the log files to see on average how often your script has to reset the IP back to static?

sfrappier commented 1 week ago

Hi @sfrappier , that "default.script" belongs to and is called by udhcpc. "There are a million ways to skin a cat". Thanks for providing one of the ways to the community for anyone else that may need a specific/custom function for a static IP. Out of curiosity more than anything, have you checked the log files to see on average how often your script has to reset the IP back to static?

Thanks @endertable! I think I'm going to look at a way to intercept that file - I've been looking into how other files are replaced, like fstab, in this design and I think I can make a cleaner script that udhcpc calls, and if static assignment is set in config, it does the IP/subnet/gateway/nameservers defined. The reason I asked about the script was because I noticed it has all the interface assignments in that script.

I did capture how many times it calls - basically every hour. The shortcoming of this script is that the system does technically do a DHCP lease request to the server, but this script then clears the assignment to the interface but doesn't release the lease on the DHCP server. My guess is even with the update to the default.script that this behavior would still occur, but on the device itself it would never assign that IP info to the interface.

As @gtxaspec mentioned, the right way will probably be to make it so that if iCamera calls udhcpc that this is intercepted for static assignment.

One last note - I am experimenting with a version of this script that checks to see if udhcpc is running and then kills that process. This is working so far on my cameras and it is not restarting udhcpc. This has eliminated the hourly flip-flop assignment as well, and gets me closer - I'll post that with an update after I've tested it a bit more.

I appreciate all of the info all of you have provided - ty again for the guidance!

sfrappier commented 1 week ago

Hey all - as I was testing the script with a Wyze Cam v3 I started to run into some issues. It turns out that the v3 build doesn't look to have a /var/log directory that exists on the v2's.

The v2 build I was using was 4.9.8.1002. The v3 build I was using was 4.36.9.139.

Updating the script to save the log file to the /tmp/ directory on the v3's fixed the issue. In the script at the top you can see the definition for the config and log file - just make sure to update it if you use the script or it will get stuck and not do IP address assignment if you have enabled logging.

sfrappier commented 1 week ago

Here's a version of the script that points to the /tmp/ directory and also has the udhcpc kill built-in so that the camera does not try to renew a lease:

#!/bin/sh

# Path to the configuration file
CONFIG_FILE="/opt/wz_mini/etc/staticip.conf"

# Log file path
LOG_FILE="/tmp/staticip_monitor.log"

# Function to write log entries only if logging is enabled
log_event() {
    if [ "$STATIC_IP_LOGGING" = "true" ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
    fi
}

# Function to read the configuration file
read_config() {
    . "$CONFIG_FILE"
    IP_ADDR="$IP_ADDRESS"
    SUBNET_MASK="$SUBNET"
    GATEWAY="$DEFAULT_GATEWAY"
    ENABLE_STATIC_IP="$ENABLE_STATIC_IP"
    STATIC_IP_LOGGING="$STATIC_IP_LOGGING"
}

# Function to log unforeseen errors
error_handler() {
    local error_message="$1"
    log_event "ERROR: $error_message"
}

# Trap errors and signals
trap 'error_handler "An unforeseen error occurred. Exiting."' ERR INT TERM

# Initialize logging
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"

# Read the configuration file early
read_config

# Exit if static IP assignment is disabled
if [ "$ENABLE_STATIC_IP" = "false" ]; then
    log_event "Static IP assignment is disabled. Exiting script."
    exit 0
fi

# Function to kill the `udhcp` process if it is running
kill_udhcp() {
    UDHCP_PID=$(pgrep -x "udhcpc")
    if [ -n "$UDHCP_PID" ]; then
        if ! kill "$UDHCP_PID" 2>&1; then
            error_handler "Failed to kill udhcpc process with PID $UDHCP_PID."
            return 1
        fi
        log_event "Killed udhcpc process with PID $UDHCP_PID."
    fi
}

# Reset the network interface by flushing all IP address details
reset_interface() {
    INTERFACE="wlan0"  # Replace `wlan0` with your network interface
    FLUSH_OUTPUT=$(busybox ip addr flush dev "$INTERFACE" 2>&1)
    if [ $? -ne 0 ]; then
        error_handler "Failed to clear IP address details for interface $INTERFACE: $FLUSH_OUTPUT"
        return 1
    fi
    log_event "Cleared all IP address details for interface $INTERFACE."
}

# Configure network settings based on the configuration file
configure_network() {
    read_config
    INTERFACE="wlan0"  # Replace with your specific network interface
    if ! kill_udhcp; then
        error_handler "Failed to kill udhcp process."
        return 1
    fi
    if ! reset_interface; then
        error_handler "Reset interface $INTERFACE failed."
        return 1
    fi
    if ! ifconfig "$INTERFACE" "$IP_ADDR" netmask "$SUBNET_MASK" up; then
        error_handler "Failed to configure IP $IP_ADDR on interface $INTERFACE."
        return 1
    fi
    if ! route add default gw "$GATEWAY" "$INTERFACE"; then
        error_handler "Failed to set default gateway $GATEWAY on interface $INTERFACE."
        return 1
    fi
    log_event "Network configured with IP $IP_ADDR, Subnet $SUBNET_MASK, and Gateway $GATEWAY."
}

# Get the current IP address of the network interface
get_current_ip() {
    busybox ip -4 addr show dev "$INTERFACE" | grep -o 'inet [0-9.]\+' | awk '{print $2}'
}

# Monitor and maintain the configured IP address    
monitor_ip() {
    while true; do
        CURRENT_IP=$(get_current_ip)
        if [ "$CURRENT_IP" != "$IP_ADDR" ]; then
            log_event "Current IP ($CURRENT_IP) does not match configured IP ($IP_ADDR). Reconfiguring network."
            configure_network
        #else
        #    log_event "Current IP ($CURRENT_IP) matches configured IP ($IP_ADDR)."
        fi
        sleep 1  # Check every second
    done
}

# Start monitoring
monitor_ip
robughblah commented 1 week ago

I am not using your script, but suggest:1) monitoring and rotating your log file size to ensure the fs does not fill to 100%. 2) You are logging every second on success, perhaps log just the first success and then periodically every 5 min with a counter that wraps to 3600 2) instead of /tmp, maybe test for existence of /var/log and create it if it doesn’t exist? then you only need one size fits all script. Nice commenting Scott. We are both freaks that way.RobOn May 24, 2024, at 10:07 AM, Scott Frappier @.***> wrote: Here's a version of the script that points to the /tmp/ directory and also has the udhcpc kill built-in so that the camera does not try to renew a lease:

!/bin/sh

Path to the configuration file

CONFIG_FILE="/opt/wz_mini/etc/staticip.conf"

Log file path

LOG_FILE="/tmp/staticip_monitor.log"

Function to write log entries only if logging is enabled

log_event() { if [ "$STATIC_IP_LOGGING" = "true" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" fi }

Function to read the configuration file

read_config() { . "$CONFIG_FILE" IP_ADDR="$IP_ADDRESS" SUBNET_MASK="$SUBNET" GATEWAY="$DEFAULT_GATEWAY" ENABLE_STATIC_IP="$ENABLE_STATIC_IP" STATIC_IP_LOGGING="$STATIC_IP_LOGGING" }

Function to log unforeseen errors

error_handler() { local error_message="$1" log_event "ERROR: $error_message" }

Trap errors and signals

trap 'error_handler "An unforeseen error occurred. Exiting."' ERR INT TERM

Initialize logging

touch "$LOG_FILE" chmod 644 "$LOG_FILE"

Read the configuration file early

read_config

Exit if static IP assignment is disabled

if [ "$ENABLE_STATIC_IP" = "false" ]; then log_event "Static IP assignment is disabled. Exiting script." exit 0 fi

Function to kill the udhcp process if it is running

kill_udhcp() { UDHCP_PID=$(pgrep -x "udhcpc") if [ -n "$UDHCP_PID" ]; then if ! kill "$UDHCP_PID" 2>&1; then error_handler "Failed to kill udhcpc process with PID $UDHCP_PID." return 1 fi log_event "Killed udhcpc process with PID $UDHCP_PID." fi }

Reset the network interface by flushing all IP address details

reset_interface() { INTERFACE="wlan0" # Replace wlan0 with your network interface FLUSH_OUTPUT=$(busybox ip addr flush dev "$INTERFACE" 2>&1) if [ $? -ne 0 ]; then error_handler "Failed to clear IP address details for interface $INTERFACE: $FLUSH_OUTPUT" return 1 fi log_event "Cleared all IP address details for interface $INTERFACE." }

Configure network settings based on the configuration file

configure_network() { read_config INTERFACE="wlan0" # Replace with your specific network interface if ! kill_udhcp; then error_handler "Failed to kill udhcp process." return 1 fi if ! reset_interface; then error_handler "Reset interface $INTERFACE failed." return 1 fi if ! ifconfig "$INTERFACE" "$IP_ADDR" netmask "$SUBNET_MASK" up; then error_handler "Failed to configure IP $IP_ADDR on interface $INTERFACE." return 1 fi if ! route add default gw "$GATEWAY" "$INTERFACE"; then error_handler "Failed to set default gateway $GATEWAY on interface $INTERFACE." return 1 fi log_event "Network configured with IP $IP_ADDR, Subnet $SUBNET_MASK, and Gateway $GATEWAY." }

Get the current IP address of the network interface

get_current_ip() { busybox ip -4 addr show dev "$INTERFACE" | grep -o 'inet [0-9.]+' | awk '{print $2}' }

Monitor and maintain the configured IP address

monitor_ip() { while true; do CURRENT_IP=$(get_current_ip) if [ "$CURRENT_IP" != "$IP_ADDR" ]; then log_event "Current IP ($CURRENT_IP) does not match configured IP ($IP_ADDR). Reconfiguring network." configure_network

else

    #    log_event "Current IP ($CURRENT_IP) matches configured IP ($IP_ADDR)."
    fi
    sleep 1  # Check every second
done

}

Start monitoring

monitor_ip

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>