Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,4 @@ web/dist/
# Playwright test artifacts
playwright-report/
test-results/
/data
149 changes: 149 additions & 0 deletions ONVIF_NETWORK_OVERRIDE_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# ONVIF Network Override Implementation

## Summary

This implementation adds support for overriding the ONVIF discovery network via environment variable, making it easier to use ONVIF camera discovery in containerized deployments.

## Problem Statement

When running LightNVR in a Docker container, the automatic network detection skips Docker bridge interfaces (`docker*`, `veth*`, `br-*`, `lxc*`). This prevents ONVIF discovery from working in containerized environments where cameras may be on the host network or external networks accessible through Docker networking.

## Solution

Added a priority-based network selection system with support for environment variable override:

### Priority Order

1. **Explicit parameter** - Network passed directly to `discover_onvif_devices()` function
2. **Environment variable** - `LIGHTNVR_ONVIF_NETWORK` (new)
3. **Config file** - `[onvif]` section `discovery_network` setting (new)
4. **Auto-detection** - Existing behavior (skips Docker interfaces)

## Implementation Details

### 1. Configuration Structure (`include/core/config.h`)

Already had the fields defined:
```c
bool onvif_discovery_enabled;
int onvif_discovery_interval;
char onvif_discovery_network[64];
```

### 2. Default Configuration (`src/core/config.c`)

Added initialization in `load_default_config()`:
```c
config->onvif_discovery_enabled = false;
config->onvif_discovery_interval = 300; // 5 minutes
snprintf(config->onvif_discovery_network, sizeof(config->onvif_discovery_network), "auto");
```

### 3. Config File Parsing (`src/core/config.c`)

Added `[onvif]` section handler in `config_ini_handler()`:
```c
else if (strcmp(section, "onvif") == 0) {
if (strcmp(name, "discovery_enabled") == 0) {
config->onvif_discovery_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(name, "discovery_interval") == 0) {
config->onvif_discovery_interval = atoi(value);
// Clamped to 30-3600 seconds
} else if (strcmp(name, "discovery_network") == 0) {
strncpy(config->onvif_discovery_network, value, sizeof(config->onvif_discovery_network) - 1);
}
}
```

### 4. Priority-Based Network Selection (`src/video/onvif_discovery.c`)

Modified `discover_onvif_devices()` to implement complete priority order:
```c
if (!network || strlen(network) == 0 || strcmp(network, "auto") == 0) {
// Priority 1: Check environment variable
const char *env_network = getenv("LIGHTNVR_ONVIF_NETWORK");
if (env_network && strlen(env_network) > 0 && strcmp(env_network, "auto") != 0) {
log_info("Using ONVIF discovery network from environment variable: %s", env_network);
network = env_network;
}
// Priority 2: Check config file
else if (g_config.onvif_discovery_network[0] != '\0' &&
strcmp(g_config.onvif_discovery_network, "auto") != 0) {
log_info("Using ONVIF discovery network from config file: %s", g_config.onvif_discovery_network);
network = g_config.onvif_discovery_network;
}
// Priority 3: Auto-detect
else {
network_count = detect_local_networks(detected_networks, MAX_DETECTED_NETWORKS);
// ... use first detected network
}
}
```

### 5. Documentation Updates

- **config/lightnvr.ini** - Added `[onvif]` section with examples
- **docs/DOCKER.md** - Added environment variable documentation and usage examples
- **docker-compose.yml** - Added commented example for `LIGHTNVR_ONVIF_NETWORK`
- **docker-entrypoint.sh** - Added `[onvif]` section to default config template

## Usage Examples

### Docker Compose

```yaml
services:
lightnvr:
environment:
- LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
```

### Docker Run

```bash
docker run -e LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24 \
-p 8080:8080 \
ghcr.io/opensensor/lightnvr:latest
```

### Config File

```ini
[onvif]
discovery_enabled = true
discovery_interval = 300
discovery_network = 192.168.1.0/24
```

## Finding Your Network

On the Docker host:
```bash
ip addr show
# If host IP is 192.168.1.100 with netmask 255.255.255.0
# Use: LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
```

## Testing

The implementation maintains backward compatibility:
- Existing auto-detection still works when no override is set
- Explicit function parameters still take highest priority
- Config file parsing is optional (defaults to "auto")

## Files Modified

1. `src/core/config.c` - Added ONVIF defaults and config parsing
2. `src/video/onvif_discovery.c` - Added environment variable check
3. `config/lightnvr.ini` - Added `[onvif]` section
4. `docs/DOCKER.md` - Added documentation
5. `docker-compose.yml` - Added example
6. `docker-entrypoint.sh` - Added default config template

## Benefits

- **Container-friendly** - Easy to configure via environment variables
- **Flexible** - Multiple configuration methods
- **Backward compatible** - Existing behavior unchanged
- **Well-documented** - Clear examples for users

Empty file modified config/go2rtc/go2rtc-test.yaml
100644 → 100755
Empty file.
3 changes: 3 additions & 0 deletions config/go2rtc/go2rtc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ log:

streams:
# Streams will be added dynamically by LightNVR
Full:
- rtsp://thingino:thingino@192.168.82.156:554/ch1#transport=tcp#timeout=30
- ffmpeg:Full#video=copy#audio=aac
Empty file modified config/lightnvr-test.ini
100644 → 100755
Empty file.
14 changes: 14 additions & 0 deletions config/lightnvr.ini
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,17 @@ stun_server = stun.l.google.com:19302
; Format: stun:host:port or turn:host:port
; Example: stun:stun.example.com:3478,turn:turn.example.com:3478
; ice_servers =

[onvif]
; ONVIF camera discovery settings
; Enable automatic discovery of ONVIF cameras on the network
discovery_enabled = false

; Interval in seconds between discovery scans (30-3600)
discovery_interval = 300

; Network to scan for ONVIF devices in CIDR notation
; Use "auto" for automatic detection (skips Docker/virtual interfaces)
; For containers, set LIGHTNVR_ONVIF_NETWORK environment variable instead
; Examples: 192.168.1.0/24, 10.0.0.0/16, auto
discovery_network = auto
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ services:

# Auto-initialize configuration files on first run
- LIGHTNVR_AUTO_INIT=true

# ONVIF camera discovery network (for containers)
# Uncomment and set to your camera network in CIDR notation
# Example: 192.168.1.0/24 for cameras on 192.168.1.x network
# - LIGHTNVR_ONVIF_NETWORK=192.168.82.0/24

networks:
- lightnvr

networks:
lightnvr:
driver: bridge

7 changes: 7 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ api_port = 1984
; WebRTC configuration for NAT/firewall traversal
webrtc_enabled = true
webrtc_ice_servers = stun:stun.l.google.com:19302

[onvif]
; ONVIF camera discovery settings
discovery_enabled = false
discovery_interval = 300
; For containers, use LIGHTNVR_ONVIF_NETWORK environment variable instead
discovery_network = auto
EOF
log_info "Default configuration created at /etc/lightnvr/lightnvr.ini"
else
Expand Down
29 changes: 29 additions & 0 deletions docs/DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ services:
| `GO2RTC_CONFIG_PERSIST` | `true` | Persist go2rtc config across restarts |
| `LIGHTNVR_AUTO_INIT` | `true` | Auto-initialize config files on first run |
| `LIGHTNVR_WEB_ROOT` | `/var/lib/lightnvr/web` | Web assets directory |
| `LIGHTNVR_ONVIF_NETWORK` | (none) | Override ONVIF discovery network (e.g., `192.168.1.0/24`) |

### Example Usage

Expand All @@ -237,6 +238,34 @@ environment:
- TZ=America/New_York
- GO2RTC_CONFIG_PERSIST=true
- LIGHTNVR_AUTO_INIT=true
- LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
```

### ONVIF Discovery in Containers

When running in a container, ONVIF auto-detection skips Docker bridge interfaces by default. To enable ONVIF camera discovery in containerized deployments, set the `LIGHTNVR_ONVIF_NETWORK` environment variable to specify which network to scan:

```yaml
services:
lightnvr:
environment:
# Specify the network where your cameras are located
- LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
```

**Network Priority:**
1. Explicit network parameter (API calls)
2. `LIGHTNVR_ONVIF_NETWORK` environment variable
3. `discovery_network` in config file (`[onvif]` section)
4. Auto-detection (skips Docker interfaces)

**Finding Your Network:**
```bash
# On the Docker host, find your camera network
ip addr show

# Example: If your host IP is 192.168.1.100 with netmask 255.255.255.0
# Use: LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
```

## First Run Experience
Expand Down
27 changes: 25 additions & 2 deletions src/core/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ void load_default_config(config_t *config) {
snprintf(config->go2rtc_stun_server, sizeof(config->go2rtc_stun_server), "stun.l.google.com:19302");
config->go2rtc_external_ip[0] = '\0'; // Empty by default (auto-detect)
config->go2rtc_ice_servers[0] = '\0'; // Empty by default (use STUN server)


// ONVIF discovery settings
config->onvif_discovery_enabled = false; // Disabled by default
config->onvif_discovery_interval = 300; // 5 minutes between scans
snprintf(config->onvif_discovery_network, sizeof(config->onvif_discovery_network), "auto");

// Initialize default values for detection-based recording in streams
for (int i = 0; i < MAX_STREAMS; i++) {
config->streams[i].detection_based_recording = false;
Expand Down Expand Up @@ -489,7 +494,25 @@ static int config_ini_handler(void* user, const char* section, const char* name,
config->go2rtc_ice_servers[sizeof(config->go2rtc_ice_servers) - 1] = '\0';
}
}

// ONVIF settings
else if (strcmp(section, "onvif") == 0) {
if (strcmp(name, "discovery_enabled") == 0) {
config->onvif_discovery_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(name, "discovery_interval") == 0) {
config->onvif_discovery_interval = atoi(value);
// Clamp to reasonable range (30 seconds to 1 hour)
if (config->onvif_discovery_interval < 30) {
config->onvif_discovery_interval = 30;
}
if (config->onvif_discovery_interval > 3600) {
config->onvif_discovery_interval = 3600;
}
} else if (strcmp(name, "discovery_network") == 0) {
strncpy(config->onvif_discovery_network, value, sizeof(config->onvif_discovery_network) - 1);
config->onvif_discovery_network[sizeof(config->onvif_discovery_network) - 1] = '\0';
}
}

return 1; // Return 1 to continue processing
}

Expand Down
46 changes: 33 additions & 13 deletions src/video/onvif_discovery.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "video/onvif_discovery_thread.h"
#include "video/onvif_device_management.h"
#include "core/logger.h"
#include "core/config.h"
#include "core/curl_init.h"
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -190,24 +191,43 @@ int discover_onvif_devices(const char *network, onvif_device_info_t *devices,

// Check if we need to auto-detect networks
if (!network || strlen(network) == 0 || strcmp(network, "auto") == 0) {
log_info("Auto-detecting networks for ONVIF discovery");
// Priority 1: Check environment variable LIGHTNVR_ONVIF_NETWORK
const char *env_network = getenv("LIGHTNVR_ONVIF_NETWORK");
if (env_network && strlen(env_network) > 0 && strcmp(env_network, "auto") != 0) {
log_info("Using ONVIF discovery network from environment variable: %s", env_network);
strncpy(selected_network, env_network, sizeof(selected_network) - 1);
selected_network[sizeof(selected_network) - 1] = '\0';
network = selected_network;
}
// Priority 2: Check config file setting
else if (g_config.onvif_discovery_network[0] != '\0' &&
strcmp(g_config.onvif_discovery_network, "auto") != 0) {
log_info("Using ONVIF discovery network from config file: %s", g_config.onvif_discovery_network);
strncpy(selected_network, g_config.onvif_discovery_network, sizeof(selected_network) - 1);
selected_network[sizeof(selected_network) - 1] = '\0';
network = selected_network;
}
// Priority 3: Auto-detect networks
else {
log_info("Auto-detecting networks for ONVIF discovery");

// Detect local networks
network_count = detect_local_networks(detected_networks, MAX_DETECTED_NETWORKS);
// Detect local networks
network_count = detect_local_networks(detected_networks, MAX_DETECTED_NETWORKS);

if (network_count <= 0) {
log_error("Failed to auto-detect networks for ONVIF discovery");
return -1;
}
if (network_count <= 0) {
log_error("Failed to auto-detect networks for ONVIF discovery");
return -1;
}

// Use the first detected network
strncpy(selected_network, detected_networks[0], sizeof(selected_network) - 1);
selected_network[sizeof(selected_network) - 1] = '\0';
// Use the first detected network
strncpy(selected_network, detected_networks[0], sizeof(selected_network) - 1);
selected_network[sizeof(selected_network) - 1] = '\0';

log_info("Auto-detected network for ONVIF discovery: %s", selected_network);
log_info("Auto-detected network for ONVIF discovery: %s", selected_network);

// Use the selected network
network = selected_network;
// Use the selected network
network = selected_network;
}
}

log_info("Starting ONVIF discovery on network %s", network);
Expand Down
Loading