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 key_state;
20mod node_info;
21mod tcp;
22mod udp;
23
24use key_state::Keystate;
25use node_info::NodeInfo;
26
27#[pymodule]
29pub mod _internal {
30 use super::*;
31 #[pymodule_export]
32 use crate::{
33 Device, Keystate,
34 tcp::{TcpListener, TcpStream},
35 udp::UdpSocket,
36 };
37
38 #[pyfunction]
40 #[pyo3(signature = (key_file_path=None, /, auth_key=None, *, control_server_url=None, hostname=None, tags=None, keys=None))]
41 pub fn connect(
42 py: Python<'_>,
43 key_file_path: Option<String>,
44 auth_key: Option<String>,
45 control_server_url: Option<String>,
46 hostname: Option<String>,
47 tags: Option<Vec<String>>,
48 keys: Option<Keystate>,
49 ) -> PyFut<'_> {
50 static TRACING_ONCE: Once = Once::new();
51 TRACING_ONCE.call_once(|| {
52 tracing_subscriber::fmt()
53 .with_env_filter(
54 tracing_subscriber::EnvFilter::builder()
55 .with_default_directive(LevelFilter::INFO.into())
56 .from_env_lossy(),
57 )
58 .init();
59 });
60
61 future_into_py(py, async move {
62 let mut config = if let Some(key_file_path) = key_file_path {
63 ts::Config::default_with_key_file(key_file_path)
64 .await
65 .map_err(py_value_err)?
66 } else {
67 ts::Config::default()
68 };
69
70 config.client_name = Some("ts_python".to_owned());
71 if let Some(control_server_url) = control_server_url {
72 config.control_server_url = control_server_url.parse().map_err(py_value_err)?;
73 }
74
75 if let Some(hostname) = hostname {
76 config.requested_hostname = Some(hostname);
77 }
78
79 if let Some(tags) = tags {
80 config.requested_tags = tags;
81 }
82
83 if let Some(keys) = &keys {
84 config.key_state = keys.try_into().map_err(|_| py_value_err("invalid keys"))?;
85 }
86
87 let dev = ts::Device::new(&config, auth_key)
88 .await
89 .map_err(py_value_err)?;
90
91 Ok(Device { dev: Arc::new(dev) })
92 })
93 }
94}
95
96#[pyclass(frozen, module = "tailscale")]
98pub struct Device {
99 dev: Arc<ts::Device>,
100}
101
102#[pymethods]
103impl Device {
104 pub fn udp_bind<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
108 let dev = self.dev.clone();
109 let ip: Result<IpAddr, _> = addr.0.try_into();
110
111 future_into_py(py, async move {
112 let ip = ip?;
113
114 let sock = dev
115 .udp_bind((ip, addr.1).into())
116 .await
117 .map_err(py_value_err)?;
118
119 Ok(udp::UdpSocket {
120 sock: Arc::new(sock),
121 })
122 })
123 }
124
125 pub fn tcp_listen<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
129 let dev = self.dev.clone();
130 let ip: Result<IpAddr, _> = addr.0.try_into();
131
132 future_into_py(py, async move {
133 let ip = ip?;
134
135 let listener = dev
136 .tcp_listen((ip, addr.1).into())
137 .await
138 .map_err(py_value_err)?;
139
140 Ok(tcp::TcpListener {
141 listener: Arc::new(listener),
142 })
143 })
144 }
145
146 pub fn tcp_connect<'p>(&self, py: Python<'p>, addr: (IpRepr, u16)) -> PyFut<'p> {
150 let dev = self.dev.clone();
151 let ip: Result<IpAddr, _> = addr.0.try_into();
152
153 future_into_py(py, async move {
154 let ip = ip?;
155
156 let sock = dev
157 .tcp_connect((ip, addr.1).into())
158 .await
159 .map_err(|e| PyValueError::new_err(e.to_string()))?;
160
161 Ok(tcp::TcpStream {
162 sock: Arc::new(sock),
163 })
164 })
165 }
166
167 pub fn ipv4_addr<'p>(&self, py: Python<'p>) -> PyFut<'p> {
169 let dev = self.dev.clone();
170
171 future_into_py(py, async move {
172 let ip = dev.ipv4_addr().await.map_err(py_value_err)?;
173 Ok(ip)
174 })
175 }
176
177 pub fn ipv6_addr<'p>(&self, py: Python<'p>) -> PyFut<'p> {
179 let dev = self.dev.clone();
180
181 future_into_py(py, async move {
182 let ip = dev.ipv6_addr().await.map_err(py_value_err)?;
183 Ok(ip)
184 })
185 }
186
187 pub fn peer_by_name<'p>(&self, py: Python<'p>, name: String) -> PyFut<'p> {
191 let dev = self.dev.clone();
192
193 future_into_py(py, async move {
194 let node = dev.peer_by_name(&name).await.map_err(py_value_err)?;
195
196 Ok(node.map(|node| NodeInfo::from(&node)))
197 })
198 }
199
200 pub fn self_node<'p>(&self, py: Python<'p>) -> PyFut<'p> {
202 let dev = self.dev.clone();
203
204 future_into_py(py, async move {
205 let node = dev.self_node().await.map_err(py_value_err)?;
206 Ok(NodeInfo::from(&node))
207 })
208 }
209
210 pub fn peer_by_tailnet_ip<'p>(&self, py: Python<'p>, ip: IpRepr) -> PyFut<'p> {
212 let dev = self.dev.clone();
213
214 future_into_py(py, async move {
215 let ip = ip.try_into().map_err(py_value_err)?;
216 let node = dev.peer_by_tailnet_ip(ip).await.map_err(py_value_err)?;
217
218 Ok(node.map(|node| NodeInfo::from(&node)))
219 })
220 }
221
222 pub fn peers_with_route<'p>(&self, py: Python<'p>, ip: IpRepr) -> PyFut<'p> {
227 let dev = self.dev.clone();
228
229 future_into_py(py, async move {
230 let ip = ip.try_into().map_err(py_value_err)?;
231 let nodes = dev.peers_with_route(ip).await.map_err(py_value_err)?;
232
233 Ok(nodes
234 .into_iter()
235 .map(|node| NodeInfo::from(&node))
236 .collect::<Vec<_>>())
237 })
238 }
239}
240
241fn sockaddr_as_tuple(s: SocketAddr) -> (IpAddr, u16) {
242 (s.ip(), s.port())
243}
244
245fn py_value_err(e: impl ToString) -> PyErr {
246 PyValueError::new_err(e.to_string())
247}