1#![doc = include_str!("../README.md")]
2
3use std::{
4 net::{IpAddr, SocketAddr},
5 sync::{Arc, Once},
6};
7
8use pyo3::{exceptions::PyValueError, prelude::*};
9use pyo3_async_runtimes::tokio::future_into_py;
10use tracing_subscriber::filter::LevelFilter;
11
12use crate::ip_or_str::IpRepr;
13
14extern crate tailscale as ts;
15
16type PyFut<'p> = PyResult<Bound<'p, PyAny>>;
17
18mod ip_or_str;
19mod node_info;
20mod tcp;
21mod udp;
22
23use node_info::NodeInfo;
24
25#[pymodule]
27pub mod _internal {
28 use super::*;
29 #[pymodule_export]
30 use crate::{
31 Device,
32 tcp::{TcpListener, TcpStream},
33 udp::UdpSocket,
34 };
35
36 #[pyfunction]
38 #[pyo3(signature = (config_path, auth_key=None))]
39 pub fn connect(py: Python<'_>, config_path: String, auth_key: Option<String>) -> PyFut<'_> {
40 static TRACING_ONCE: Once = Once::new();
41 TRACING_ONCE.call_once(|| {
42 let env_filter = tracing_subscriber::EnvFilter::builder()
43 .with_default_directive(LevelFilter::INFO.into())
44 .from_env_lossy();
45
46 tracing_subscriber::fmt().with_env_filter(env_filter).init();
47 });
48
49 future_into_py(py, async move {
50 let config = ts::Config {
51 key_state: ts::load_key_file(config_path, Default::default())
52 .await
53 .map_err(py_value_err)?,
54 client_name: Some("ts_python".to_owned()),
55 ..Default::default()
56 };
57
58 let dev = ts::Device::new(&config, auth_key)
59 .await
60 .map_err(py_value_err)?;
61
62 Ok(Device { dev: Arc::new(dev) })
63 })
64 }
65}
66
67#[pyclass(frozen, module = "tailscale")]
69pub struct Device {
70 dev: Arc<ts::Device>,
71}
72
73#[pymethods]
74impl Device {
75 pub fn udp_bind<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
79 let dev = self.dev.clone();
80 let ip: Result<IpAddr, _> = addr.0.try_into();
81
82 future_into_py(py, async move {
83 let ip = ip?;
84
85 let sock = dev
86 .udp_bind((ip, addr.1).into())
87 .await
88 .map_err(py_value_err)?;
89
90 Ok(udp::UdpSocket {
91 sock: Arc::new(sock),
92 })
93 })
94 }
95
96 pub fn tcp_listen<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
100 let dev = self.dev.clone();
101 let ip: Result<IpAddr, _> = addr.0.try_into();
102
103 future_into_py(py, async move {
104 let ip = ip?;
105
106 let listener = dev
107 .tcp_listen((ip, addr.1).into())
108 .await
109 .map_err(py_value_err)?;
110
111 Ok(tcp::TcpListener {
112 listener: Arc::new(listener),
113 })
114 })
115 }
116
117 pub fn tcp_connect<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
121 let dev = self.dev.clone();
122 let ip: Result<IpAddr, _> = addr.0.try_into();
123
124 future_into_py(py, async move {
125 let ip = ip?;
126
127 let sock = dev
128 .tcp_connect((ip, addr.1).into())
129 .await
130 .map_err(|e| PyValueError::new_err(e.to_string()))?;
131
132 Ok(tcp::TcpStream {
133 sock: Arc::new(sock),
134 })
135 })
136 }
137
138 pub fn ipv4_addr<'p>(&self, py: Python<'p>) -> PyFut<'p> {
140 let dev = self.dev.clone();
141
142 future_into_py(py, async move {
143 let ip = dev.ipv4_addr().await.map_err(py_value_err)?;
144 Ok(ip)
145 })
146 }
147
148 pub fn ipv6_addr<'p>(&self, py: Python<'p>) -> PyFut<'p> {
150 let dev = self.dev.clone();
151
152 future_into_py(py, async move {
153 let ip = dev.ipv6_addr().await.map_err(py_value_err)?;
154 Ok(ip)
155 })
156 }
157
158 pub fn peer_by_name<'p>(&self, py: Python<'p>, name: String) -> PyFut<'p> {
162 let dev = self.dev.clone();
163
164 future_into_py(py, async move {
165 let node = dev.peer_by_name(&name).await.map_err(py_value_err)?;
166
167 Ok(node.map(|node| NodeInfo::from(&node)))
168 })
169 }
170}
171
172fn sockaddr_as_tuple(s: SocketAddr) -> (IpAddr, u16) {
173 (s.ip(), s.port())
174}
175
176fn py_value_err(e: impl ToString) -> PyErr {
177 PyValueError::new_err(e.to_string())
178}