1use std::str::FromStr;
2
3use anyhow::{Result, anyhow};
4pub use easy_upnp::{PortMappingProtocol, UpnpConfig};
5
6#[derive(Debug, Clone, Default)]
7pub enum Protocol {
8 #[default]
9 TCP,
10 UDP,
11}
12
13impl std::fmt::Display for Protocol {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 f.write_str(match self {
16 Self::UDP => "udp",
17 Self::TCP => "tcp",
18 })
19 }
20}
21
22impl From<Protocol> for PortMappingProtocol {
23 fn from(value: Protocol) -> Self {
24 match value {
25 Protocol::TCP => Self::TCP,
26 Protocol::UDP => Self::UDP,
27 }
28 }
29}
30
31impl FromStr for Protocol {
32 type Err = anyhow::Error;
33
34 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
35 match s.to_lowercase().as_str() {
36 "tcp" => Ok(Protocol::TCP),
37 "udp" => Ok(Protocol::UDP),
38 x => Err(anyhow!("Invalid Protocol `{}`", x)),
39 }
40 }
41}
42
43fn port_forward(port: u16, protocol: Protocol, duration: Option<u32>) -> UpnpConfig {
44 UpnpConfig {
45 port,
46 protocol: protocol.into(),
47 address: None,
48 duration: duration.unwrap_or(3600),
49 comment: "Forward from cupnp CLI tool".into(),
50 }
51}
52
53fn results(res: Vec<Result<(), easy_upnp::Error>>) -> Result<()> {
54 for result in res {
55 result?
56 }
57
58 Ok(())
59}
60
61pub fn expose_port(port: u16, protocol: Protocol, duration: Option<u32>) -> Result<()> {
62 results(easy_upnp::add_ports([port_forward(port, protocol, duration)]).collect())
63}
64
65pub fn delete_port(port: u16, protocol: Protocol) -> Result<()> {
66 results(easy_upnp::delete_ports([port_forward(port, protocol, None)]).collect())
67}