1use std::io;
2use std::mem::size_of;
3use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
4
5pub(crate) fn new_ip_socket(addr: SocketAddr, socket_type: libc::c_int) -> io::Result<libc::c_int> {
6 let domain: i32 = match addr {
7 SocketAddr::V4(..) => libc::AF_INET,
8 SocketAddr::V6(..) => libc::AF_INET6,
9 };
10
11 new_socket(domain, socket_type)
12}
13
14/// Create a new non-blocking socket.
15pub(crate) fn new_socket(domain: libc::c_int, socket_type: libc::c_int) -> io::Result<libc::c_int> {
16 #[cfg(any(
17 target_os = "android",
18 target_os = "dragonfly",
19 target_os = "freebsd",
20 target_os = "illumos",
21 target_os = "linux",
22 target_os = "netbsd",
23 target_os = "openbsd",
24 target_os = "solaris",
25 ))]
26 let socket_type = socket_type | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC;
27
28 let socket = syscall!(socket(domain, socket_type, 0))?;
29
30 // Mimick `libstd` and set `SO_NOSIGPIPE` on apple systems.
31 #[cfg(any(
32 target_os = "ios",
33 target_os = "macos",
34 target_os = "tvos",
35 target_os = "watchos",
36 ))]
37 if let Err(err) = syscall!(setsockopt(
38 socket,
39 libc::SOL_SOCKET,
40 libc::SO_NOSIGPIPE,
41 &1 as *const libc::c_int as *const libc::c_void,
42 size_of::<libc::c_int>() as libc::socklen_t
43 )) {
44 let _ = syscall!(close(socket));
45 return Err(err);
46 }
47
48 // Darwin (and others) doesn't have SOCK_NONBLOCK or SOCK_CLOEXEC.
49 #[cfg(any(
50 target_os = "ios",
51 target_os = "macos",
52 target_os = "tvos",
53 target_os = "watchos",
54 target_os = "espidf",
55 target_os = "vita",
56 ))]
57 {
58 if let Err(err) = syscall!(fcntl(socket, libc::F_SETFL, libc::O_NONBLOCK)) {
59 let _ = syscall!(close(socket));
60 return Err(err);
61 }
62 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
63 if let Err(err) = syscall!(fcntl(socket, libc::F_SETFD, libc::FD_CLOEXEC)) {
64 let _ = syscall!(close(socket));
65 return Err(err);
66 }
67 }
68
69 Ok(socket)
70}
71
72/// A type with the same memory layout as `libc::sockaddr`. Used in converting Rust level
73/// SocketAddr* types into their system representation. The benefit of this specific
74/// type over using `libc::sockaddr_storage` is that this type is exactly as large as it
75/// needs to be and not a lot larger. And it can be initialized cleaner from Rust.
76#[repr(C)]
77pub(crate) union SocketAddrCRepr {
78 v4: libc::sockaddr_in,
79 v6: libc::sockaddr_in6,
80}
81
82impl SocketAddrCRepr {
83 pub(crate) fn as_ptr(&self) -> *const libc::sockaddr {
84 self as *const _ as *const libc::sockaddr
85 }
86}
87
88/// Converts a Rust `SocketAddr` into the system representation.
89pub(crate) fn socket_addr(addr: &SocketAddr) -> (SocketAddrCRepr, libc::socklen_t) {
90 match addr {
91 SocketAddr::V4(ref addr) => {
92 // `s_addr` is stored as BE on all machine and the array is in BE order.
93 // So the native endian conversion method is used so that it's never swapped.
94 let sin_addr = libc::in_addr {
95 s_addr: u32::from_ne_bytes(addr.ip().octets()),
96 };
97
98 let sockaddr_in = libc::sockaddr_in {
99 sin_family: libc::AF_INET as libc::sa_family_t,
100 sin_port: addr.port().to_be(),
101 sin_addr,
102 #[cfg(not(target_os = "vita"))]
103 sin_zero: [0; 8],
104 #[cfg(target_os = "vita")]
105 sin_zero: [0; 6],
106 #[cfg(any(
107 target_os = "aix",
108 target_os = "dragonfly",
109 target_os = "freebsd",
110 target_os = "ios",
111 target_os = "macos",
112 target_os = "netbsd",
113 target_os = "openbsd",
114 target_os = "tvos",
115 target_os = "watchos",
116 target_os = "espidf",
117 target_os = "vita",
118 ))]
119 sin_len: 0,
120 #[cfg(target_os = "vita")]
121 sin_vport: addr.port().to_be(),
122 };
123
124 let sockaddr = SocketAddrCRepr { v4: sockaddr_in };
125 let socklen = size_of::<libc::sockaddr_in>() as libc::socklen_t;
126 (sockaddr, socklen)
127 }
128 SocketAddr::V6(ref addr) => {
129 let sockaddr_in6 = libc::sockaddr_in6 {
130 sin6_family: libc::AF_INET6 as libc::sa_family_t,
131 sin6_port: addr.port().to_be(),
132 sin6_addr: libc::in6_addr {
133 s6_addr: addr.ip().octets(),
134 },
135 sin6_flowinfo: addr.flowinfo(),
136 sin6_scope_id: addr.scope_id(),
137 #[cfg(any(
138 target_os = "aix",
139 target_os = "dragonfly",
140 target_os = "freebsd",
141 target_os = "ios",
142 target_os = "macos",
143 target_os = "netbsd",
144 target_os = "openbsd",
145 target_os = "tvos",
146 target_os = "watchos",
147 target_os = "espidf",
148 target_os = "vita",
149 ))]
150 sin6_len: 0,
151 #[cfg(target_os = "vita")]
152 sin6_vport: addr.port().to_be(),
153 #[cfg(any(target_os = "illumos", target_os = "solaris"))]
154 __sin6_src_id: 0,
155 };
156
157 let sockaddr = SocketAddrCRepr { v6: sockaddr_in6 };
158 let socklen = size_of::<libc::sockaddr_in6>() as libc::socklen_t;
159 (sockaddr, socklen)
160 }
161 }
162}
163
164/// Converts a `libc::sockaddr` compatible struct into a native Rust `SocketAddr`.
165///
166/// # Safety
167///
168/// `storage` must have the `ss_family` field correctly initialized.
169/// `storage` must be initialised to a `sockaddr_in` or `sockaddr_in6`.
170pub(crate) unsafe fn to_socket_addr(
171 storage: *const libc::sockaddr_storage,
172) -> io::Result<SocketAddr> {
173 match (*storage).ss_family as libc::c_int {
174 libc::AF_INET => {
175 // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in.
176 let addr: &libc::sockaddr_in = &*(storage as *const libc::sockaddr_in);
177 let ip: Ipv4Addr = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes());
178 let port: u16 = u16::from_be(addr.sin_port);
179 Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
180 }
181 libc::AF_INET6 => {
182 // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6.
183 let addr: &libc::sockaddr_in6 = &*(storage as *const libc::sockaddr_in6);
184 let ip: Ipv6Addr = Ipv6Addr::from(addr.sin6_addr.s6_addr);
185 let port: u16 = u16::from_be(addr.sin6_port);
186 Ok(SocketAddr::V6(SocketAddrV6::new(
187 ip,
188 port,
189 addr.sin6_flowinfo,
190 addr.sin6_scope_id,
191 )))
192 }
193 _ => Err(io::ErrorKind::InvalidInput.into()),
194 }
195}
196