• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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