erbium_net/
udp.rs

1/*   Copyright 2023 Perry Lorier
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 *  SPDX-License-Identifier: Apache-2.0
16 *
17 *  An async UdpSocket type with recvmsg / sendmsg support.
18 */
19
20// We desperately need recvmsg / sendmsg support, and rust doesn't support it, so we need *yet
21// another* udp socket type.
22//
23// This should not export libc::, nix:: or tokio:: types, only std:: and it's own types to insulate
24// the rest of the program of the horrors of portability.
25
26use crate::addr::NetAddr;
27use std::convert::TryFrom;
28use std::io;
29use std::net;
30use std::os::unix::io::AsRawFd as _;
31use tokio::io::unix::AsyncFd;
32
33use nix::libc;
34
35pub struct UdpSocket {
36    fd: AsyncFd<mio::net::UdpSocket>,
37}
38
39pub type MsgFlags = crate::socket::MsgFlags;
40pub type ControlMessage = crate::socket::ControlMessage;
41pub type RecvMsg = crate::socket::RecvMsg;
42
43pub fn std_to_libc_in_addr(addr: net::Ipv4Addr) -> libc::in_addr {
44    libc::in_addr {
45        s_addr: addr
46            .octets()
47            .iter()
48            .fold(0, |acc, x| ((acc << 8) | (*x as u32))),
49    }
50}
51
52pub const fn std_to_libc_in6_addr(addr: net::Ipv6Addr) -> libc::in6_addr {
53    libc::in6_addr {
54        s6_addr: addr.octets(),
55    }
56}
57
58impl TryFrom<mio::net::UdpSocket> for UdpSocket {
59    type Error = io::Error;
60    fn try_from(s: mio::net::UdpSocket) -> Result<Self, Self::Error> {
61        Ok(UdpSocket {
62            fd: AsyncFd::new(s)?,
63        })
64    }
65}
66
67impl UdpSocket {
68    pub async fn bind(addrs: &[NetAddr]) -> Result<Self, io::Error> {
69        use crate::addr::NetAddrExt as _;
70        let mut last_err = None;
71
72        for addr in addrs {
73            match mio::net::UdpSocket::bind(addr.to_std_socket_addr().unwrap()) {
74                Ok(socket) => return Self::try_from(socket),
75                Err(e) => last_err = Some(e),
76            }
77        }
78
79        Err(last_err.unwrap_or_else(|| {
80            io::Error::new(
81                io::ErrorKind::InvalidInput,
82                "could not resolve to any address",
83            )
84        }))
85    }
86
87    pub async fn recv_msg(&self, bufsize: usize, flags: MsgFlags) -> io::Result<RecvMsg> {
88        crate::socket::recv_msg(&self.fd, bufsize, flags).await
89    }
90
91    pub async fn send_msg(
92        &self,
93        buffer: &[u8],
94        cmsg: &ControlMessage,
95        flags: MsgFlags,
96        addr: Option<&NetAddr>,
97    ) -> io::Result<()> {
98        crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await
99    }
100
101    pub fn local_addr(&self) -> Result<NetAddr, io::Error> {
102        self.fd.get_ref().local_addr().map(|x| x.into())
103    }
104
105    pub fn set_opt_ipv4_packet_info(&self, b: bool) -> Result<(), io::Error> {
106        nix::sys::socket::setsockopt(
107            self.fd.get_ref().as_raw_fd(),
108            nix::sys::socket::sockopt::Ipv4PacketInfo,
109            &b,
110        )
111        .map_err(|e| e.into())
112    }
113
114    pub fn set_opt_ipv6_packet_info(&self, b: bool) -> Result<(), io::Error> {
115        nix::sys::socket::setsockopt(
116            self.fd.get_ref().as_raw_fd(),
117            nix::sys::socket::sockopt::Ipv6RecvPacketInfo,
118            &b,
119        )
120        .map_err(|e| e.into())
121    }
122
123    pub fn set_opt_reuse_port(&self, b: bool) -> Result<(), io::Error> {
124        nix::sys::socket::setsockopt(
125            self.fd.get_ref().as_raw_fd(),
126            nix::sys::socket::sockopt::ReusePort,
127            &b,
128        )
129        .map_err(|e| e.into())
130    }
131}