crdhcpc 0.1.1

Standalone DHCP Client for Linux with DHCPv4, DHCPv6, PXE, and Dynamic DNS support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
//! Standalone DHCP Client Binary
//!
//! This is a standalone DHCP client that can run independently without crrouter-web.
//! It provides a simple CLI interface for managing DHCP on network interfaces.

use crdhcpc::*;

use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::signal;
use tokio::sync::RwLock;
use tracing::{info, error, warn};
use tracing_subscriber::EnvFilter;

#[derive(Parser, Debug)]
#[command(author, version, about = "Standalone DHCP Client for Linux", long_about = None)]
struct Args {
    /// Configuration file path
    #[arg(short, long, default_value = "/etc/dhcp-client.toml")]
    config: PathBuf,

    /// Verbose logging
    #[arg(short, long)]
    verbose: bool,

    /// Subcommand
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Start DHCP client as a daemon
    Daemon {
        /// Run in foreground (don't daemonize)
        #[arg(short, long)]
        foreground: bool,
    },

    /// Start DHCP client on a specific interface
    Start {
        /// Network interface name
        interface: String,

        /// Run in foreground and show live status
        #[arg(short, long)]
        foreground: bool,
    },

    /// Stop DHCP client on an interface
    Stop {
        /// Network interface name
        interface: String,
    },

    /// Renew DHCP lease on an interface
    Renew {
        /// Network interface name
        interface: String,
    },

    /// Release DHCP lease on an interface
    Release {
        /// Network interface name
        interface: String,
    },

    /// Show DHCP client status
    Status {
        /// Network interface name (optional, shows all if not specified)
        interface: Option<String>,
    },

    /// Validate configuration file
    Check {
        /// Generate example configuration file to stdout
        #[arg(long)]
        generate_example: bool,
    },
}

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();

    // Setup logging
    let log_level = if args.verbose { "debug" } else { "info" };
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::new(log_level))
        .init();

    #[cfg(feature = "dhcp-debug")]
    if args.verbose {
        info!("========================================");
        info!("VERBOSE MODE ENABLED - Debug logging active");
        info!("Log level: {}", log_level);
        info!("========================================");
    }

    match args.command {
        // Check command can run without loading config (for --generate-example)
        Commands::Check { generate_example } => {
            check_config(&args.config, generate_example)?;
        }
        // All other commands need config
        _ => {
            // Load configuration
            let config = load_config(&args.config)?;

            match args.command {
                Commands::Daemon { foreground } => {
                    run_daemon(config, foreground).await?;
                }
                Commands::Start { interface, foreground } => {
                    start_interface(&interface, config, foreground).await?;
                }
                Commands::Stop { interface } => {
                    stop_interface(&interface).await?;
                }
                Commands::Renew { interface } => {
                    renew_interface(&interface).await?;
                }
                Commands::Release { interface } => {
                    release_interface(&interface).await?;
                }
                Commands::Status { interface } => {
                    show_status(interface).await?;
                }
                Commands::Check { .. } => unreachable!(), // Already handled above
            }
        }
    }

    Ok(())
}

fn load_config(path: &PathBuf) -> std::result::Result<DhcpClientConfig, Box<dyn std::error::Error>> {
    use std::fs;

    info!("Loading configuration from: {}", path.display());

    if !path.exists() {
        return Err(format!("Configuration file not found: {}", path.display()).into());
    }

    let config_str = fs::read_to_string(path)?;
    let config: DhcpClientConfig = toml::from_str(&config_str)?;

    info!("Configuration loaded successfully");
    Ok(config)
}

fn check_config(path: &PathBuf, generate_example: bool) -> std::result::Result<(), Box<dyn std::error::Error>> {
    use std::fs;
    use std::os::unix::fs::PermissionsExt;

    // Generate example configuration if requested
    if generate_example {
        println!("{}", generate_example_config());
        return Ok(());
    }

    println!("🔍 Checking configuration file: {}", path.display());
    println!();

    // Check if file exists
    if !path.exists() {
        eprintln!("❌ ERROR: Configuration file not found: {}", path.display());
        eprintln!("   Generate an example with: crdhcpc check --generate-example > /etc/dhcp-client.toml");
        std::process::exit(1);
    }

    // Check file permissions
    let metadata = fs::metadata(path)?;
    let permissions = metadata.permissions();
    let mode = permissions.mode();

    println!("📄 File information:");
    println!("   Path: {}", path.display());
    println!("   Permissions: {:o}", mode & 0o777);

    if mode & 0o004 != 0 {
        println!("   ⚠  WARNING: Config file is world-readable (others can read sensitive info)");
        println!("      Recommended: chmod 640 {}", path.display());
    } else {
        println!("   ✓ Permissions are appropriate");
    }
    println!();

    // Load and validate configuration
    let config = match load_config(path) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("❌ ERROR: Failed to parse configuration:");
            eprintln!("   {}", e);
            eprintln!();
            eprintln!("   Check the TOML syntax and ensure all required fields are present.");
            eprintln!("   Generate an example with: crdhcpc check --generate-example");
            std::process::exit(1);
        }
    };

    println!("✅ Configuration file is valid TOML");
    println!();

    // Configuration summary
    println!("📊 Configuration summary:");
    println!("   Global enabled: {}", if config.enabled { "✓ yes" } else { "✗ no" });
    println!("   Interfaces: {}", if config.interfaces.is_empty() {
        "⚠  NONE (daemon will not manage any interfaces)".to_string()
    } else {
        format!("{:?}", config.interfaces)
    });
    println!();

    // Protocol settings
    println!("🌐 Protocol settings:");
    println!("   DHCPv4: {}", if config.dhcpv4.enabled { "✓ enabled" } else { "✗ disabled" });
    if config.dhcpv4.enabled {
        println!("      Timeout: {}s", config.dhcpv4.timeout);
    }
    println!("   DHCPv6: {}", if config.dhcpv6.enabled { "✓ enabled" } else { "✗ disabled" });
    if config.dhcpv6.enabled {
        println!("      Timeout: {}s", config.dhcpv6.timeout);
    }

    if !config.dhcpv4.enabled && !config.dhcpv6.enabled {
        println!("   ⚠  WARNING: Both DHCPv4 and DHCPv6 are disabled!");
    }
    println!();

    // Additional features
    println!("🔧 Additional features:");
    println!("   PXE Boot: {}", if config.pxe.enabled { "✓ enabled" } else { "✗ disabled" });
    println!("   Dynamic DNS: {}", if config.ddns.enabled { "✓ enabled" } else { "✗ disabled" });
    println!("   Failover: {}", if config.failover.enabled { "✓ enabled" } else { "✗ disabled" });
    println!();

    // Security validation
    println!("🔒 Security settings:");
    let mut warnings = 0;

    if config.security.allowed_servers.is_empty() {
        println!("   ⚠  WARNING: No allowed DHCP servers configured!");
        println!("      This accepts DHCP responses from ANY server (security risk)");
        println!("      Recommended: Set security.allowed_servers = [\"192.168.1.1\"]");
        warnings += 1;
    } else {
        println!("   ✓ Allowed servers: {:?}", config.security.allowed_servers);
    }

    println!("   Message validation: {}", if config.security.validate_messages { "✓ enabled" } else { "⚠  disabled" });
    if !config.security.validate_messages {
        println!("      WARNING: Message validation is disabled (security risk)");
        warnings += 1;
    }

    println!("   Option validation: {}", if config.security.validate_options { "✓ enabled" } else { "⚠  disabled" });
    if !config.security.validate_options {
        println!("      WARNING: Option validation is disabled (security risk)");
        warnings += 1;
    }

    println!("   Max request rate: {} req/s", config.security.max_request_rate);
    if config.security.max_request_rate == 0 {
        println!("      ⚠  WARNING: Rate limiting is disabled");
        warnings += 1;
    }
    println!();

    // Interface validation
    println!("🔌 Interface validation:");
    if config.interfaces.is_empty() {
        println!("   ⚠  No interfaces configured");
    } else {
        // Try to check if interfaces exist (best effort)
        for iface in &config.interfaces {
            let iface_path = format!("/sys/class/net/{}", iface);
            if std::path::Path::new(&iface_path).exists() {
                println!("{} (exists)", iface);
            } else {
                println!("{} (not found - may not exist yet)", iface);
            }
        }
    }
    println!();

    // Summary
    if warnings > 0 {
        println!("⚠️  Configuration is valid but has {} security warning(s)", warnings);
        println!("   Review the warnings above and update your configuration for better security.");
    } else {
        println!("✅ Configuration is valid and secure!");
    }

    println!();
    println!("💡 Next steps:");
    println!("   • Start daemon: sudo systemctl start dhcp-client-standalone.service");
    println!("   • Check status: sudo crdhcpc status");
    println!("   • View logs: sudo journalctl -u dhcp-client-standalone.service -f");

    Ok(())
}

fn generate_example_config() -> String {
    r#"# DHCP Client Configuration
# This file configures the crdhcpc standalone DHCP client daemon
# Location: /etc/dhcp-client.toml

# Enable the DHCP client
enabled = true

# Mock mode (for testing only - simulates DHCP without network operations)
mock_mode = false

# List of network interfaces to manage
# The daemon will start DHCP on all listed interfaces
interfaces = ["eth0"]

# DHCPv4 configuration
[dhcpv4]
enabled = true
send_hostname = true
timeout = 10  # seconds
retry_count = 3
request_options = [1, 3, 6, 15, 28, 42]  # subnet mask, router, DNS, domain, broadcast, NTP
# hostname = "my-hostname"  # Optional: override system hostname
# vendor_class = "crrouter-web"  # Optional: vendor class identifier

# DHCPv6 configuration
[dhcpv6]
enabled = true
prefix_delegation = true
rapid_commit = false
timeout = 10  # seconds
retry_count = 3
request_options = [23, 24]  # DNS servers, domain search list

# Security settings
[security]
# Validate DHCP message format and checksums
validate_messages = true

# Validate DHCP options for correctness
validate_options = true

# Validate DHCP server (check against allowed_servers list)
validate_server = false

# List of allowed DHCP server IP addresses
# Empty list = accept responses from any server (if validate_server = true, this must not be empty)
# Example: allowed_servers = ["192.168.1.1", "10.0.0.1"]
allowed_servers = []

# Maximum DHCP requests per second (rate limiting)
# Set to 0 to disable rate limiting (not recommended)
max_request_rate = 10

# Enable DHCP snooping (additional security layer)
enable_dhcp_snooping = false

# Minimum acceptable lease time (seconds)
min_lease_time = 300  # 5 minutes

# Maximum acceptable lease time (seconds)
max_lease_time = 86400  # 24 hours

# PXE (Preboot Execution Environment) support
[pxe]
enabled = false
vendor_class = "PXEClient"
architecture = 7  # 0 = x86 BIOS, 7 = x64 UEFI, 9 = x64 UEFI HTTP

# Dynamic DNS (DDNS) updates
[ddns]
enabled = false
update_forward = true
update_reverse = true
ttl = 3600  # seconds
# server = "ns1.example.com"  # Optional: DNS server for updates
# tsig_key_name = "dhcp-update"  # Optional: TSIG key name for authentication
# tsig_key = "base64-encoded-secret"  # Optional: TSIG key for authentication

# TFTP client for PXE boot files
[tftp]
enabled = false
timeout = 5  # seconds
max_retries = 3
block_size = 1468  # Maximum for Ethernet without fragmentation

# Failover and high availability
[failover]
enabled = false
server_timeout = 5  # seconds
health_check_interval = 30  # seconds
allowed_servers = []  # List of allowed DHCP servers for failover
prefer_previous_server = true  # Prefer previously used server if healthy
"#.to_string()
}

async fn run_daemon(
    config: DhcpClientConfig,
    foreground: bool,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
    info!("Starting DHCP client daemon");

    if !foreground {
        // In a real implementation, we would daemonize here
        warn!("Daemonization not yet implemented, running in foreground");
    }

    let manager = Arc::new(RwLock::new(DhcpClientManager::new(config.clone())));

    // Start DHCP client on all configured interfaces
    for interface in &config.interfaces {
        info!("Starting DHCP client on interface: {}", interface);
        match manager.read().await.start_interface(interface).await {
            Ok(_) => info!("✓ Started DHCP client on {}", interface),
            Err(e) => error!("✗ Failed to start DHCP client on {}: {}", interface, e),
        }
    }

    // Create control interface handler
    let control_handler = DhcpControlHandler::new(manager.clone());

    // Configure Unix socket server
    let socket_path = "/var/run/crdhcpc.sock";
    let server_config = ServerConfig {
        socket_path: socket_path.to_string(),
        socket_mode: 0o666, // Allow all users to connect (handler validates credentials)
        max_connections: 10,
        allowed_uids: None,
        require_root: false,
    };

    info!("Starting control interface on {}", socket_path);

    // Create and start the Unix socket server
    let server = Arc::new(UnixServer::new(server_config, control_handler));
    let server_clone = server.clone();

    // Spawn the server task
    let server_task = tokio::spawn(async move {
        if let Err(e) = server_clone.run().await {
            error!("Control interface error: {}", e);
        }
    });

    // Wait for shutdown signal
    info!("DHCP client daemon running. Control interface: {}", socket_path);
    info!("Press Ctrl+C to stop, or send SIGTERM to gracefully shutdown");

    #[cfg(unix)]
    info!("Send SIGHUP to reload config, SIGUSR1 to log status");

    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};

        let mut sigterm = signal(SignalKind::terminate())
            .expect("Failed to setup SIGTERM handler");
        let mut sighup = signal(SignalKind::hangup())
            .expect("Failed to setup SIGHUP handler");
        let mut sigusr1 = signal(SignalKind::user_defined1())
            .expect("Failed to setup SIGUSR1 handler");

        loop {
            tokio::select! {
                _ = signal::ctrl_c() => {
                    info!("Received SIGINT (Ctrl+C), stopping DHCP client...");
                    break;
                }
                _ = sigterm.recv() => {
                    info!("Received SIGTERM, stopping DHCP client...");
                    break;
                }
                _ = sighup.recv() => {
                    info!("Received SIGHUP - reloading configuration");
                    // TODO: Reload configuration from disk
                    warn!("Configuration reload not yet implemented");
                }
                _ = sigusr1.recv() => {
                    info!("Received SIGUSR1 - logging status");
                    let status = manager.read().await.get_status().await;
                    info!("=== DHCP Client Status ===");
                    info!("Running: {}", status.running);
                    info!("DHCPv4 clients: {}", status.v4_clients.len());
                    for client in &status.v4_clients {
                        info!("  - {} (state: {})", client.interface, client.state);
                    }
                    info!("DHCPv6 clients: {}", status.v6_clients.len());
                    for client in &status.v6_clients {
                        info!("  - {} (state: {})", client.interface, client.state);
                    }
                    info!("==========================");
                }
            }
        }
    }

    #[cfg(not(unix))]
    {
        match signal::ctrl_c().await {
            Ok(()) => {
                info!("Received SIGINT (Ctrl+C), stopping DHCP client...");
            }
            Err(err) => {
                error!("Error waiting for shutdown signal: {}", err);
            }
        }
    }

    // Abort the server task
    server_task.abort();

    // Stop all interfaces
    for interface in &config.interfaces {
        info!("Stopping DHCP client on interface: {}", interface);
        match manager.read().await.stop_interface(interface).await {
            Ok(_) => info!("✓ Stopped DHCP client on {}", interface),
            Err(e) => error!("✗ Failed to stop DHCP client on {}: {}", interface, e),
        }
    }

    // Clean up socket file
    if std::path::Path::new(socket_path).exists() {
        if let Err(e) = std::fs::remove_file(socket_path) {
            warn!("Failed to remove socket file: {}", e);
        }
    }

    info!("DHCP client daemon stopped");
    Ok(())
}

async fn start_interface(
    interface: &str,
    config: DhcpClientConfig,
    foreground: bool,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
    #[cfg(feature = "dhcp-debug")]
    {
        info!("========================================");
        info!("DEBUG: start_interface() called");
        info!("  interface: {}", interface);
        info!("  foreground: {}", foreground);
        info!("========================================");
    }

    let manager = DhcpClientManager::new(config);

    #[cfg(feature = "dhcp-debug")]
    info!("DEBUG: Calling manager.start_interface({})", interface);

    manager.start_interface(interface).await?;

    info!("✓ DHCP client started on {}", interface);

    if foreground {
        info!("Running in foreground with status updates, press Ctrl+C to stop");

        // Show status updates every 5 seconds
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(5));

        loop {
            tokio::select! {
                _ = signal::ctrl_c() => {
                    info!("Received shutdown signal");
                    break;
                }
                _ = interval.tick() => {
                    let status = manager.get_status().await;
                    println!("\nStatus: running={}", status.running);

                    for client in &status.v4_clients {
                        if client.interface == interface {
                            println!("  DHCPv4 on {}: state={}", client.interface, client.state);
                            if let Some(lease) = &client.lease {
                                println!("    IP: {}", lease.ip_address);
                                println!("    Subnet: {}", lease.subnet_mask);
                                if let Some(router) = lease.router {
                                    println!("    Gateway: {}", router);
                                }
                                println!("    DNS: {:?}", lease.dns_servers);
                            }
                        }
                    }

                    for client in &status.v6_clients {
                        if client.interface == interface {
                            println!("  DHCPv6 on {}: state={}", client.interface, client.state);
                        }
                    }
                }
            }
        }
    } else {
        info!("Running in background, press Ctrl+C to stop");

        // Wait for shutdown signal without status updates
        match signal::ctrl_c().await {
            Ok(()) => {
                info!("Received shutdown signal");
            }
            Err(err) => {
                error!("Error waiting for shutdown signal: {}", err);
            }
        }
    }

    manager.stop_interface(interface).await?;
    info!("✓ DHCP client stopped on {}", interface);

    Ok(())
}

async fn stop_interface(interface: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
    info!("Stopping DHCP client on interface: {}", interface);

    // Connect to the daemon via Unix socket
    let client = DaemonClient::real("/var/run/crdhcpc.sock");

    match client.connect().await {
        Ok(_) => {
            info!("Connected to crdhcpc daemon");

            let params = serde_json::json!({
                "interface": interface
            });

            match client.call("dhcpc.stop", Some(params)).await {
                Ok(result) => {
                    info!("✓ Stopped DHCP client on {}", interface);
                    println!("{}", serde_json::to_string_pretty(&result)?);
                    Ok(())
                }
                Err(e) => {
                    error!("Failed to stop DHCP client: {}", e);
                    Err(format!("Failed to stop DHCP client: {}", e).into())
                }
            }
        }
        Err(e) => {
            error!("Failed to connect to daemon: {}", e);
            error!("Make sure crdhcpc daemon is running: crdhcpc daemon");
            Err(format!("Failed to connect to daemon: {}", e).into())
        }
    }
}

async fn renew_interface(interface: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
    info!("Renewing DHCP lease on interface: {}", interface);

    // Connect to the daemon via Unix socket
    let client = DaemonClient::real("/var/run/crdhcpc.sock");

    match client.connect().await {
        Ok(_) => {
            info!("Connected to crdhcpc daemon");

            let params = serde_json::json!({
                "interface": interface
            });

            match client.call("dhcpc.renew", Some(params)).await {
                Ok(result) => {
                    info!("✓ Renewed DHCP lease on {}", interface);
                    println!("{}", serde_json::to_string_pretty(&result)?);
                    Ok(())
                }
                Err(e) => {
                    error!("Failed to renew DHCP lease: {}", e);
                    Err(format!("Failed to renew DHCP lease: {}", e).into())
                }
            }
        }
        Err(e) => {
            error!("Failed to connect to daemon: {}", e);
            error!("Make sure crdhcpc daemon is running: crdhcpc daemon");
            Err(format!("Failed to connect to daemon: {}", e).into())
        }
    }
}

async fn release_interface(interface: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
    info!("Releasing DHCP lease on interface: {}", interface);

    // Connect to the daemon via Unix socket
    let client = DaemonClient::real("/var/run/crdhcpc.sock");

    match client.connect().await {
        Ok(_) => {
            info!("Connected to crdhcpc daemon");

            let params = serde_json::json!({
                "interface": interface
            });

            match client.call("dhcpc.release", Some(params)).await {
                Ok(result) => {
                    info!("✓ Released DHCP lease on {}", interface);
                    println!("{}", serde_json::to_string_pretty(&result)?);
                    Ok(())
                }
                Err(e) => {
                    error!("Failed to release DHCP lease: {}", e);
                    Err(format!("Failed to release DHCP lease: {}", e).into())
                }
            }
        }
        Err(e) => {
            error!("Failed to connect to daemon: {}", e);
            error!("Make sure crdhcpc daemon is running: crdhcpc daemon");
            Err(format!("Failed to connect to daemon: {}", e).into())
        }
    }
}

async fn show_status(interface: Option<String>) -> std::result::Result<(), Box<dyn std::error::Error>> {
    info!("Showing DHCP client status");

    // Connect to the daemon via Unix socket
    let client = DaemonClient::real("/var/run/crdhcpc.sock");

    match client.connect().await {
        Ok(_) => {
            info!("Connected to crdhcpc daemon");

            let params = if let Some(iface) = &interface {
                Some(serde_json::json!({
                    "interface": iface
                }))
            } else {
                None
            };

            match client.call("dhcpc.status", params).await {
                Ok(result) => {
                    if let Some(iface) = interface {
                        println!("\nStatus for interface: {}", iface);
                    } else {
                        println!("\nStatus for all interfaces:");
                    }
                    println!("{}", serde_json::to_string_pretty(&result)?);
                    Ok(())
                }
                Err(e) => {
                    error!("Failed to get status: {}", e);
                    Err(format!("Failed to get status: {}", e).into())
                }
            }
        }
        Err(e) => {
            error!("Failed to connect to daemon: {}", e);
            error!("Make sure crdhcpc daemon is running: crdhcpc daemon");
            Err(format!("Failed to connect to daemon: {}", e).into())
        }
    }
}