net_util/sys/linux/
tap.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fs::File;
6use std::io::Read;
7use std::io::Result as IoResult;
8use std::io::Write;
9use std::mem;
10use std::net;
11use std::os::raw::*;
12use std::os::unix::io::AsRawFd;
13use std::os::unix::io::FromRawFd;
14use std::os::unix::io::RawFd;
15
16use base::add_fd_flags;
17use base::error;
18use base::ioctl_with_mut_ref;
19use base::ioctl_with_ref;
20use base::ioctl_with_val;
21use base::volatile_impl;
22use base::warn;
23use base::AsRawDescriptor;
24use base::Error as SysError;
25use base::FileReadWriteVolatile;
26use base::FromRawDescriptor;
27use base::IoctlNr;
28use base::RawDescriptor;
29use base::ReadNotifier;
30use cros_async::IntoAsync;
31
32use crate::sys::linux::TapTLinux;
33use crate::Error;
34use crate::MacAddress;
35use crate::Result;
36use crate::TapT;
37use crate::TapTCommon;
38
39/// Handle for a network tap interface.
40///
41/// For now, this simply wraps the file descriptor for the tap device so methods
42/// can run ioctls on the interface. The tap interface descriptor will be closed when
43/// Tap goes out of scope, and the kernel will clean up the interface
44/// automatically.
45#[derive(Debug)]
46pub struct Tap {
47    tap_file: File,
48    if_name: [c_char; 16usize],
49    if_flags: ::std::os::raw::c_short,
50}
51
52impl Tap {
53    /// # Safety
54    /// 1. descriptor's ownership must be released by the caller. It is now owned by the returned
55    ///    value (`Tap`), or is closed (if an error is returned).
56    pub unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Result<Tap> {
57        let tap_file = File::from_raw_descriptor(descriptor);
58
59        // Ensure that the file is opened non-blocking, otherwise
60        // ipvtaps with shell-provided FDs are very slow.
61        add_fd_flags(tap_file.as_raw_descriptor(), libc::O_NONBLOCK).map_err(Error::IoctlError)?;
62
63        // Get the interface name since we will need it for some ioctls.
64        let mut ifreq: net_sys::ifreq = Default::default();
65        let ret = ioctl_with_mut_ref(&tap_file, net_sys::TUNGETIFF, &mut ifreq);
66
67        if ret < 0 {
68            return Err(Error::IoctlError(SysError::last()));
69        }
70
71        Ok(Tap {
72            tap_file,
73            if_name: ifreq.ifr_ifrn.ifrn_name,
74            if_flags: ifreq.ifr_ifru.ifru_flags,
75        })
76    }
77
78    pub fn create_tap_with_ifreq(ifreq: &mut net_sys::ifreq) -> Result<Tap> {
79        // SAFETY:
80        // Open calls are safe because we give a constant nul-terminated
81        // string and verify the result.
82        let rd = unsafe {
83            libc::open64(
84                c"/dev/net/tun".as_ptr() as *const c_char,
85                libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
86            )
87        };
88        if rd < 0 {
89            return Err(Error::OpenTun(SysError::last()));
90        }
91
92        // SAFETY:
93        // We just checked that the fd is valid.
94        let tuntap = unsafe { File::from_raw_descriptor(rd) };
95        // SAFETY:
96        // ioctl is safe since we call it with a valid tap fd and check the return
97        // value.
98        let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_sys::TUNSETIFF, ifreq) };
99
100        if ret < 0 {
101            return Err(Error::CreateTap(SysError::last()));
102        }
103
104        Ok(Tap {
105            tap_file: tuntap,
106            // SAFETY:
107            // Safe since only the name is accessed, and it's copied out.
108            if_name: unsafe { ifreq.ifr_ifrn.ifrn_name },
109            // SAFETY:
110            // Safe since only the name is accessed, and it's copied out.
111            if_flags: unsafe { ifreq.ifr_ifru.ifru_flags },
112        })
113    }
114
115    fn get_ifreq(&self) -> net_sys::ifreq {
116        let mut ifreq: net_sys::ifreq = Default::default();
117
118        // SAFETY:
119        // This sets the name of the interface, which is the only entry
120        // in a single-field union.
121        unsafe {
122            let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
123            ifrn_name.clone_from_slice(&self.if_name);
124        }
125
126        // This sets the flags with which the interface was created, which is the only entry we set
127        // on the second union.
128        ifreq.ifr_ifru.ifru_flags = self.if_flags;
129
130        ifreq
131    }
132
133    pub fn try_clone(&self) -> Result<Tap> {
134        self.tap_file
135            .try_clone()
136            .map(|tap_file| Tap {
137                tap_file,
138                if_name: self.if_name,
139                if_flags: self.if_flags,
140            })
141            .map_err(SysError::from)
142            .map_err(Error::CloneTap)
143    }
144}
145
146impl TapTCommon for Tap {
147    /// Create a new tap interface.
148    ///
149    /// Set the `vnet_hdr` flag to true to allow offloading on this tap,
150    /// which will add an extra 12 byte virtio net header to incoming frames. Offloading cannot
151    /// be used if `vnet_hdr` is false.
152    /// Set 'multi_vq' to true, if tap have multi virt queue pairs
153    fn new(vnet_hdr: bool, multi_vq: bool) -> Result<Self> {
154        const TUNTAP_DEV_FORMAT: &[u8] = b"vmtap%d";
155        Self::new_with_name(TUNTAP_DEV_FORMAT, vnet_hdr, multi_vq)
156    }
157
158    fn new_with_name(name: &[u8], vnet_hdr: bool, multi_vq: bool) -> Result<Tap> {
159        let mut ifreq: net_sys::ifreq = Default::default();
160        // SAFETY:
161        // This is pretty messy because of the unions used by ifreq. Since we
162        // don't call as_mut on the same union field more than once, this block
163        // is safe.
164        unsafe {
165            let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
166            for (dst, src) in ifrn_name
167                .iter_mut()
168                // Add a zero terminator to the source string.
169                .zip(name.iter().chain(std::iter::once(&0)))
170            {
171                *dst = *src as c_char;
172            }
173            ifreq.ifr_ifru.ifru_flags =
174                (libc::IFF_TAP | libc::IFF_NO_PI | if vnet_hdr { libc::IFF_VNET_HDR } else { 0 })
175                    as c_short;
176            if multi_vq {
177                ifreq.ifr_ifru.ifru_flags |= libc::IFF_MULTI_QUEUE as c_short;
178            }
179        }
180
181        Tap::create_tap_with_ifreq(&mut ifreq)
182    }
183
184    fn into_mq_taps(self, vq_pairs: u16) -> Result<Vec<Tap>> {
185        let mut taps: Vec<Tap> = Vec::new();
186
187        if vq_pairs <= 1 {
188            taps.push(self);
189            return Ok(taps);
190        }
191
192        // Add other socket into the origin tap interface
193        for _ in 0..vq_pairs - 1 {
194            let mut ifreq = self.get_ifreq();
195            let tap = Tap::create_tap_with_ifreq(&mut ifreq)?;
196
197            tap.enable()?;
198
199            taps.push(tap);
200        }
201
202        taps.insert(0, self);
203        Ok(taps)
204    }
205
206    fn ip_addr(&self) -> Result<net::Ipv4Addr> {
207        let sock = create_socket()?;
208        let mut ifreq = self.get_ifreq();
209
210        // SAFETY:
211        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
212        let ret = unsafe {
213            ioctl_with_mut_ref(&sock, net_sys::sockios::SIOCGIFADDR as IoctlNr, &mut ifreq)
214        };
215
216        if ret < 0 {
217            return Err(Error::IoctlError(SysError::last()));
218        }
219
220        // SAFETY:
221        // We only access one field of the ifru union, hence this is safe.
222        let addr = unsafe { ifreq.ifr_ifru.ifru_addr };
223
224        Ok(read_ipv4_addr(&addr))
225    }
226
227    fn set_ip_addr(&self, ip_addr: net::Ipv4Addr) -> Result<()> {
228        let sock = create_socket()?;
229        let addr = create_sockaddr(ip_addr);
230
231        let mut ifreq = self.get_ifreq();
232        ifreq.ifr_ifru.ifru_addr = addr;
233
234        let ret =
235        // SAFETY:
236        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
237            unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFADDR as IoctlNr, &ifreq) };
238        if ret < 0 {
239            return Err(Error::IoctlError(SysError::last()));
240        }
241
242        Ok(())
243    }
244
245    fn netmask(&self) -> Result<net::Ipv4Addr> {
246        let sock = create_socket()?;
247        let mut ifreq = self.get_ifreq();
248
249        // SAFETY:
250        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
251        let ret = unsafe {
252            ioctl_with_mut_ref(
253                &sock,
254                net_sys::sockios::SIOCGIFNETMASK as IoctlNr,
255                &mut ifreq,
256            )
257        };
258        if ret < 0 {
259            return Err(Error::IoctlError(SysError::last()));
260        }
261
262        // SAFETY:
263        // We only access one field of the ifru union, hence this is safe.
264        let addr = unsafe { ifreq.ifr_ifru.ifru_netmask };
265
266        Ok(read_ipv4_addr(&addr))
267    }
268
269    fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
270        let sock = create_socket()?;
271        let addr = create_sockaddr(netmask);
272
273        let mut ifreq = self.get_ifreq();
274        ifreq.ifr_ifru.ifru_netmask = addr;
275
276        let ret =
277        // SAFETY:
278        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
279            unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFNETMASK as IoctlNr, &ifreq) };
280        if ret < 0 {
281            return Err(Error::IoctlError(SysError::last()));
282        }
283
284        Ok(())
285    }
286
287    fn mtu(&self) -> Result<u16> {
288        let sock = create_socket()?;
289        let mut ifreq = self.get_ifreq();
290
291        // SAFETY:
292        // ioctl is safe. Called with a valid sock fd, and we check the return.
293        let ret = unsafe {
294            ioctl_with_mut_ref(&sock, net_sys::sockios::SIOCGIFMTU as IoctlNr, &mut ifreq)
295        };
296        if ret < 0 {
297            return Err(Error::IoctlError(SysError::last()));
298        }
299
300        // SAFETY:
301        // We only access one field of the ifru union, hence this is safe.
302        let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu } as u16;
303        Ok(mtu)
304    }
305
306    fn set_mtu(&self, mtu: u16) -> Result<()> {
307        let sock = create_socket()?;
308
309        let mut ifreq = self.get_ifreq();
310        ifreq.ifr_ifru.ifru_mtu = i32::from(mtu);
311
312        // SAFETY:
313        // ioctl is safe. Called with a valid sock fd, and we check the return.
314        let ret = unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFMTU as IoctlNr, &ifreq) };
315        if ret < 0 {
316            return Err(Error::IoctlError(SysError::last()));
317        }
318
319        Ok(())
320    }
321
322    fn mac_address(&self) -> Result<MacAddress> {
323        let sock = create_socket()?;
324        let mut ifreq = self.get_ifreq();
325
326        // SAFETY:
327        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
328        let ret = unsafe {
329            ioctl_with_mut_ref(
330                &sock,
331                net_sys::sockios::SIOCGIFHWADDR as IoctlNr,
332                &mut ifreq,
333            )
334        };
335        if ret < 0 {
336            return Err(Error::IoctlError(SysError::last()));
337        }
338
339        // SAFETY:
340        // We only access one field of the ifru union, hence this is safe.
341        let sa: libc::sockaddr = unsafe { ifreq.ifr_ifru.ifru_hwaddr };
342
343        if sa.sa_family != libc::ARPHRD_ETHER {
344            return Err(crate::Error::IoctlError(base::Error::new(libc::EINVAL)));
345        }
346
347        let mut mac = MacAddress::default();
348
349        #[allow(clippy::unnecessary_cast)] // c_char is u8 on some platforms and i8 on others
350        for (mac_addr, sa_data) in mac.addr.iter_mut().zip(sa.sa_data.iter()) {
351            *mac_addr = *sa_data as u8;
352        }
353
354        Ok(mac)
355    }
356
357    fn set_mac_address(&self, mac_addr: MacAddress) -> Result<()> {
358        let mut sa = libc::sockaddr {
359            sa_family: libc::ARPHRD_ETHER,
360            sa_data: Default::default(),
361        };
362
363        #[allow(clippy::unnecessary_cast)] // c_char is u8 on some platforms and i8 on others
364        for (sa_data, mac) in sa
365            .sa_data
366            .iter_mut()
367            .zip(mac_addr.octets().iter().chain(std::iter::repeat(&0)))
368        {
369            *sa_data = *mac as c_char;
370        }
371
372        let sock = create_socket()?;
373
374        let mut ifreq = self.get_ifreq();
375        ifreq.ifr_ifru.ifru_hwaddr = sa;
376
377        let ret =
378        // SAFETY:
379        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
380            unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFHWADDR as IoctlNr, &ifreq) };
381        if ret < 0 {
382            return Err(Error::IoctlError(SysError::last()));
383        }
384
385        Ok(())
386    }
387
388    fn set_offload(&self, flags: c_uint) -> Result<()> {
389        let ret =
390        // SAFETY:
391        // ioctl is safe. Called with a valid tap descriptor, and we check the return.
392            unsafe { ioctl_with_val(&self.tap_file, net_sys::TUNSETOFFLOAD, flags as c_ulong) };
393        if ret < 0 {
394            return Err(Error::IoctlError(SysError::last()));
395        }
396
397        Ok(())
398    }
399
400    fn enable(&self) -> Result<()> {
401        let sock = create_socket()?;
402
403        let mut ifreq = self.get_ifreq();
404        ifreq.ifr_ifru.ifru_flags =
405            (net_sys::net_device_flags::IFF_UP | net_sys::net_device_flags::IFF_RUNNING).0 as i16;
406
407        let ret =
408        // SAFETY:
409        // ioctl is safe. Called with a valid sock descriptor, and we check the return.
410            unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFFLAGS as IoctlNr, &ifreq) };
411        if ret < 0 {
412            return Err(Error::IoctlError(SysError::last()));
413        }
414
415        Ok(())
416    }
417
418    fn try_clone(&self) -> Result<Self> {
419        self.try_clone()
420    }
421
422    // SAFETY:
423    // Safe if caller provides a valid descriptor.
424    unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Result<Self> {
425        Tap::from_raw_descriptor(descriptor)
426    }
427}
428
429impl TapTLinux for Tap {
430    fn set_vnet_hdr_size(&self, size: usize) -> Result<()> {
431        let size = size as c_int;
432        // SAFETY:
433        // ioctl is safe. Called with a valid tap descriptor, and we check the return.
434        let ret = unsafe { ioctl_with_ref(&self.tap_file, net_sys::TUNSETVNETHDRSZ, &size) };
435        if ret < 0 {
436            return Err(Error::IoctlError(SysError::last()));
437        }
438
439        Ok(())
440    }
441
442    fn if_flags(&self) -> u32 {
443        self.if_flags as u32
444    }
445}
446
447impl Read for Tap {
448    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
449        self.tap_file.read(buf)
450    }
451}
452
453impl Write for Tap {
454    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
455        self.tap_file.write(buf)
456    }
457
458    fn flush(&mut self) -> IoResult<()> {
459        Ok(())
460    }
461}
462
463impl AsRawFd for Tap {
464    fn as_raw_fd(&self) -> RawFd {
465        self.tap_file.as_raw_descriptor()
466    }
467}
468
469impl AsRawDescriptor for Tap {
470    fn as_raw_descriptor(&self) -> RawDescriptor {
471        self.tap_file.as_raw_descriptor()
472    }
473}
474
475impl ReadNotifier for Tap {
476    fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
477        self
478    }
479}
480
481fn create_socket() -> Result<net::UdpSocket> {
482    // SAFETY:
483    // This is safe since we check the return value.
484    let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
485    if sock >= 0 {
486        // SAFETY:
487        // This is safe; nothing else will use or hold onto the raw sock descriptor.
488        return Ok(unsafe { net::UdpSocket::from_raw_fd(sock) });
489    }
490
491    warn!("INET not supported on this machine. Trying to open an INET6 socket.");
492
493    // SAFETY:
494    // Open an AF_INET6 socket
495    let sock6 = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0) };
496    if sock6 >= 0 {
497        // SAFETY:
498        // This is safe; nothing else will use or hold onto the raw sock descriptor.
499        return Ok(unsafe { net::UdpSocket::from_raw_fd(sock6) });
500    }
501
502    error!("Neither INET nor INET6 supported on this machine");
503
504    Err(Error::CreateSocket(SysError::last()))
505}
506
507fn sockaddr_from_sockaddr_in(addr_in: libc::sockaddr_in) -> libc::sockaddr {
508    assert_eq!(
509        mem::size_of::<libc::sockaddr_in>(),
510        mem::size_of::<libc::sockaddr>()
511    );
512
513    // SAFETY: trivially safe
514    unsafe { mem::transmute::<libc::sockaddr_in, libc::sockaddr>(addr_in) }
515}
516
517fn sockaddr_in_from_sockaddr(addr: libc::sockaddr) -> Option<libc::sockaddr_in> {
518    if addr.sa_family as i32 != libc::AF_INET {
519        return None;
520    }
521
522    assert_eq!(
523        mem::size_of::<libc::sockaddr_in>(),
524        mem::size_of::<libc::sockaddr>()
525    );
526
527    // SAFETY:
528    // This is safe because sockaddr and sockaddr_in are the same size, and we've checked that
529    // this address is AF_INET.
530    Some(unsafe { mem::transmute::<libc::sockaddr, libc::sockaddr_in>(addr) })
531}
532
533/// Create a sockaddr_in from an IPv4 address, and expose it as
534/// an opaque sockaddr suitable for usage by socket ioctls.
535fn create_sockaddr(ip_addr: net::Ipv4Addr) -> libc::sockaddr {
536    let addr_in = libc::sockaddr_in {
537        sin_family: libc::AF_INET as u16,
538        sin_port: 0,
539        sin_addr: libc::in_addr {
540            // `Ipv4Addr::octets()` returns the address in network byte order, so use
541            // `from_be_bytes()` to convert it into the native endianness, then `to_be()` to convert
542            // it back into big-endian (network) byte order as required by `sockaddr_in`. This is
543            // effectively a no-op, and we could use `u32::from_ne_bytes()` instead, but it is
544            // easier to understand when written this way.
545            s_addr: u32::from_be_bytes(ip_addr.octets()).to_be(),
546        },
547        sin_zero: [0; 8usize],
548    };
549
550    sockaddr_from_sockaddr_in(addr_in)
551}
552
553/// Extract the IPv4 address from a sockaddr. Assumes the sockaddr is a sockaddr_in.
554fn read_ipv4_addr(addr: &libc::sockaddr) -> net::Ipv4Addr {
555    let in_addr = sockaddr_in_from_sockaddr(*addr).unwrap();
556    net::Ipv4Addr::from(in_addr.sin_addr.s_addr)
557}
558
559impl TapT for Tap {}
560impl IntoAsync for Tap {}
561volatile_impl!(Tap);
562
563pub mod fakes {
564    use std::fs::remove_file;
565    use std::fs::OpenOptions;
566
567    use super::*;
568
569    const TMP_FILE: &str = "/tmp/crosvm_tap_test_file";
570
571    pub struct FakeTap {
572        tap_file: File,
573    }
574
575    impl TapTCommon for FakeTap {
576        fn new(_vnet_hdr: bool, _multi_vq: bool) -> Result<Self> {
577            // Params don't matter
578            Self::new_with_name(b"", false, false)
579        }
580
581        fn new_with_name(_: &[u8], _: bool, _: bool) -> Result<FakeTap> {
582            Ok(FakeTap {
583                tap_file: OpenOptions::new()
584                    .read(true)
585                    .append(true)
586                    .create(true)
587                    .open(TMP_FILE)
588                    .unwrap(),
589            })
590        }
591
592        fn into_mq_taps(self, _vq_pairs: u16) -> Result<Vec<FakeTap>> {
593            Ok(Vec::new())
594        }
595
596        fn ip_addr(&self) -> Result<net::Ipv4Addr> {
597            Ok(net::Ipv4Addr::new(1, 2, 3, 4))
598        }
599
600        fn set_ip_addr(&self, _: net::Ipv4Addr) -> Result<()> {
601            Ok(())
602        }
603
604        fn netmask(&self) -> Result<net::Ipv4Addr> {
605            Ok(net::Ipv4Addr::new(255, 255, 255, 252))
606        }
607
608        fn set_netmask(&self, _: net::Ipv4Addr) -> Result<()> {
609            Ok(())
610        }
611
612        fn mtu(&self) -> Result<u16> {
613            Ok(1500)
614        }
615
616        fn set_mtu(&self, _: u16) -> Result<()> {
617            Ok(())
618        }
619
620        fn mac_address(&self) -> Result<MacAddress> {
621            Ok("01:02:03:04:05:06".parse().unwrap())
622        }
623
624        fn set_mac_address(&self, _: MacAddress) -> Result<()> {
625            Ok(())
626        }
627
628        fn set_offload(&self, _: c_uint) -> Result<()> {
629            Ok(())
630        }
631
632        fn enable(&self) -> Result<()> {
633            Ok(())
634        }
635
636        // Return self so it can compile
637        fn try_clone(&self) -> Result<Self> {
638            Ok(FakeTap {
639                tap_file: self.tap_file.try_clone().unwrap(),
640            })
641        }
642
643        unsafe fn from_raw_descriptor(_descriptor: RawDescriptor) -> Result<Self> {
644            unimplemented!()
645        }
646    }
647
648    impl TapTLinux for FakeTap {
649        fn set_vnet_hdr_size(&self, _: usize) -> Result<()> {
650            Ok(())
651        }
652
653        fn if_flags(&self) -> u32 {
654            net_sys::IFF_TAP
655        }
656    }
657
658    impl Drop for FakeTap {
659        fn drop(&mut self) {
660            let _ = remove_file(TMP_FILE);
661        }
662    }
663
664    impl Read for FakeTap {
665        fn read(&mut self, _: &mut [u8]) -> IoResult<usize> {
666            Ok(0)
667        }
668    }
669
670    impl Write for FakeTap {
671        fn write(&mut self, _: &[u8]) -> IoResult<usize> {
672            Ok(0)
673        }
674
675        fn flush(&mut self) -> IoResult<()> {
676            Ok(())
677        }
678    }
679
680    impl AsRawFd for FakeTap {
681        fn as_raw_fd(&self) -> RawFd {
682            self.tap_file.as_raw_descriptor()
683        }
684    }
685
686    impl AsRawDescriptor for FakeTap {
687        fn as_raw_descriptor(&self) -> RawDescriptor {
688            self.tap_file.as_raw_descriptor()
689        }
690    }
691    impl TapT for FakeTap {}
692    volatile_impl!(FakeTap);
693}
694
695#[cfg(test)]
696pub mod tests {
697    use super::*;
698
699    #[test]
700    fn sockaddr_byte_order() {
701        let sa = create_sockaddr(net::Ipv4Addr::new(1, 2, 3, 4));
702        assert_eq!(sa.sa_family, 2); // AF_INET
703        assert_eq!(
704            sa.sa_data,
705            [
706                0, 0, // sin_port
707                1, 2, 3, 4, // sin_addr
708                0, 0, 0, 0, 0, 0, 0, 0, // sin_zero
709            ]
710        );
711    }
712}