1 // Copyright (C) 2022 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Functionality for communicating with Trusty services. 16 //! 17 //! This crate provides the [`TipcChannel`] type, which allows you to establish a 18 //! connection to a Trusty service and then communicate with that service. 19 //! 20 //! # Usage 21 //! 22 //! To connect to a Trusty service you need two things: 23 //! 24 //! * The filesystem path to the Trusty IPC device. This is usually 25 //! `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`]. 26 //! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`. 27 //! 28 //! Pass these values to [`TipcChannel::connect`] to establish a connection to a 29 //! service. 30 //! 31 //! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv] 32 //! methods to communicate with the service. Messages are passed as byte buffers, and 33 //! each Trusty service has its own protocol for what data messages are expected to 34 //! contain. Consult the documentation for the service you are communicating with to 35 //! determine how to format outgoing messages and interpret incoming ones. 36 //! 37 //! The connection is closed automatically when [`TipcChannel`] is dropped. 38 //! 39 //! # Examples 40 //! 41 //! This example is a simplified version of the echo test from `tipc-test-rs`: 42 //! 43 //! ```no_run 44 //! use trusty::{DEFAULT_DEVICE, TipcChannel}; 45 //! use std::io::{Read, Write}; 46 //! 47 //! let mut chann = TipcChannel::connect( 48 //! DEFAULT_DEVICE, 49 //! "com.android.ipc-unittest.srv.echo", 50 //! ).unwrap(); 51 //! 52 //! chann.send("Hello, world!".as_bytes()).unwrap(); 53 //! 54 //! let mut read_buf = Vec::new(); 55 //! let read_len = stream.recv(&mut read_buf).unwrap(); 56 //! 57 //! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap(); 58 //! assert_eq!("Hello, world!", response); 59 //! 60 //! // The connection is closed here. 61 //! ``` 62 63 use crate::sys::tipc_connect; 64 use log::{trace, warn}; 65 use nix::sys::socket; 66 use std::convert::From; 67 use std::ffi::CString; 68 use std::fs::File; 69 use std::io; 70 use std::io::prelude::*; 71 use std::io::{ErrorKind, Result}; 72 use std::os::unix::prelude::AsRawFd; 73 use std::path::Path; 74 use std::thread; 75 use std::time; 76 77 mod sys; 78 79 /// The default filesystem path for the Trusty IPC device. 80 pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0"; 81 82 /// The maximum size an incoming TIPC message can be. 83 /// 84 /// This can be used to pre-allocate buffer space in order to ensure that your 85 /// read buffer can always hold an incoming message. 86 pub const MAX_MESSAGE_SIZE: usize = 4096; 87 88 /// A channel for communicating with a Trusty service. 89 /// 90 /// See the [crate-level documentation][crate] for usage details and examples. 91 #[derive(Debug)] 92 pub struct TipcChannel(File); 93 94 impl TipcChannel { 95 /// Attempts to establish a connection to the specified Trusty service. 96 /// 97 /// The first argument is the path of the Trusty device in the local filesystem, 98 /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service 99 /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`. 100 /// 101 /// # Panics 102 /// 103 /// This function will panic if `service` contains any intermediate `NUL` 104 /// bytes. This is handled with a panic because the service names are all 105 /// hard-coded constants, and so such an error should always be indicative of a 106 /// bug in the calling code. connect(device: &str, service: &str) -> Result<Self>107 pub fn connect(device: &str, service: &str) -> Result<Self> { 108 if let Some(cid_port_str) = device.strip_prefix("VSOCK:") { 109 Self::connect_vsock(cid_port_str, service) 110 } else { 111 Self::connect_tipc(device, service) 112 } 113 } 114 connect_vsock(type_cid_port_str: &str, service: &str) -> Result<Self>115 fn connect_vsock(type_cid_port_str: &str, service: &str) -> Result<Self> { 116 let cid_port_str; 117 let socket_type; 118 if let Some(stream_cid_port_str) = type_cid_port_str.strip_prefix("STREAM:") { 119 socket_type = socket::SockType::Stream; 120 cid_port_str = stream_cid_port_str; 121 } else if let Some(seqpacket_cid_port_str) = type_cid_port_str.strip_prefix("SEQPACKET:") { 122 socket_type = socket::SockType::SeqPacket; 123 cid_port_str = seqpacket_cid_port_str; 124 } else { 125 /* 126 * Default to SOCK_STREAM if neither type is specified. 127 * 128 * TODO: use SOCK_SEQPACKET by default instead of SOCK_STREAM when SOCK_SEQPACKET is fully 129 * supported since it matches tipc better. At the moment SOCK_SEQPACKET is not supported by 130 * crosvm. It is also significantly slower since the Linux kernel implementation (as of 131 * v6.7-rc1) sends credit update packets every time it receives a data packet while the 132 * SOCK_STREAM version skips these unless the remaining buffer space is "low". 133 */ 134 socket_type = socket::SockType::Stream; 135 cid_port_str = type_cid_port_str; 136 } 137 138 let [cid, port]: [u32; 2] = cid_port_str 139 .split(':') 140 .map(|v| v.parse::<u32>().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))) 141 .collect::<Result<Vec<u32>>>()? 142 .try_into() 143 .map_err(|e| { 144 io::Error::new(io::ErrorKind::InvalidInput, format!("Wrong number of args: {e:?}")) 145 })?; 146 147 trace!("got cid, port: {cid}, {port}"); 148 let s = socket::socket( 149 socket::AddressFamily::Vsock, 150 socket_type, 151 socket::SockFlag::SOCK_CLOEXEC, 152 None, 153 )?; 154 trace!("got socket"); 155 let sa = socket::VsockAddr::new(cid, port); 156 trace!("got sa"); 157 158 //let connect_timeout = libc::timeval {tv_sec: 60, tv_usec: 0}; 159 // TODO: Set AF_VSOCK/SO_VM_SOCKETS_CONNECT_TIMEOUT sockopt. 160 161 let mut retry = 10; 162 loop { 163 let res = socket::connect(s.as_raw_fd(), &sa); 164 if res.is_ok() || retry <= 0 { 165 res?; 166 break; 167 } 168 warn!("vsock:{cid}:{port} connect failed {res:?}, {retry} retries remaining"); 169 retry -= 1; 170 thread::sleep(time::Duration::from_secs(5)); 171 } 172 trace!("connected"); 173 // TODO: Current vsock tipc bridge in trusty expects a port name in the 174 // first packet. We need to replace this with a protocol that also does DICE 175 // based authentication. 176 // `s` is a valid file descriptor because it came from socket::socket. 177 let mut channel = Self(File::from(s)); 178 channel.send(service.as_bytes())?; 179 trace!("sent tipc port name"); 180 181 // Work around lack of seq packet support. Read a status byte to prevent 182 // the caller from sending more data until srv_name has been read. 183 let mut status = [0; 1]; 184 channel.recv_no_alloc(&mut status)?; 185 trace!("got status byte: {status:?}"); 186 Ok(channel) 187 } 188 connect_tipc(device: impl AsRef<Path>, service: &str) -> Result<Self>189 fn connect_tipc(device: impl AsRef<Path>, service: &str) -> Result<Self> { 190 let file = File::options().read(true).write(true).open(device)?; 191 192 let srv_name = CString::new(service).expect("Service name contained null bytes"); 193 // SAFETY: The file descriptor is valid because it came from a `File`, and the name is a 194 // valid C string because it came from a `CString`. 195 unsafe { 196 tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?; 197 } 198 199 Ok(Self(file)) 200 } 201 202 /// Sends a message to the connected service. 203 /// 204 /// The entire contents of `buf` will be sent as a single message to the 205 /// connected service. send(&mut self, buf: &[u8]) -> Result<()>206 pub fn send(&mut self, buf: &[u8]) -> Result<()> { 207 let write_len = self.0.write(buf)?; 208 209 // Verify that the expected number of bytes were written. The entire message 210 // should always be written with a single `write` call, or an error should have 211 // been returned if the message couldn't be written. An assertion failure here 212 // potentially means a bug in the kernel driver. 213 assert_eq!( 214 buf.len(), 215 write_len, 216 "Failed to send full message ({} of {} bytes written)", 217 write_len, 218 buf.len(), 219 ); 220 221 Ok(()) 222 } 223 224 /// Reads the next incoming message. 225 /// 226 /// Attempts to read the next incoming message from the connected service if any 227 /// exist. If the initial capacity of `buf` is not enough to hold the incoming 228 /// message the function repeatedly attempts to reserve additional space until 229 /// it is able to fully read the message. 230 /// 231 /// Blocks until there is an incoming message if there is not already a message 232 /// ready to be received. 233 /// 234 /// # Errors 235 /// 236 /// If this function encounters an error of the kind [`ErrorKind::Interrupted`] 237 /// then the error is ignored and the operation will be tried again. 238 /// 239 /// If this function encounters an error with the error code `EMSGSIZE` then 240 /// additional space will be reserved in `buf` and the operation will be tried 241 /// again. 242 /// 243 /// If any other read error is encountered then this function immediately 244 /// returns the error to the caller, and the length of `buf` is set to 0. recv(&mut self, buf: &mut Vec<u8>) -> Result<()>245 pub fn recv(&mut self, buf: &mut Vec<u8>) -> Result<()> { 246 // If no space has been allocated in the buffer reserve enough space to hold any 247 // incoming message. 248 if buf.capacity() == 0 { 249 buf.reserve(MAX_MESSAGE_SIZE); 250 } 251 252 loop { 253 // Resize the vec to make its full capacity available to write into. 254 buf.resize(buf.capacity(), 0); 255 256 match self.0.read(buf.as_mut_slice()) { 257 Ok(len) => { 258 buf.truncate(len); 259 return Ok(()); 260 } 261 262 Err(err) => { 263 if let Some(libc::EMSGSIZE) = err.raw_os_error() { 264 // Ensure that we didn't get `EMSGSIZE` when we already had enough capacity 265 // to contain the maximum message size. This should never happen, but if it 266 // does we don't want to hang by looping infinitely. 267 assert!( 268 buf.capacity() < MAX_MESSAGE_SIZE, 269 "Received `EMSGSIZE` error when buffer capacity was already at maximum", 270 ); 271 272 // If we didn't have enough space to hold the incoming message, reserve 273 // enough space to fit the maximum message size regardless of how much 274 // capacity the buffer already had. 275 buf.reserve(MAX_MESSAGE_SIZE - buf.capacity()); 276 } else if err.kind() == ErrorKind::Interrupted { 277 // If we get an interrupted error the operation can be retried as-is, i.e. 278 // we don't need to allocate additional space. 279 continue; 280 } else { 281 buf.truncate(0); 282 return Err(err); 283 } 284 } 285 } 286 } 287 } 288 289 /// Reads the next incoming message without allocating. 290 /// 291 /// Returns the number of bytes in the received message, or any error that 292 /// occurred when reading the message. 293 /// 294 /// Blocks until there is an incoming message if there is not already a message 295 /// ready to be received. 296 /// 297 /// # Errors 298 /// 299 /// Returns an error with native error code `EMSGSIZE` if `buf` isn't large 300 /// enough to contain the incoming message. Use 301 /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code to 302 /// determine if you need to increase the size of `buf`. If error code 303 /// `EMSGSIZE` is returned the incoming message will not be dropped, and a 304 /// subsequent call to `recv_no_alloc` can still read it. 305 /// 306 /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read 307 /// operation should be retried if there is nothing else to do. recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize>308 pub fn recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize> { 309 self.0.read(buf) 310 } 311 312 // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports 313 // sending shared memory buffers. 314 } 315