mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
3482 lines
121 KiB
Rust
3482 lines
121 KiB
Rust
// spell-checker:disable
|
|
|
|
pub(crate) use _socket::module_def;
|
|
|
|
#[cfg(feature = "ssl")]
|
|
pub(super) use _socket::{PySocket, SelectKind, sock_select, timeout_error_msg};
|
|
|
|
#[pymodule]
|
|
mod _socket {
|
|
use crate::common::lock::{PyMappedRwLockReadGuard, PyRwLock, PyRwLockReadGuard};
|
|
use crate::vm::{
|
|
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
|
builtins::{
|
|
PyBaseExceptionRef, PyListRef, PyModule, PyOSError, PyStrRef, PyTupleRef, PyTypeRef,
|
|
},
|
|
common::os::ErrorExt,
|
|
convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject},
|
|
function::{
|
|
ArgBytesLike, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath,
|
|
OptionalArg, OptionalOption,
|
|
},
|
|
types::{Constructor, DefaultConstructor, Initializer, Representable},
|
|
utils::ToCString,
|
|
};
|
|
|
|
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
|
|
#[cfg(windows)]
|
|
crate::vm::windows::init_winsock();
|
|
|
|
__module_exec(vm, module);
|
|
Ok(())
|
|
}
|
|
use core::{
|
|
mem::MaybeUninit,
|
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
|
|
time::Duration,
|
|
};
|
|
use crossbeam_utils::atomic::AtomicCell;
|
|
use num_traits::ToPrimitive;
|
|
use socket2::Socket;
|
|
use std::{
|
|
ffi,
|
|
io::{self, Read, Write},
|
|
net::{self, Shutdown, ToSocketAddrs},
|
|
time::Instant,
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
use libc as c;
|
|
#[cfg(windows)]
|
|
mod c {
|
|
pub use windows_sys::Win32::NetworkManagement::IpHelper::{if_indextoname, if_nametoindex};
|
|
pub use windows_sys::Win32::Networking::WinSock::{
|
|
INADDR_ANY, INADDR_BROADCAST, INADDR_LOOPBACK, INADDR_NONE,
|
|
};
|
|
|
|
pub use windows_sys::Win32::Networking::WinSock::{
|
|
AF_APPLETALK, AF_DECnet, AF_IPX, AF_LINK, AI_ADDRCONFIG, AI_ALL, AI_CANONNAME,
|
|
AI_NUMERICSERV, AI_V4MAPPED, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_HDRINCL,
|
|
IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_OPTIONS, IP_RECVDSTADDR,
|
|
IP_TOS, IP_TTL, IPPORT_RESERVED, IPPROTO_AH, IPPROTO_DSTOPTS, IPPROTO_EGP, IPPROTO_ESP,
|
|
IPPROTO_FRAGMENT, IPPROTO_GGP, IPPROTO_HOPOPTS, IPPROTO_ICMP, IPPROTO_ICMPV6,
|
|
IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IP, IPPROTO_IP as IPPROTO_IPIP, IPPROTO_IPV4,
|
|
IPPROTO_IPV6, IPPROTO_ND, IPPROTO_NONE, IPPROTO_PIM, IPPROTO_PUP, IPPROTO_RAW,
|
|
IPPROTO_ROUTING, IPPROTO_TCP, IPPROTO_UDP, IPV6_CHECKSUM, IPV6_DONTFRAG, IPV6_HOPLIMIT,
|
|
IPV6_HOPOPTS, IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS,
|
|
IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_PKTINFO, IPV6_RECVRTHDR, IPV6_RECVTCLASS,
|
|
IPV6_RTHDR, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, MSG_BCAST, MSG_CTRUNC,
|
|
MSG_DONTROUTE, MSG_MCAST, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, NI_DGRAM,
|
|
NI_MAXHOST, NI_MAXSERV, NI_NAMEREQD, NI_NOFQDN, NI_NUMERICHOST, NI_NUMERICSERV,
|
|
RCVALL_IPLEVEL, RCVALL_OFF, RCVALL_ON, RCVALL_SOCKETLEVELONLY, SD_BOTH as SHUT_RDWR,
|
|
SD_RECEIVE as SHUT_RD, SD_SEND as SHUT_WR, SIO_KEEPALIVE_VALS, SIO_LOOPBACK_FAST_PATH,
|
|
SIO_RCVALL, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF,
|
|
SO_REUSEADDR, SO_SNDBUF, SO_TYPE, SO_USELOOPBACK, SOCK_DGRAM, SOCK_RAW, SOCK_RDM,
|
|
SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SOMAXCONN, TCP_NODELAY, WSAEBADF,
|
|
WSAECONNRESET, WSAENOTSOCK, WSAEWOULDBLOCK,
|
|
};
|
|
pub use windows_sys::Win32::Networking::WinSock::{
|
|
INVALID_SOCKET, SOCKET_ERROR, WSA_FLAG_OVERLAPPED, WSADuplicateSocketW,
|
|
WSAGetLastError, WSAIoctl, WSAPROTOCOL_INFOW, WSASocketW,
|
|
};
|
|
pub use windows_sys::Win32::Networking::WinSock::{
|
|
SO_REUSEADDR as SO_EXCLUSIVEADDRUSE, getprotobyname, getservbyname, getservbyport,
|
|
getsockopt, setsockopt,
|
|
};
|
|
pub use windows_sys::Win32::Networking::WinSock::{
|
|
WSA_NOT_ENOUGH_MEMORY as EAI_MEMORY, WSAEAFNOSUPPORT as EAI_FAMILY,
|
|
WSAEINVAL as EAI_BADFLAGS, WSAESOCKTNOSUPPORT as EAI_SOCKTYPE,
|
|
WSAHOST_NOT_FOUND as EAI_NODATA, WSAHOST_NOT_FOUND as EAI_NONAME,
|
|
WSANO_RECOVERY as EAI_FAIL, WSATRY_AGAIN as EAI_AGAIN,
|
|
WSATYPE_NOT_FOUND as EAI_SERVICE,
|
|
};
|
|
pub const IF_NAMESIZE: usize =
|
|
windows_sys::Win32::NetworkManagement::Ndis::IF_MAX_STRING_SIZE as _;
|
|
pub const AF_UNSPEC: i32 = windows_sys::Win32::Networking::WinSock::AF_UNSPEC as _;
|
|
pub const AF_INET: i32 = windows_sys::Win32::Networking::WinSock::AF_INET as _;
|
|
pub const AF_INET6: i32 = windows_sys::Win32::Networking::WinSock::AF_INET6 as _;
|
|
pub const AI_PASSIVE: i32 = windows_sys::Win32::Networking::WinSock::AI_PASSIVE as _;
|
|
pub const AI_NUMERICHOST: i32 =
|
|
windows_sys::Win32::Networking::WinSock::AI_NUMERICHOST as _;
|
|
pub const FROM_PROTOCOL_INFO: i32 = -1;
|
|
}
|
|
// constants
|
|
#[pyattr(name = "has_ipv6")]
|
|
const HAS_IPV6: bool = true;
|
|
#[pyattr]
|
|
// put IPPROTO_MAX later
|
|
use c::{
|
|
AF_INET, AF_INET6, AF_UNSPEC, INADDR_ANY, INADDR_LOOPBACK, INADDR_NONE, IPPROTO_ICMP,
|
|
IPPROTO_ICMPV6, IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP, IPPROTO_TCP as SOL_TCP, IPPROTO_UDP,
|
|
MSG_CTRUNC, MSG_DONTROUTE, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, NI_DGRAM, NI_MAXHOST,
|
|
NI_NAMEREQD, NI_NOFQDN, NI_NUMERICHOST, NI_NUMERICSERV, SHUT_RD, SHUT_RDWR, SHUT_WR,
|
|
SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF, SO_REUSEADDR,
|
|
SO_SNDBUF, SO_TYPE, SOCK_DGRAM, SOCK_STREAM, SOL_SOCKET, TCP_NODELAY,
|
|
};
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
#[pyattr]
|
|
use c::{
|
|
AF_APPLETALK, AF_DECnet, AF_IPX, IPPROTO_AH, IPPROTO_DSTOPTS, IPPROTO_EGP, IPPROTO_ESP,
|
|
IPPROTO_FRAGMENT, IPPROTO_HOPOPTS, IPPROTO_IDP, IPPROTO_IGMP, IPPROTO_IPIP, IPPROTO_NONE,
|
|
IPPROTO_PIM, IPPROTO_PUP, IPPROTO_RAW, IPPROTO_ROUTING,
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
#[pyattr]
|
|
use c::{AF_UNIX, SO_REUSEPORT};
|
|
|
|
#[pyattr]
|
|
use c::{AI_ADDRCONFIG, AI_NUMERICHOST, AI_NUMERICSERV, AI_PASSIVE};
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
#[pyattr]
|
|
use c::{SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET};
|
|
|
|
#[cfg(target_os = "android")]
|
|
#[pyattr]
|
|
use c::{SOL_ATALK, SOL_AX25, SOL_IPX, SOL_NETROM, SOL_ROSE};
|
|
|
|
#[cfg(target_os = "freebsd")]
|
|
#[pyattr]
|
|
use c::SO_SETFIB;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
use c::{
|
|
CAN_BCM, CAN_EFF_FLAG, CAN_EFF_MASK, CAN_ERR_FLAG, CAN_ERR_MASK, CAN_ISOTP, CAN_J1939,
|
|
CAN_RAW, CAN_RAW_ERR_FILTER, CAN_RAW_FD_FRAMES, CAN_RAW_FILTER, CAN_RAW_JOIN_FILTERS,
|
|
CAN_RAW_LOOPBACK, CAN_RAW_RECV_OWN_MSGS, CAN_RTR_FLAG, CAN_SFF_MASK, IPPROTO_MPTCP,
|
|
J1939_IDLE_ADDR, J1939_MAX_UNICAST_ADDR, J1939_NLA_BYTES_ACKED, J1939_NLA_PAD,
|
|
J1939_NO_ADDR, J1939_NO_NAME, J1939_NO_PGN, J1939_PGN_ADDRESS_CLAIMED,
|
|
J1939_PGN_ADDRESS_COMMANDED, J1939_PGN_MAX, J1939_PGN_PDU1_MAX, J1939_PGN_REQUEST,
|
|
SCM_J1939_DEST_ADDR, SCM_J1939_DEST_NAME, SCM_J1939_ERRQUEUE, SCM_J1939_PRIO,
|
|
SO_J1939_ERRQUEUE, SO_J1939_FILTER, SO_J1939_PROMISC, SO_J1939_SEND_PRIO, SOL_CAN_BASE,
|
|
SOL_CAN_RAW,
|
|
};
|
|
|
|
// CAN BCM opcodes
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_SETUP: i32 = 1;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_DELETE: i32 = 2;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_READ: i32 = 3;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_SEND: i32 = 4;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_SETUP: i32 = 5;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_DELETE: i32 = 6;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_READ: i32 = 7;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_STATUS: i32 = 8;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_EXPIRED: i32 = 9;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_STATUS: i32 = 10;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_TIMEOUT: i32 = 11;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_CHANGED: i32 = 12;
|
|
|
|
// CAN BCM flags (linux/can/bcm.h)
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_SETTIMER: i32 = 0x0001;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_STARTTIMER: i32 = 0x0002;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_COUNTEVT: i32 = 0x0004;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_ANNOUNCE: i32 = 0x0008;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_CP_CAN_ID: i32 = 0x0010;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_FILTER_ID: i32 = 0x0020;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_CHECK_DLC: i32 = 0x0040;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_NO_AUTOTIMER: i32 = 0x0080;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_ANNOUNCE_RESUME: i32 = 0x0100;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_TX_RESET_MULTI_IDX: i32 = 0x0200;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_RX_RTR_FRAME: i32 = 0x0400;
|
|
#[cfg(target_os = "linux")]
|
|
#[pyattr]
|
|
const CAN_BCM_CAN_FD_FRAME: i32 = 0x0800;
|
|
|
|
#[cfg(all(target_os = "linux", target_env = "gnu"))]
|
|
#[pyattr]
|
|
use c::SOL_RDS;
|
|
|
|
#[cfg(target_os = "netbsd")]
|
|
#[pyattr]
|
|
use c::IPPROTO_VRRP;
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
use c::{AF_SYSTEM, PF_SYSTEM, SYSPROTO_CONTROL, TCP_KEEPALIVE};
|
|
|
|
// RFC3542 IPv6 socket options for macOS (netinet6/in6.h)
|
|
// Not available in libc, define manually
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RECVHOPLIMIT: i32 = 37;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RECVRTHDR: i32 = 38;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RECVHOPOPTS: i32 = 39;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RECVDSTOPTS: i32 = 40;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_USE_MIN_MTU: i32 = 42;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RECVPATHMTU: i32 = 43;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_PATHMTU: i32 = 44;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_NEXTHOP: i32 = 48;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_HOPOPTS: i32 = 49;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_DSTOPTS: i32 = 50;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RTHDR: i32 = 51;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RTHDRDSTOPTS: i32 = 57;
|
|
#[cfg(target_vendor = "apple")]
|
|
#[pyattr]
|
|
const IPV6_RTHDR_TYPE_0: i32 = 0;
|
|
|
|
#[cfg(windows)]
|
|
#[pyattr]
|
|
use c::{
|
|
IPPORT_RESERVED, IPPROTO_IPV4, RCVALL_IPLEVEL, RCVALL_OFF, RCVALL_ON,
|
|
RCVALL_SOCKETLEVELONLY, SIO_KEEPALIVE_VALS, SIO_LOOPBACK_FAST_PATH, SIO_RCVALL,
|
|
SO_EXCLUSIVEADDRUSE,
|
|
};
|
|
|
|
#[cfg(not(windows))]
|
|
#[pyattr]
|
|
const IPPORT_RESERVED: i32 = 1024;
|
|
|
|
#[pyattr]
|
|
const IPPORT_USERRESERVED: i32 = 5000;
|
|
|
|
#[cfg(any(unix, target_os = "android"))]
|
|
#[pyattr]
|
|
use c::{
|
|
EAI_SYSTEM, MSG_EOR, SO_ACCEPTCONN, SO_DEBUG, SO_DONTROUTE, SO_RCVLOWAT, SO_RCVTIMEO,
|
|
SO_SNDLOWAT, SO_SNDTIMEO,
|
|
};
|
|
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
#[pyattr]
|
|
use c::{
|
|
ALG_OP_DECRYPT, ALG_OP_ENCRYPT, ALG_SET_AEAD_ASSOCLEN, ALG_SET_AEAD_AUTHSIZE, ALG_SET_IV,
|
|
ALG_SET_KEY, ALG_SET_OP, IP_DEFAULT_MULTICAST_LOOP, IP_RECVOPTS, IP_RETOPTS, IPV6_DSTOPTS,
|
|
IPV6_NEXTHOP, IPV6_PATHMTU, IPV6_RECVDSTOPTS, IPV6_RECVHOPLIMIT, IPV6_RECVHOPOPTS,
|
|
IPV6_RECVPATHMTU, IPV6_RTHDRDSTOPTS, NETLINK_CRYPTO, NETLINK_DNRTMSG, NETLINK_FIREWALL,
|
|
NETLINK_IP6_FW, NETLINK_NFLOG, NETLINK_ROUTE, NETLINK_USERSOCK, NETLINK_XFRM, SO_PASSSEC,
|
|
SO_PEERSEC, SOL_ALG,
|
|
};
|
|
|
|
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
|
#[pyattr]
|
|
use c::{AI_DEFAULT, AI_MASK, AI_V4MAPPED_CFG};
|
|
|
|
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
|
|
#[pyattr]
|
|
use c::MSG_NOTIFICATION;
|
|
|
|
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
use c::TCP_USER_TIMEOUT;
|
|
|
|
#[cfg(any(unix, target_os = "android", windows))]
|
|
#[pyattr]
|
|
use c::{
|
|
INADDR_BROADCAST, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_IF,
|
|
IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_TTL, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF,
|
|
IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY,
|
|
};
|
|
|
|
#[cfg(any(unix, target_os = "android", windows))]
|
|
#[pyattr]
|
|
const INADDR_UNSPEC_GROUP: u32 = 0xe0000000;
|
|
|
|
#[cfg(any(unix, target_os = "android", windows))]
|
|
#[pyattr]
|
|
const INADDR_ALLHOSTS_GROUP: u32 = 0xe0000001;
|
|
|
|
#[cfg(any(unix, target_os = "android", windows))]
|
|
#[pyattr]
|
|
const INADDR_MAX_LOCAL_GROUP: u32 = 0xe00000ff;
|
|
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
use c::{
|
|
AF_ALG, AF_ASH, AF_ATMPVC, AF_ATMSVC, AF_AX25, AF_BRIDGE, AF_CAN, AF_ECONET, AF_IRDA,
|
|
AF_LLC, AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PPPOX, AF_RDS, AF_SECURITY,
|
|
AF_TIPC, AF_VSOCK, AF_WANPIPE, AF_X25, IP_TRANSPARENT, MSG_CONFIRM, MSG_ERRQUEUE,
|
|
MSG_FASTOPEN, MSG_MORE, PF_CAN, PF_PACKET, PF_RDS, SCM_CREDENTIALS, SO_BINDTODEVICE,
|
|
SO_MARK, SOL_IP, SOL_TIPC, SOL_UDP, TCP_CORK, TCP_DEFER_ACCEPT, TCP_LINGER2, TCP_QUICKACK,
|
|
TCP_SYNCNT, TCP_WINDOW_CLAMP,
|
|
};
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const SO_VM_SOCKETS_BUFFER_SIZE: u32 = 0;
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const SO_VM_SOCKETS_BUFFER_MIN_SIZE: u32 = 1;
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const SO_VM_SOCKETS_BUFFER_MAX_SIZE: u32 = 2;
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const VMADDR_CID_ANY: u32 = 0xffffffff; // 0xffffffff
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const VMADDR_PORT_ANY: u32 = 0xffffffff; // 0xffffffff
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const VMADDR_CID_HOST: u32 = 2;
|
|
|
|
// gated on presence of AF_VSOCK:
|
|
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
#[pyattr]
|
|
const VM_SOCKETS_INVALID_VERSION: u32 = 0xffffffff; // 0xffffffff
|
|
|
|
// TODO: gated on https://github.com/rust-lang/libc/pull/1662
|
|
// // gated on presence of AF_VSOCK:
|
|
// #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
|
// #[pyattr(name = "IOCTL_VM_SOCKETS_GET_LOCAL_CID", once)]
|
|
// fn ioctl_vm_sockets_get_local_cid(_vm: &VirtualMachine) -> i32 {
|
|
// c::_IO(7, 0xb9)
|
|
// }
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
|
|
#[pyattr]
|
|
const SOL_IP: i32 = 0;
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
|
|
#[pyattr]
|
|
const SOL_UDP: i32 = 17;
|
|
|
|
#[cfg(any(target_os = "android", target_os = "linux", windows))]
|
|
#[pyattr]
|
|
use c::{IP_OPTIONS, IPV6_HOPOPTS, IPV6_RECVRTHDR, IPV6_RTHDR};
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::{IPPROTO_HELLO, IPPROTO_XTP, LOCAL_PEERCRED, MSG_EOF};
|
|
|
|
#[cfg(any(target_os = "netbsd", target_os = "openbsd", windows))]
|
|
#[pyattr]
|
|
use c::{MSG_BCAST, MSG_MCAST};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
target_os = "freebsd",
|
|
target_os = "linux"
|
|
))]
|
|
#[pyattr]
|
|
use c::{IPPROTO_UDPLITE, TCP_CONGESTION};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
target_os = "freebsd",
|
|
target_os = "linux"
|
|
))]
|
|
#[pyattr]
|
|
const UDPLITE_SEND_CSCOV: i32 = 10;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
target_os = "freebsd",
|
|
target_os = "linux"
|
|
))]
|
|
#[pyattr]
|
|
const UDPLITE_RECV_CSCOV: i32 = 11;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::AF_KEY;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::SO_DOMAIN;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "fuchsia",
|
|
all(
|
|
target_os = "linux",
|
|
any(
|
|
target_arch = "aarch64",
|
|
target_arch = "x86",
|
|
target_arch = "loongarch64",
|
|
target_arch = "mips",
|
|
target_arch = "powerpc",
|
|
target_arch = "powerpc64",
|
|
target_arch = "riscv64",
|
|
target_arch = "s390x",
|
|
target_arch = "x86_64"
|
|
)
|
|
),
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::SO_PRIORITY;
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::IPPROTO_MOBILE;
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::SCM_CREDS;
|
|
|
|
#[cfg(any(
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::TCP_FASTOPEN;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
all(
|
|
target_os = "linux",
|
|
any(
|
|
target_arch = "aarch64",
|
|
target_arch = "x86",
|
|
target_arch = "loongarch64",
|
|
target_arch = "mips",
|
|
target_arch = "powerpc",
|
|
target_arch = "powerpc64",
|
|
target_arch = "riscv64",
|
|
target_arch = "s390x",
|
|
target_arch = "x86_64"
|
|
)
|
|
),
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::SO_PROTOCOL;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "linux",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::IPV6_DONTFRAG;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::{SO_PASSCRED, SO_PEERCRED};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::TCP_INFO;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::IP_RECVTOS;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "netbsd",
|
|
target_os = "redox",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::NI_MAXSERV;
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::{IPPROTO_EON, IPPROTO_IPCOMP};
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::IPPROTO_ND;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "linux",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::{IPV6_CHECKSUM, IPV6_HOPLIMIT};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::IPPROTO_SCTP; // also in windows
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::{AI_ALL, AI_V4MAPPED};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::EAI_NODATA;
|
|
|
|
#[cfg(any(
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::{
|
|
AF_LINK, IP_RECVDSTADDR, IPPROTO_GGP, IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP, SO_USELOOPBACK,
|
|
};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::{MSG_CMSG_CLOEXEC, MSG_NOSIGNAL};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::TCP_KEEPIDLE;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::{TCP_KEEPCNT, TCP_KEEPINTVL};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "redox"
|
|
))]
|
|
#[pyattr]
|
|
use c::{SOCK_CLOEXEC, SOCK_NONBLOCK};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple"
|
|
))]
|
|
#[pyattr]
|
|
use c::{
|
|
AF_ROUTE, AF_SNA, EAI_OVERFLOW, IPPROTO_GRE, IPPROTO_RSVP, IPPROTO_TP, IPV6_RECVPKTINFO,
|
|
MSG_DONTWAIT, SCM_RIGHTS, TCP_MAXSEG,
|
|
};
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::IPV6_PKTINFO;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::AI_CANONNAME;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
))]
|
|
#[pyattr]
|
|
use c::{
|
|
EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_SERVICE,
|
|
EAI_SOCKTYPE, IP_HDRINCL, IP_TOS, IPV6_RECVTCLASS, IPV6_TCLASS, SOMAXCONN,
|
|
};
|
|
|
|
#[cfg(not(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_vendor = "apple",
|
|
windows
|
|
)))]
|
|
#[pyattr]
|
|
const SOMAXCONN: i32 = 5; // Common value
|
|
|
|
// HERE IS WHERE THE BLUETOOTH CONSTANTS START
|
|
// TODO: there should be a more intelligent way of detecting bluetooth on a platform.
|
|
// CPython uses header-detection, but blocks NetBSD and DragonFly BSD
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
use c::AF_BLUETOOTH;
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
const BDADDR_ANY: &str = "00:00:00:00:00:00";
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
target_os = "openbsd"
|
|
))]
|
|
#[pyattr]
|
|
const BDADDR_LOCAL: &str = "00:00:00:FF:FF:FF";
|
|
// HERE IS WHERE THE BLUETOOTH CONSTANTS END
|
|
|
|
#[cfg(windows)]
|
|
#[pyattr]
|
|
use windows_sys::Win32::Networking::WinSock::{
|
|
IPPROTO_CBT, IPPROTO_ICLFXBM, IPPROTO_IGP, IPPROTO_L2TP, IPPROTO_PGM, IPPROTO_RDP,
|
|
IPPROTO_SCTP, IPPROTO_ST,
|
|
};
|
|
|
|
#[pyattr]
|
|
fn error(vm: &VirtualMachine) -> PyTypeRef {
|
|
vm.ctx.exceptions.os_error.to_owned()
|
|
}
|
|
|
|
#[pyattr]
|
|
fn timeout(vm: &VirtualMachine) -> PyTypeRef {
|
|
vm.ctx.exceptions.timeout_error.to_owned()
|
|
}
|
|
|
|
#[pyattr(once)]
|
|
fn herror(vm: &VirtualMachine) -> PyTypeRef {
|
|
vm.ctx.new_exception_type(
|
|
"socket",
|
|
"herror",
|
|
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
|
|
)
|
|
}
|
|
#[pyattr(once)]
|
|
fn gaierror(vm: &VirtualMachine) -> PyTypeRef {
|
|
vm.ctx.new_exception_type(
|
|
"socket",
|
|
"gaierror",
|
|
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
|
|
)
|
|
}
|
|
|
|
#[pyfunction]
|
|
const fn htonl(x: u32) -> u32 {
|
|
u32::to_be(x)
|
|
}
|
|
|
|
#[pyfunction]
|
|
const fn htons(x: u16) -> u16 {
|
|
u16::to_be(x)
|
|
}
|
|
|
|
#[pyfunction]
|
|
const fn ntohl(x: u32) -> u32 {
|
|
u32::from_be(x)
|
|
}
|
|
|
|
#[pyfunction]
|
|
const fn ntohs(x: u16) -> u16 {
|
|
u16::from_be(x)
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
type RawSocket = std::os::unix::io::RawFd;
|
|
#[cfg(windows)]
|
|
type RawSocket = std::os::windows::raw::SOCKET;
|
|
|
|
#[cfg(unix)]
|
|
macro_rules! errcode {
|
|
($e:ident) => {
|
|
c::$e
|
|
};
|
|
}
|
|
#[cfg(windows)]
|
|
macro_rules! errcode {
|
|
($e:ident) => {
|
|
paste::paste!(c::[<WSA $e>])
|
|
};
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
use windows_sys::Win32::NetworkManagement::IpHelper;
|
|
|
|
fn get_raw_sock(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<RawSocket> {
|
|
#[cfg(unix)]
|
|
type CastFrom = libc::c_long;
|
|
#[cfg(windows)]
|
|
type CastFrom = libc::c_longlong;
|
|
|
|
// should really just be to_index() but test_socket tests the error messages explicitly
|
|
if obj.fast_isinstance(vm.ctx.types.float_type) {
|
|
return Err(vm.new_type_error("integer argument expected, got float"));
|
|
}
|
|
let int = obj
|
|
.try_index_opt(vm)
|
|
.unwrap_or_else(|| Err(vm.new_type_error("an integer is required")))?;
|
|
int.try_to_primitive::<CastFrom>(vm)
|
|
.map(|sock| sock as RawSocket)
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[derive(FromArgs)]
|
|
struct SendmsgAfalgArgs {
|
|
#[pyarg(any, default)]
|
|
msg: Vec<ArgBytesLike>,
|
|
#[pyarg(named)]
|
|
op: u32,
|
|
#[pyarg(named, default)]
|
|
iv: Option<ArgBytesLike>,
|
|
#[pyarg(named, default)]
|
|
assoclen: OptionalArg<isize>,
|
|
#[pyarg(named, default)]
|
|
flags: i32,
|
|
}
|
|
|
|
#[pyattr(name = "socket")]
|
|
#[pyattr(name = "SocketType")]
|
|
#[pyclass(name = "socket")]
|
|
#[derive(Debug, PyPayload)]
|
|
pub struct PySocket {
|
|
kind: AtomicCell<i32>,
|
|
family: AtomicCell<i32>,
|
|
proto: AtomicCell<i32>,
|
|
pub(crate) timeout: AtomicCell<f64>,
|
|
sock: PyRwLock<Option<Socket>>,
|
|
}
|
|
|
|
const _: () = assert!(core::mem::size_of::<Option<Socket>>() == core::mem::size_of::<Socket>());
|
|
|
|
impl Default for PySocket {
|
|
fn default() -> Self {
|
|
Self {
|
|
kind: AtomicCell::default(),
|
|
family: AtomicCell::default(),
|
|
proto: AtomicCell::default(),
|
|
timeout: AtomicCell::new(-1.0),
|
|
sock: PyRwLock::new(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
const CLOSED_ERR: i32 = c::WSAENOTSOCK;
|
|
#[cfg(unix)]
|
|
const CLOSED_ERR: i32 = c::EBADF;
|
|
|
|
impl Read for &PySocket {
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
(&mut &*self.sock()?).read(buf)
|
|
}
|
|
}
|
|
|
|
impl Write for &PySocket {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
(&mut &*self.sock()?).write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
(&mut &*self.sock()?).flush()
|
|
}
|
|
}
|
|
|
|
impl PySocket {
|
|
pub fn sock_opt(&self) -> Option<PyMappedRwLockReadGuard<'_, Socket>> {
|
|
let sock = PyRwLockReadGuard::try_map(self.sock.read(), |sock| sock.as_ref());
|
|
sock.ok()
|
|
}
|
|
|
|
pub fn sock(&self) -> io::Result<PyMappedRwLockReadGuard<'_, Socket>> {
|
|
self.sock_opt()
|
|
.ok_or_else(|| io::Error::from_raw_os_error(CLOSED_ERR))
|
|
}
|
|
|
|
fn init_inner(
|
|
&self,
|
|
family: i32,
|
|
socket_kind: i32,
|
|
proto: i32,
|
|
sock: Socket,
|
|
) -> io::Result<()> {
|
|
self.family.store(family);
|
|
// Mask out SOCK_NONBLOCK and SOCK_CLOEXEC flags from stored type
|
|
// to ensure consistent cross-platform behavior
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "illumos",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "redox"
|
|
))]
|
|
let masked_kind = socket_kind & !(c::SOCK_NONBLOCK | c::SOCK_CLOEXEC);
|
|
#[cfg(not(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "illumos",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "redox"
|
|
)))]
|
|
let masked_kind = socket_kind;
|
|
self.kind.store(masked_kind);
|
|
self.proto.store(proto);
|
|
let mut s = self.sock.write();
|
|
let sock = s.insert(sock);
|
|
// If SOCK_NONBLOCK is set, use timeout 0 (non-blocking)
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "illumos",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "redox"
|
|
))]
|
|
let timeout = if socket_kind & c::SOCK_NONBLOCK != 0 {
|
|
0.0
|
|
} else {
|
|
DEFAULT_TIMEOUT.load()
|
|
};
|
|
#[cfg(not(any(
|
|
target_os = "android",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "illumos",
|
|
target_os = "linux",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "redox"
|
|
)))]
|
|
let timeout = DEFAULT_TIMEOUT.load();
|
|
self.timeout.store(timeout);
|
|
if timeout >= 0.0 {
|
|
sock.set_nonblocking(true)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// returns Err(blocking)
|
|
pub fn get_timeout(&self) -> Result<Duration, bool> {
|
|
let timeout = self.timeout.load();
|
|
if timeout > 0.0 {
|
|
Ok(Duration::from_secs_f64(timeout))
|
|
} else {
|
|
Err(timeout != 0.0)
|
|
}
|
|
}
|
|
|
|
fn sock_op<F, R>(
|
|
&self,
|
|
vm: &VirtualMachine,
|
|
select: SelectKind,
|
|
f: F,
|
|
) -> Result<R, IoOrPyException>
|
|
where
|
|
F: FnMut() -> io::Result<R>,
|
|
{
|
|
let timeout = self.get_timeout().ok();
|
|
self.sock_op_timeout_err(vm, select, timeout, f)
|
|
}
|
|
|
|
fn sock_op_timeout_err<F, R>(
|
|
&self,
|
|
vm: &VirtualMachine,
|
|
select: SelectKind,
|
|
timeout: Option<Duration>,
|
|
mut f: F,
|
|
) -> Result<R, IoOrPyException>
|
|
where
|
|
F: FnMut() -> io::Result<R>,
|
|
{
|
|
let deadline = timeout.map(Deadline::new);
|
|
|
|
loop {
|
|
if deadline.is_some() || matches!(select, SelectKind::Connect) {
|
|
let interval = deadline.as_ref().map(|d| d.time_until()).transpose()?;
|
|
let res = sock_select(&*self.sock()?, select, interval);
|
|
match res {
|
|
Ok(true) => return Err(IoOrPyException::Timeout),
|
|
Err(e) if e.kind() == io::ErrorKind::Interrupted => {
|
|
vm.check_signals()?;
|
|
continue;
|
|
}
|
|
Err(e) => return Err(e.into()),
|
|
Ok(false) => {} // no timeout, continue as normal
|
|
}
|
|
}
|
|
|
|
let err = loop {
|
|
// loop on interrupt
|
|
match f() {
|
|
Ok(x) => return Ok(x),
|
|
Err(e) if e.kind() == io::ErrorKind::Interrupted => vm.check_signals()?,
|
|
Err(e) => break e,
|
|
}
|
|
};
|
|
if timeout.is_some() && err.kind() == io::ErrorKind::WouldBlock {
|
|
continue;
|
|
}
|
|
return Err(err.into());
|
|
}
|
|
}
|
|
|
|
fn extract_address(
|
|
&self,
|
|
addr: PyObjectRef,
|
|
caller: &str,
|
|
vm: &VirtualMachine,
|
|
) -> Result<socket2::SockAddr, IoOrPyException> {
|
|
let family = self.family.load();
|
|
match family {
|
|
#[cfg(unix)]
|
|
c::AF_UNIX => {
|
|
use crate::vm::function::ArgStrOrBytesLike;
|
|
use std::os::unix::ffi::OsStrExt;
|
|
let buf = ArgStrOrBytesLike::try_from_object(vm, addr)?;
|
|
let bytes = &*buf.borrow_bytes();
|
|
let path = match &buf {
|
|
ArgStrOrBytesLike::Buf(_) => ffi::OsStr::from_bytes(bytes).into(),
|
|
ArgStrOrBytesLike::Str(s) => vm.fsencode(s)?,
|
|
};
|
|
socket2::SockAddr::unix(path)
|
|
.map_err(|_| vm.new_os_error("AF_UNIX path too long".to_owned()).into())
|
|
}
|
|
c::AF_INET => {
|
|
let tuple: PyTupleRef = addr.downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_INET address must be tuple, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
if tuple.len() != 2 {
|
|
return Err(vm
|
|
.new_type_error("AF_INET address must be a pair (host, post)")
|
|
.into());
|
|
}
|
|
let addr = Address::from_tuple(&tuple, vm)?;
|
|
let mut addr4 = get_addr(vm, addr.host, c::AF_INET)?;
|
|
match &mut addr4 {
|
|
SocketAddr::V4(addr4) => {
|
|
addr4.set_port(addr.port);
|
|
}
|
|
SocketAddr::V6(_) => unreachable!(),
|
|
}
|
|
Ok(addr4.into())
|
|
}
|
|
c::AF_INET6 => {
|
|
let tuple: PyTupleRef = addr.downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_INET6 address must be tuple, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
match tuple.len() {
|
|
2..=4 => {}
|
|
_ => return Err(vm.new_type_error(
|
|
"AF_INET6 address must be a tuple (host, port[, flowinfo[, scopeid]])",
|
|
).into()),
|
|
}
|
|
let (addr, flowinfo, scopeid) = Address::from_tuple_ipv6(&tuple, vm)?;
|
|
let mut addr6 = get_addr(vm, addr.host, c::AF_INET6)?;
|
|
match &mut addr6 {
|
|
SocketAddr::V6(addr6) => {
|
|
addr6.set_port(addr.port);
|
|
addr6.set_flowinfo(flowinfo);
|
|
addr6.set_scope_id(scopeid);
|
|
}
|
|
SocketAddr::V4(_) => unreachable!(),
|
|
}
|
|
Ok(addr6.into())
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
c::AF_CAN => {
|
|
let tuple: PyTupleRef = addr.downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_CAN address must be tuple, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
if tuple.is_empty() || tuple.len() > 2 {
|
|
return Err(vm
|
|
.new_type_error(
|
|
"AF_CAN address must be a tuple (interface,) or (interface, addr)",
|
|
)
|
|
.into());
|
|
}
|
|
let interface: PyStrRef = tuple[0].clone().downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_CAN interface must be str, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
let ifname = interface.as_str();
|
|
|
|
// Get interface index
|
|
let ifindex = if ifname.is_empty() {
|
|
0 // Bind to all CAN interfaces
|
|
} else {
|
|
// Check interface name length (IFNAMSIZ is typically 16)
|
|
if ifname.len() >= 16 {
|
|
return Err(vm
|
|
.new_os_error("interface name too long".to_owned())
|
|
.into());
|
|
}
|
|
let cstr = alloc::ffi::CString::new(ifname)
|
|
.map_err(|_| vm.new_os_error("invalid interface name".to_owned()))?;
|
|
let idx = unsafe { libc::if_nametoindex(cstr.as_ptr()) };
|
|
if idx == 0 {
|
|
return Err(io::Error::last_os_error().into());
|
|
}
|
|
idx as i32
|
|
};
|
|
|
|
// Create sockaddr_can
|
|
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
|
|
let can_addr =
|
|
&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_can;
|
|
unsafe {
|
|
(*can_addr).can_family = libc::AF_CAN as libc::sa_family_t;
|
|
(*can_addr).can_ifindex = ifindex;
|
|
}
|
|
let storage: socket2::SockAddrStorage = unsafe { std::mem::transmute(storage) };
|
|
Ok(unsafe {
|
|
socket2::SockAddr::new(
|
|
storage,
|
|
std::mem::size_of::<libc::sockaddr_can>() as libc::socklen_t,
|
|
)
|
|
})
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
c::AF_ALG => {
|
|
let tuple: PyTupleRef = addr.downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_ALG address must be tuple, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
if tuple.len() != 2 {
|
|
return Err(vm
|
|
.new_type_error("AF_ALG address must be a tuple (type, name)")
|
|
.into());
|
|
}
|
|
let alg_type: PyStrRef = tuple[0].clone().downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_ALG type must be str, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
let alg_name: PyStrRef = tuple[1].clone().downcast().map_err(|obj| {
|
|
vm.new_type_error(format!(
|
|
"{}(): AF_ALG name must be str, not {}",
|
|
caller,
|
|
obj.class().name()
|
|
))
|
|
})?;
|
|
|
|
let type_str = alg_type.as_str();
|
|
let name_str = alg_name.as_str();
|
|
|
|
// salg_type is 14 bytes, salg_name is 64 bytes
|
|
if type_str.len() >= 14 {
|
|
return Err(vm.new_value_error("type too long".to_owned()).into());
|
|
}
|
|
if name_str.len() >= 64 {
|
|
return Err(vm.new_value_error("name too long".to_owned()).into());
|
|
}
|
|
|
|
// Create sockaddr_alg
|
|
let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
|
|
let alg_addr =
|
|
&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_alg;
|
|
unsafe {
|
|
(*alg_addr).salg_family = libc::AF_ALG as libc::sa_family_t;
|
|
// Copy type string
|
|
for (i, b) in type_str.bytes().enumerate() {
|
|
(*alg_addr).salg_type[i] = b;
|
|
}
|
|
// Copy name string
|
|
for (i, b) in name_str.bytes().enumerate() {
|
|
(*alg_addr).salg_name[i] = b;
|
|
}
|
|
}
|
|
let storage: socket2::SockAddrStorage = unsafe { std::mem::transmute(storage) };
|
|
Ok(unsafe {
|
|
socket2::SockAddr::new(
|
|
storage,
|
|
std::mem::size_of::<libc::sockaddr_alg>() as libc::socklen_t,
|
|
)
|
|
})
|
|
}
|
|
_ => Err(vm.new_os_error(format!("{caller}(): bad family")).into()),
|
|
}
|
|
}
|
|
|
|
fn connect_inner(
|
|
&self,
|
|
address: PyObjectRef,
|
|
caller: &str,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(), IoOrPyException> {
|
|
let sock_addr = self.extract_address(address, caller, vm)?;
|
|
|
|
let err = match self.sock()?.connect(&sock_addr) {
|
|
Ok(()) => return Ok(()),
|
|
Err(e) => e,
|
|
};
|
|
|
|
let wait_connect = if err.kind() == io::ErrorKind::Interrupted {
|
|
vm.check_signals()?;
|
|
self.timeout.load() != 0.0
|
|
} else {
|
|
#[cfg(unix)]
|
|
use c::EINPROGRESS;
|
|
#[cfg(windows)]
|
|
use c::WSAEWOULDBLOCK as EINPROGRESS;
|
|
|
|
self.timeout.load() > 0.0 && err.raw_os_error() == Some(EINPROGRESS)
|
|
};
|
|
|
|
if wait_connect {
|
|
// basically, connect() is async, and it registers an "error" on the socket when it's
|
|
// done connecting. SelectKind::Connect fills the errorfds fd_set, so if we wake up
|
|
// from poll and the error is EISCONN then we know that the connect is done
|
|
self.sock_op(vm, SelectKind::Connect, || {
|
|
let sock = self.sock()?;
|
|
let err = sock.take_error()?;
|
|
match err {
|
|
Some(e) if e.posix_errno() == libc::EISCONN => Ok(()),
|
|
Some(e) => Err(e),
|
|
// TODO: is this accurate?
|
|
None => Ok(()),
|
|
}
|
|
})
|
|
} else {
|
|
Err(err.into())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DefaultConstructor for PySocket {}
|
|
|
|
impl Initializer for PySocket {
|
|
type Args = (
|
|
OptionalArg<i32>,
|
|
OptionalArg<i32>,
|
|
OptionalArg<i32>,
|
|
OptionalOption<PyObjectRef>,
|
|
);
|
|
|
|
fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
|
|
Self::_init(zelf, args, vm).map_err(|e| e.into_pyexception(vm))
|
|
}
|
|
}
|
|
|
|
impl Representable for PySocket {
|
|
#[inline]
|
|
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
|
|
Ok(format!(
|
|
"<socket object, fd={}, family={}, type={}, proto={}>",
|
|
zelf.fileno(),
|
|
zelf.family.load(),
|
|
zelf.kind.load(),
|
|
zelf.proto.load(),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[pyclass(with(Constructor, Initializer, Representable), flags(BASETYPE))]
|
|
impl PySocket {
|
|
fn _init(
|
|
zelf: PyRef<Self>,
|
|
(family, socket_kind, proto, fileno): <Self as Initializer>::Args,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(), IoOrPyException> {
|
|
let mut family = family.unwrap_or(-1);
|
|
let mut socket_kind = socket_kind.unwrap_or(-1);
|
|
let mut proto = proto.unwrap_or(-1);
|
|
|
|
let sock;
|
|
|
|
// On Windows, fileno can be bytes from socket.share() for fromshare()
|
|
#[cfg(windows)]
|
|
if let Some(fileno_obj) = fileno.flatten() {
|
|
use crate::vm::builtins::PyBytes;
|
|
if let Ok(bytes) = fileno_obj.clone().downcast::<PyBytes>() {
|
|
let bytes_data = bytes.as_bytes();
|
|
let expected_size = std::mem::size_of::<c::WSAPROTOCOL_INFOW>();
|
|
|
|
if bytes_data.len() != expected_size {
|
|
return Err(vm
|
|
.new_value_error(format!(
|
|
"socket descriptor string has wrong size, should be {} bytes",
|
|
expected_size
|
|
))
|
|
.into());
|
|
}
|
|
|
|
let mut info: c::WSAPROTOCOL_INFOW = unsafe { std::mem::zeroed() };
|
|
unsafe {
|
|
std::ptr::copy_nonoverlapping(
|
|
bytes_data.as_ptr(),
|
|
&mut info as *mut c::WSAPROTOCOL_INFOW as *mut u8,
|
|
expected_size,
|
|
);
|
|
}
|
|
|
|
let fd = unsafe {
|
|
c::WSASocketW(
|
|
c::FROM_PROTOCOL_INFO,
|
|
c::FROM_PROTOCOL_INFO,
|
|
c::FROM_PROTOCOL_INFO,
|
|
&info,
|
|
0,
|
|
c::WSA_FLAG_OVERLAPPED,
|
|
)
|
|
};
|
|
|
|
if fd == c::INVALID_SOCKET {
|
|
return Err(Self::wsa_error().into());
|
|
}
|
|
|
|
crate::vm::stdlib::nt::raw_set_handle_inheritable(fd as _, false)?;
|
|
|
|
family = info.iAddressFamily;
|
|
socket_kind = info.iSocketType;
|
|
proto = info.iProtocol;
|
|
|
|
sock = unsafe { sock_from_raw_unchecked(fd as RawSocket) };
|
|
return Ok(zelf.init_inner(family, socket_kind, proto, sock)?);
|
|
}
|
|
|
|
// Not bytes, treat as regular fileno
|
|
let fileno = get_raw_sock(fileno_obj, vm)?;
|
|
sock = sock_from_raw(fileno, vm)?;
|
|
match sock.local_addr() {
|
|
Ok(addr) if family == -1 => family = addr.family() as i32,
|
|
Err(e)
|
|
if family == -1
|
|
|| matches!(
|
|
e.raw_os_error(),
|
|
Some(errcode!(ENOTSOCK)) | Some(errcode!(EBADF))
|
|
) =>
|
|
{
|
|
std::mem::forget(sock);
|
|
return Err(e.into());
|
|
}
|
|
_ => {}
|
|
}
|
|
if socket_kind == -1 {
|
|
socket_kind = sock.r#type().map_err(|e| e.into_pyexception(vm))?.into();
|
|
}
|
|
proto = 0;
|
|
return Ok(zelf.init_inner(family, socket_kind, proto, sock)?);
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
let fileno = fileno
|
|
.flatten()
|
|
.map(|obj| get_raw_sock(obj, vm))
|
|
.transpose()?;
|
|
#[cfg(not(windows))]
|
|
if let Some(fileno) = fileno {
|
|
sock = sock_from_raw(fileno, vm)?;
|
|
match sock.local_addr() {
|
|
Ok(addr) if family == -1 => family = addr.family() as i32,
|
|
Err(e)
|
|
if family == -1
|
|
|| matches!(
|
|
e.raw_os_error(),
|
|
Some(errcode!(ENOTSOCK)) | Some(errcode!(EBADF))
|
|
) =>
|
|
{
|
|
core::mem::forget(sock);
|
|
return Err(e.into());
|
|
}
|
|
_ => {}
|
|
}
|
|
if socket_kind == -1 {
|
|
socket_kind = sock.r#type().map_err(|e| e.into_pyexception(vm))?.into();
|
|
}
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "linux",
|
|
))] {
|
|
if proto == -1 {
|
|
proto = sock.protocol()?.map_or(0, Into::into);
|
|
}
|
|
} else {
|
|
proto = 0;
|
|
}
|
|
}
|
|
return Ok(zelf.init_inner(family, socket_kind, proto, sock)?);
|
|
}
|
|
|
|
// No fileno provided, create new socket
|
|
{
|
|
if family == -1 {
|
|
family = c::AF_INET as _
|
|
}
|
|
if socket_kind == -1 {
|
|
socket_kind = c::SOCK_STREAM
|
|
}
|
|
if proto == -1 {
|
|
proto = 0
|
|
}
|
|
sock = Socket::new(family.into(), socket_kind.into(), Some(proto.into()))?;
|
|
};
|
|
Ok(zelf.init_inner(family, socket_kind, proto, sock)?)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn connect(
|
|
&self,
|
|
address: PyObjectRef,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(), IoOrPyException> {
|
|
self.connect_inner(address, "connect", vm)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn connect_ex(&self, address: PyObjectRef, vm: &VirtualMachine) -> PyResult<i32> {
|
|
match self.connect_inner(address, "connect_ex", vm) {
|
|
Ok(()) => Ok(0),
|
|
Err(err) => err.errno(),
|
|
}
|
|
}
|
|
|
|
#[pymethod]
|
|
fn bind(&self, address: PyObjectRef, vm: &VirtualMachine) -> Result<(), IoOrPyException> {
|
|
let sock_addr = self.extract_address(address, "bind", vm)?;
|
|
Ok(self.sock()?.bind(&sock_addr)?)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn listen(&self, backlog: OptionalArg<i32>) -> io::Result<()> {
|
|
let backlog = backlog.unwrap_or(128);
|
|
let backlog = if backlog < 0 { 0 } else { backlog };
|
|
self.sock()?.listen(backlog)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn _accept(
|
|
&self,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(RawSocket, PyObjectRef), IoOrPyException> {
|
|
// Use accept_raw() instead of accept() to avoid socket2's set_common_flags()
|
|
// which tries to set SO_NOSIGPIPE and fails with EINVAL on Unix domain sockets on macOS
|
|
let (sock, addr) = self.sock_op(vm, SelectKind::Read, || self.sock()?.accept_raw())?;
|
|
let fd = into_sock_fileno(sock);
|
|
Ok((fd, get_addr_tuple(&addr, vm)))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn recv(
|
|
&self,
|
|
bufsize: usize,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<Vec<u8>, IoOrPyException> {
|
|
let flags = flags.unwrap_or(0);
|
|
let mut buffer = Vec::with_capacity(bufsize);
|
|
let sock = self.sock()?;
|
|
let n = self.sock_op(vm, SelectKind::Read, || {
|
|
sock.recv_with_flags(buffer.spare_capacity_mut(), flags)
|
|
})?;
|
|
unsafe { buffer.set_len(n) };
|
|
Ok(buffer)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn recv_into(
|
|
&self,
|
|
buf: ArgMemoryBuffer,
|
|
nbytes: OptionalArg<isize>,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<usize, IoOrPyException> {
|
|
let flags = flags.unwrap_or(0);
|
|
let sock = self.sock()?;
|
|
let mut buf = buf.borrow_buf_mut();
|
|
let buf = &mut *buf;
|
|
|
|
// Handle nbytes parameter
|
|
let read_len = if let OptionalArg::Present(nbytes) = nbytes {
|
|
let nbytes = nbytes.to_usize().ok_or_else(|| {
|
|
vm.new_value_error("negative buffersize in recv_into".to_owned())
|
|
})?;
|
|
nbytes.min(buf.len())
|
|
} else {
|
|
buf.len()
|
|
};
|
|
|
|
let buf = &mut buf[..read_len];
|
|
self.sock_op(vm, SelectKind::Read, || {
|
|
sock.recv_with_flags(unsafe { slice_as_uninit(buf) }, flags)
|
|
})
|
|
}
|
|
|
|
#[pymethod]
|
|
fn recvfrom(
|
|
&self,
|
|
bufsize: isize,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(Vec<u8>, PyObjectRef), IoOrPyException> {
|
|
let flags = flags.unwrap_or(0);
|
|
let bufsize = bufsize
|
|
.to_usize()
|
|
.ok_or_else(|| vm.new_value_error("negative buffersize in recvfrom"))?;
|
|
let mut buffer = Vec::with_capacity(bufsize);
|
|
let (n, addr) = self.sock_op(vm, SelectKind::Read, || {
|
|
self.sock()?
|
|
.recv_from_with_flags(buffer.spare_capacity_mut(), flags)
|
|
})?;
|
|
unsafe { buffer.set_len(n) };
|
|
Ok((buffer, get_addr_tuple(&addr, vm)))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn recvfrom_into(
|
|
&self,
|
|
buf: ArgMemoryBuffer,
|
|
nbytes: OptionalArg<isize>,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(usize, PyObjectRef), IoOrPyException> {
|
|
let mut buf = buf.borrow_buf_mut();
|
|
let buf = &mut *buf;
|
|
let buf = match nbytes {
|
|
OptionalArg::Present(i) => {
|
|
let i = i.to_usize().ok_or_else(|| {
|
|
vm.new_value_error("negative buffersize in recvfrom_into")
|
|
})?;
|
|
buf.get_mut(..i).ok_or_else(|| {
|
|
vm.new_value_error("nbytes is greater than the length of the buffer")
|
|
})?
|
|
}
|
|
OptionalArg::Missing => buf,
|
|
};
|
|
let flags = flags.unwrap_or(0);
|
|
let sock = self.sock()?;
|
|
let (n, addr) = self.sock_op(vm, SelectKind::Read, || {
|
|
sock.recv_from_with_flags(unsafe { slice_as_uninit(buf) }, flags)
|
|
})?;
|
|
Ok((n, get_addr_tuple(&addr, vm)))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn send(
|
|
&self,
|
|
bytes: ArgBytesLike,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<usize, IoOrPyException> {
|
|
let flags = flags.unwrap_or(0);
|
|
let buf = bytes.borrow_buf();
|
|
let buf = &*buf;
|
|
self.sock_op(vm, SelectKind::Write, || {
|
|
self.sock()?.send_with_flags(buf, flags)
|
|
})
|
|
}
|
|
|
|
#[pymethod]
|
|
fn sendall(
|
|
&self,
|
|
bytes: ArgBytesLike,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(), IoOrPyException> {
|
|
let flags = flags.unwrap_or(0);
|
|
|
|
let timeout = self.get_timeout().ok();
|
|
|
|
let deadline = timeout.map(Deadline::new);
|
|
|
|
let buf = bytes.borrow_buf();
|
|
let buf = &*buf;
|
|
let mut buf_offset = 0;
|
|
// now we have like 3 layers of interrupt loop :)
|
|
while buf_offset < buf.len() {
|
|
let interval = deadline.as_ref().map(|d| d.time_until()).transpose()?;
|
|
self.sock_op_timeout_err(vm, SelectKind::Write, interval, || {
|
|
let subbuf = &buf[buf_offset..];
|
|
buf_offset += self.sock()?.send_with_flags(subbuf, flags)?;
|
|
Ok(())
|
|
})?;
|
|
vm.check_signals()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[pymethod]
|
|
fn sendto(
|
|
&self,
|
|
bytes: ArgBytesLike,
|
|
arg2: PyObjectRef,
|
|
arg3: OptionalArg<PyObjectRef>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<usize, IoOrPyException> {
|
|
// signature is bytes[, flags], address
|
|
let (flags, address) = match arg3 {
|
|
OptionalArg::Present(arg3) => {
|
|
// should just be i32::try_from_obj but tests check for error message
|
|
let int = arg2
|
|
.try_index_opt(vm)
|
|
.unwrap_or_else(|| Err(vm.new_type_error("an integer is required")))?;
|
|
let flags = int.try_to_primitive::<i32>(vm)?;
|
|
(flags, arg3)
|
|
}
|
|
OptionalArg::Missing => (0, arg2),
|
|
};
|
|
let addr = self.extract_address(address, "sendto", vm)?;
|
|
let buf = bytes.borrow_buf();
|
|
let buf = &*buf;
|
|
self.sock_op(vm, SelectKind::Write, || {
|
|
self.sock()?.send_to_with_flags(buf, &addr, flags)
|
|
})
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
#[pymethod]
|
|
fn sendmsg(
|
|
&self,
|
|
buffers: Vec<ArgBytesLike>,
|
|
ancdata: OptionalArg,
|
|
flags: OptionalArg<i32>,
|
|
addr: OptionalOption,
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<usize> {
|
|
let flags = flags.unwrap_or(0);
|
|
let mut msg = socket2::MsgHdr::new();
|
|
|
|
let sockaddr;
|
|
if let Some(addr) = addr.flatten() {
|
|
sockaddr = self
|
|
.extract_address(addr, "sendmsg", vm)
|
|
.map_err(|e| e.into_pyexception(vm))?;
|
|
msg = msg.with_addr(&sockaddr);
|
|
}
|
|
|
|
let buffers = buffers
|
|
.iter()
|
|
.map(|buf| buf.borrow_buf())
|
|
.collect::<Vec<_>>();
|
|
let buffers = buffers
|
|
.iter()
|
|
.map(|buf| io::IoSlice::new(buf))
|
|
.collect::<Vec<_>>();
|
|
msg = msg.with_buffers(&buffers);
|
|
|
|
let control_buf;
|
|
if let OptionalArg::Present(ancdata) = ancdata {
|
|
let cmsgs = vm.extract_elements_with(
|
|
&ancdata,
|
|
|obj| -> PyResult<(i32, i32, ArgBytesLike)> {
|
|
let seq: Vec<PyObjectRef> = obj.try_into_value(vm)?;
|
|
let [lvl, typ, data]: [PyObjectRef; 3] = seq
|
|
.try_into()
|
|
.map_err(|_| vm.new_type_error("expected a sequence of length 3"))?;
|
|
Ok((
|
|
lvl.try_into_value(vm)?,
|
|
typ.try_into_value(vm)?,
|
|
data.try_into_value(vm)?,
|
|
))
|
|
},
|
|
)?;
|
|
control_buf = Self::pack_cmsgs_to_send(&cmsgs, vm)?;
|
|
if !control_buf.is_empty() {
|
|
msg = msg.with_control(&control_buf);
|
|
}
|
|
}
|
|
|
|
self.sock_op(vm, SelectKind::Write, || {
|
|
let sock = self.sock()?;
|
|
sock.sendmsg(&msg, flags)
|
|
})
|
|
.map_err(|e| e.into_pyexception(vm))
|
|
}
|
|
|
|
/// sendmsg_afalg([msg], *, op[, iv[, assoclen[, flags]]]) -> int
|
|
///
|
|
/// Set operation mode and target IV for an AF_ALG socket.
|
|
#[cfg(target_os = "linux")]
|
|
#[pymethod]
|
|
fn sendmsg_afalg(&self, args: SendmsgAfalgArgs, vm: &VirtualMachine) -> PyResult<usize> {
|
|
let msg = args.msg;
|
|
let op = args.op;
|
|
let iv = args.iv;
|
|
let flags = args.flags;
|
|
|
|
// Validate assoclen - must be non-negative if provided
|
|
let assoclen: Option<u32> = match args.assoclen {
|
|
OptionalArg::Present(val) if val < 0 => {
|
|
return Err(vm.new_type_error("assoclen must be non-negative".to_owned()));
|
|
}
|
|
OptionalArg::Present(val) => Some(val as u32),
|
|
OptionalArg::Missing => None,
|
|
};
|
|
|
|
// Build control messages for AF_ALG
|
|
let mut control_buf = Vec::new();
|
|
|
|
// Add ALG_SET_OP control message
|
|
{
|
|
let op_bytes = op.to_ne_bytes();
|
|
let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<u32>() as u32) } as usize;
|
|
let old_len = control_buf.len();
|
|
control_buf.resize(old_len + space, 0u8);
|
|
|
|
let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr;
|
|
unsafe {
|
|
(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u32>() as u32) as _;
|
|
(*cmsg).cmsg_level = libc::SOL_ALG;
|
|
(*cmsg).cmsg_type = libc::ALG_SET_OP;
|
|
let data = libc::CMSG_DATA(cmsg);
|
|
std::ptr::copy_nonoverlapping(op_bytes.as_ptr(), data, op_bytes.len());
|
|
}
|
|
}
|
|
|
|
// Add ALG_SET_IV control message if iv is provided
|
|
if let Some(iv_data) = iv {
|
|
let iv_bytes = iv_data.borrow_buf();
|
|
// struct af_alg_iv { __u32 ivlen; __u8 iv[]; }
|
|
let iv_struct_size = 4 + iv_bytes.len();
|
|
let space = unsafe { libc::CMSG_SPACE(iv_struct_size as u32) } as usize;
|
|
let old_len = control_buf.len();
|
|
control_buf.resize(old_len + space, 0u8);
|
|
|
|
let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr;
|
|
unsafe {
|
|
(*cmsg).cmsg_len = libc::CMSG_LEN(iv_struct_size as u32) as _;
|
|
(*cmsg).cmsg_level = libc::SOL_ALG;
|
|
(*cmsg).cmsg_type = libc::ALG_SET_IV;
|
|
let data = libc::CMSG_DATA(cmsg);
|
|
// Write ivlen
|
|
let ivlen = (iv_bytes.len() as u32).to_ne_bytes();
|
|
std::ptr::copy_nonoverlapping(ivlen.as_ptr(), data, 4);
|
|
// Write iv
|
|
std::ptr::copy_nonoverlapping(iv_bytes.as_ptr(), data.add(4), iv_bytes.len());
|
|
}
|
|
}
|
|
|
|
// Add ALG_SET_AEAD_ASSOCLEN control message if assoclen is provided
|
|
if let Some(assoclen_val) = assoclen {
|
|
let assoclen_bytes = assoclen_val.to_ne_bytes();
|
|
let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<u32>() as u32) } as usize;
|
|
let old_len = control_buf.len();
|
|
control_buf.resize(old_len + space, 0u8);
|
|
|
|
let cmsg = control_buf[old_len..].as_mut_ptr() as *mut libc::cmsghdr;
|
|
unsafe {
|
|
(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u32>() as u32) as _;
|
|
(*cmsg).cmsg_level = libc::SOL_ALG;
|
|
(*cmsg).cmsg_type = libc::ALG_SET_AEAD_ASSOCLEN;
|
|
let data = libc::CMSG_DATA(cmsg);
|
|
std::ptr::copy_nonoverlapping(
|
|
assoclen_bytes.as_ptr(),
|
|
data,
|
|
assoclen_bytes.len(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Build buffers
|
|
let buffers = msg.iter().map(|buf| buf.borrow_buf()).collect::<Vec<_>>();
|
|
let iovecs: Vec<libc::iovec> = buffers
|
|
.iter()
|
|
.map(|buf| libc::iovec {
|
|
iov_base: buf.as_ptr() as *mut _,
|
|
iov_len: buf.len(),
|
|
})
|
|
.collect();
|
|
|
|
// Set up msghdr
|
|
let mut msghdr: libc::msghdr = unsafe { std::mem::zeroed() };
|
|
msghdr.msg_iov = iovecs.as_ptr() as *mut _;
|
|
msghdr.msg_iovlen = iovecs.len() as _;
|
|
if !control_buf.is_empty() {
|
|
msghdr.msg_control = control_buf.as_mut_ptr() as *mut _;
|
|
msghdr.msg_controllen = control_buf.len() as _;
|
|
}
|
|
|
|
self.sock_op(vm, SelectKind::Write, || {
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
let ret = unsafe { libc::sendmsg(fd as libc::c_int, &msghdr, flags) };
|
|
if ret < 0 {
|
|
Err(io::Error::last_os_error())
|
|
} else {
|
|
Ok(ret as usize)
|
|
}
|
|
})
|
|
.map_err(|e| e.into_pyexception(vm))
|
|
}
|
|
|
|
/// recvmsg(bufsize[, ancbufsize[, flags]]) -> (data, ancdata, msg_flags, address)
|
|
///
|
|
/// Receive normal data and ancillary data from the socket.
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
#[pymethod]
|
|
fn recvmsg(
|
|
&self,
|
|
bufsize: isize,
|
|
ancbufsize: OptionalArg<isize>,
|
|
flags: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<PyTupleRef> {
|
|
use core::mem::MaybeUninit;
|
|
|
|
if bufsize < 0 {
|
|
return Err(vm.new_value_error("negative buffer size in recvmsg".to_owned()));
|
|
}
|
|
let bufsize = bufsize as usize;
|
|
|
|
let ancbufsize = ancbufsize.unwrap_or(0);
|
|
if ancbufsize < 0 {
|
|
return Err(
|
|
vm.new_value_error("negative ancillary buffer size in recvmsg".to_owned())
|
|
);
|
|
}
|
|
let ancbufsize = ancbufsize as usize;
|
|
let flags = flags.unwrap_or(0);
|
|
|
|
// Allocate buffers
|
|
let mut data_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); bufsize];
|
|
let mut anc_buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); ancbufsize];
|
|
let mut addr_storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() };
|
|
|
|
// Set up iovec
|
|
let mut iov = [libc::iovec {
|
|
iov_base: data_buf.as_mut_ptr().cast(),
|
|
iov_len: bufsize,
|
|
}];
|
|
|
|
// Set up msghdr
|
|
let mut msg: libc::msghdr = unsafe { core::mem::zeroed() };
|
|
msg.msg_name = (&mut addr_storage as *mut libc::sockaddr_storage).cast();
|
|
msg.msg_namelen = core::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
|
msg.msg_iov = iov.as_mut_ptr();
|
|
msg.msg_iovlen = 1;
|
|
if ancbufsize > 0 {
|
|
msg.msg_control = anc_buf.as_mut_ptr().cast();
|
|
msg.msg_controllen = ancbufsize as _;
|
|
}
|
|
|
|
let n = self
|
|
.sock_op(vm, SelectKind::Read, || {
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
let ret = unsafe { libc::recvmsg(fd as libc::c_int, &mut msg, flags) };
|
|
if ret < 0 {
|
|
Err(io::Error::last_os_error())
|
|
} else {
|
|
Ok(ret as usize)
|
|
}
|
|
})
|
|
.map_err(|e| e.into_pyexception(vm))?;
|
|
|
|
// Build data bytes
|
|
let data = unsafe {
|
|
data_buf.set_len(n);
|
|
core::mem::transmute::<Vec<MaybeUninit<u8>>, Vec<u8>>(data_buf)
|
|
};
|
|
|
|
// Build ancdata list
|
|
let ancdata = Self::parse_ancillary_data(&msg, vm)?;
|
|
|
|
// Build address tuple
|
|
let address = if msg.msg_namelen > 0 {
|
|
let storage: socket2::SockAddrStorage =
|
|
unsafe { core::mem::transmute(addr_storage) };
|
|
let addr = unsafe { socket2::SockAddr::new(storage, msg.msg_namelen) };
|
|
get_addr_tuple(&addr, vm)
|
|
} else {
|
|
vm.ctx.none()
|
|
};
|
|
|
|
Ok(vm.ctx.new_tuple(vec![
|
|
vm.ctx.new_bytes(data).into(),
|
|
ancdata,
|
|
vm.ctx.new_int(msg.msg_flags).into(),
|
|
address,
|
|
]))
|
|
}
|
|
|
|
/// Parse ancillary data from a received message header
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
fn parse_ancillary_data(msg: &libc::msghdr, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
|
let mut result = Vec::new();
|
|
|
|
// Calculate buffer end for truncation handling
|
|
let ctrl_buf = msg.msg_control as *const u8;
|
|
let ctrl_end = unsafe { ctrl_buf.add(msg.msg_controllen as _) };
|
|
|
|
let mut cmsg: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(msg) };
|
|
while !cmsg.is_null() {
|
|
let cmsg_ref = unsafe { &*cmsg };
|
|
let data_ptr = unsafe { libc::CMSG_DATA(cmsg) };
|
|
|
|
// Calculate data length, respecting buffer truncation
|
|
let data_len_from_cmsg =
|
|
cmsg_ref.cmsg_len as usize - (data_ptr as usize - cmsg as usize);
|
|
let available = ctrl_end as usize - data_ptr as usize;
|
|
let data_len = data_len_from_cmsg.min(available);
|
|
|
|
let data = unsafe { core::slice::from_raw_parts(data_ptr, data_len) };
|
|
|
|
let tuple = vm.ctx.new_tuple(vec![
|
|
vm.ctx.new_int(cmsg_ref.cmsg_level).into(),
|
|
vm.ctx.new_int(cmsg_ref.cmsg_type).into(),
|
|
vm.ctx.new_bytes(data.to_vec()).into(),
|
|
]);
|
|
|
|
result.push(tuple.into());
|
|
|
|
cmsg = unsafe { libc::CMSG_NXTHDR(msg, cmsg) };
|
|
}
|
|
|
|
Ok(vm.ctx.new_list(result).into())
|
|
}
|
|
|
|
// based on nix's implementation
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
fn pack_cmsgs_to_send(
|
|
cmsgs: &[(i32, i32, ArgBytesLike)],
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<Vec<u8>> {
|
|
use core::{mem, ptr};
|
|
|
|
if cmsgs.is_empty() {
|
|
return Ok(vec![]);
|
|
}
|
|
|
|
let capacity = cmsgs
|
|
.iter()
|
|
.map(|(_, _, buf)| buf.len())
|
|
.try_fold(0, |sum, len| {
|
|
let space = checked_cmsg_space(len).ok_or_else(|| {
|
|
vm.new_os_error("ancillary data item too large".to_owned())
|
|
})?;
|
|
usize::checked_add(sum, space)
|
|
.ok_or_else(|| vm.new_os_error("too much ancillary data".to_owned()))
|
|
})?;
|
|
|
|
let mut cmsg_buffer = vec![0u8; capacity];
|
|
|
|
// make a dummy msghdr so we can use the CMSG_* apis
|
|
let mut mhdr = unsafe { mem::zeroed::<libc::msghdr>() };
|
|
mhdr.msg_control = cmsg_buffer.as_mut_ptr().cast();
|
|
mhdr.msg_controllen = capacity as _;
|
|
|
|
let mut pmhdr: *mut libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(&mhdr) };
|
|
for (lvl, typ, buf) in cmsgs {
|
|
if pmhdr.is_null() {
|
|
return Err(vm.new_runtime_error(
|
|
"unexpected NULL result from CMSG_FIRSTHDR/CMSG_NXTHDR",
|
|
));
|
|
}
|
|
let data = &*buf.borrow_buf();
|
|
assert_eq!(data.len(), buf.len());
|
|
// Safe because we know that pmhdr is valid, and we initialized it with
|
|
// sufficient space
|
|
unsafe {
|
|
(*pmhdr).cmsg_level = *lvl;
|
|
(*pmhdr).cmsg_type = *typ;
|
|
(*pmhdr).cmsg_len = libc::CMSG_LEN(data.len() as _) as _;
|
|
ptr::copy_nonoverlapping(data.as_ptr(), libc::CMSG_DATA(pmhdr), data.len());
|
|
}
|
|
|
|
// Safe because mhdr is valid
|
|
pmhdr = unsafe { libc::CMSG_NXTHDR(&mhdr, pmhdr) };
|
|
}
|
|
|
|
Ok(cmsg_buffer)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn close(&self) -> io::Result<()> {
|
|
let sock = self.sock.write().take();
|
|
if let Some(sock) = sock {
|
|
close_inner(into_sock_fileno(sock))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[pymethod]
|
|
fn __del__(&self, vm: &VirtualMachine) {
|
|
// Emit ResourceWarning if socket is still open
|
|
if self.sock.read().is_some() {
|
|
let laddr = if let Ok(sock) = self.sock()
|
|
&& let Ok(addr) = sock.local_addr()
|
|
&& let Ok(repr) = get_addr_tuple(&addr, vm).repr(vm)
|
|
{
|
|
format!(", laddr={}", repr.as_str())
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
let msg = format!(
|
|
"unclosed <socket.socket fd={}, family={}, type={}, proto={}{}>",
|
|
self.fileno(),
|
|
self.family.load(),
|
|
self.kind.load(),
|
|
self.proto.load(),
|
|
laddr
|
|
);
|
|
let _ = crate::vm::warn::warn(
|
|
vm.ctx.new_str(msg).into(),
|
|
Some(vm.ctx.exceptions.resource_warning.to_owned()),
|
|
1,
|
|
None,
|
|
vm,
|
|
);
|
|
}
|
|
let _ = self.close();
|
|
}
|
|
|
|
#[pymethod]
|
|
#[inline]
|
|
fn detach(&self) -> i64 {
|
|
let sock = self.sock.write().take();
|
|
sock.map_or(INVALID_SOCKET as i64, |s| into_sock_fileno(s) as i64)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn fileno(&self) -> i64 {
|
|
self.sock
|
|
.read()
|
|
.as_ref()
|
|
.map_or(INVALID_SOCKET as i64, |s| sock_fileno(s) as i64)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn getsockname(&self, vm: &VirtualMachine) -> std::io::Result<PyObjectRef> {
|
|
let addr = self.sock()?.local_addr()?;
|
|
|
|
Ok(get_addr_tuple(&addr, vm))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn getpeername(&self, vm: &VirtualMachine) -> std::io::Result<PyObjectRef> {
|
|
let addr = self.sock()?.peer_addr()?;
|
|
|
|
Ok(get_addr_tuple(&addr, vm))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn gettimeout(&self) -> Option<f64> {
|
|
let timeout = self.timeout.load();
|
|
if timeout >= 0.0 { Some(timeout) } else { None }
|
|
}
|
|
|
|
#[pymethod]
|
|
fn setblocking(&self, block: bool) -> io::Result<()> {
|
|
self.timeout.store(if block { -1.0 } else { 0.0 });
|
|
self.sock()?.set_nonblocking(!block)
|
|
}
|
|
|
|
#[pymethod]
|
|
fn getblocking(&self) -> bool {
|
|
self.timeout.load() != 0.0
|
|
}
|
|
|
|
#[pymethod]
|
|
fn settimeout(&self, timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
|
|
let timeout = match timeout {
|
|
Some(t) => {
|
|
let f = t.into_float();
|
|
if f.is_nan() {
|
|
return Err(
|
|
vm.new_value_error("Invalid value NaN (not a number)".to_owned())
|
|
);
|
|
}
|
|
if f < 0.0 || !f.is_finite() {
|
|
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
|
|
}
|
|
Some(f)
|
|
}
|
|
None => None,
|
|
};
|
|
self.timeout.store(timeout.unwrap_or(-1.0));
|
|
// even if timeout is > 0 the socket needs to be nonblocking in order for us to select() on
|
|
// it
|
|
self.sock()
|
|
.map_err(|e| e.into_pyexception(vm))?
|
|
.set_nonblocking(timeout.is_some())
|
|
.map_err(|e| e.into_pyexception(vm))
|
|
}
|
|
|
|
#[pymethod]
|
|
fn getsockopt(
|
|
&self,
|
|
level: i32,
|
|
name: i32,
|
|
buflen: OptionalArg<i32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<PyObjectRef, IoOrPyException> {
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
let buflen = buflen.unwrap_or(0);
|
|
if buflen == 0 {
|
|
let mut flag: libc::c_int = 0;
|
|
let mut flagsize = core::mem::size_of::<libc::c_int>() as _;
|
|
let ret = unsafe {
|
|
c::getsockopt(
|
|
fd as _,
|
|
level,
|
|
name,
|
|
&mut flag as *mut libc::c_int as *mut _,
|
|
&mut flagsize,
|
|
)
|
|
};
|
|
if ret < 0 {
|
|
return Err(crate::common::os::errno_io_error().into());
|
|
}
|
|
Ok(vm.ctx.new_int(flag).into())
|
|
} else {
|
|
if buflen <= 0 || buflen > 1024 {
|
|
return Err(vm
|
|
.new_os_error("getsockopt buflen out of range".to_owned())
|
|
.into());
|
|
}
|
|
let mut buf = vec![0u8; buflen as usize];
|
|
let mut buflen = buflen as _;
|
|
let ret = unsafe {
|
|
c::getsockopt(
|
|
fd as _,
|
|
level,
|
|
name,
|
|
buf.as_mut_ptr() as *mut _,
|
|
&mut buflen,
|
|
)
|
|
};
|
|
if ret < 0 {
|
|
return Err(crate::common::os::errno_io_error().into());
|
|
}
|
|
buf.truncate(buflen as usize);
|
|
Ok(vm.ctx.new_bytes(buf).into())
|
|
}
|
|
}
|
|
|
|
#[pymethod]
|
|
fn setsockopt(
|
|
&self,
|
|
level: i32,
|
|
name: i32,
|
|
value: Option<Either<ArgBytesLike, i32>>,
|
|
optlen: OptionalArg<u32>,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(), IoOrPyException> {
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
let ret = match (value, optlen) {
|
|
(Some(Either::A(b)), OptionalArg::Missing) => b.with_ref(|b| unsafe {
|
|
c::setsockopt(fd as _, level, name, b.as_ptr() as *const _, b.len() as _)
|
|
}),
|
|
(Some(Either::B(ref val)), OptionalArg::Missing) => unsafe {
|
|
c::setsockopt(
|
|
fd as _,
|
|
level,
|
|
name,
|
|
val as *const i32 as *const _,
|
|
core::mem::size_of::<i32>() as _,
|
|
)
|
|
},
|
|
(None, OptionalArg::Present(optlen)) => unsafe {
|
|
c::setsockopt(fd as _, level, name, core::ptr::null(), optlen as _)
|
|
},
|
|
_ => {
|
|
return Err(vm
|
|
.new_type_error("expected the value arg xor the optlen arg")
|
|
.into());
|
|
}
|
|
};
|
|
if ret < 0 {
|
|
Err(crate::common::os::errno_io_error().into())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[pymethod]
|
|
fn shutdown(&self, how: i32, vm: &VirtualMachine) -> Result<(), IoOrPyException> {
|
|
let how = match how {
|
|
c::SHUT_RD => Shutdown::Read,
|
|
c::SHUT_WR => Shutdown::Write,
|
|
c::SHUT_RDWR => Shutdown::Both,
|
|
_ => {
|
|
return Err(vm
|
|
.new_value_error("`how` must be SHUT_RD, SHUT_WR, or SHUT_RDWR")
|
|
.into());
|
|
}
|
|
};
|
|
Ok(self.sock()?.shutdown(how)?)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn wsa_error() -> io::Error {
|
|
io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[pymethod]
|
|
fn ioctl(
|
|
&self,
|
|
cmd: PyObjectRef,
|
|
option: PyObjectRef,
|
|
vm: &VirtualMachine,
|
|
) -> Result<u32, IoOrPyException> {
|
|
use crate::vm::builtins::PyInt;
|
|
use crate::vm::convert::TryFromObject;
|
|
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
let mut recv: u32 = 0;
|
|
|
|
// Convert cmd to u32, returning ValueError for invalid/negative values
|
|
let cmd_int = cmd
|
|
.downcast::<PyInt>()
|
|
.map_err(|_| vm.new_type_error("an integer is required"))?;
|
|
let cmd_val = cmd_int.as_bigint();
|
|
let cmd: u32 = cmd_val
|
|
.to_u32()
|
|
.ok_or_else(|| vm.new_value_error(format!("invalid ioctl command {}", cmd_val)))?;
|
|
|
|
match cmd {
|
|
c::SIO_RCVALL | c::SIO_LOOPBACK_FAST_PATH => {
|
|
// Option must be an integer, not None
|
|
if vm.is_none(&option) {
|
|
return Err(vm
|
|
.new_type_error("an integer is required (got type NoneType)")
|
|
.into());
|
|
}
|
|
let option_val: u32 = TryFromObject::try_from_object(vm, option)?;
|
|
let ret = unsafe {
|
|
c::WSAIoctl(
|
|
fd as _,
|
|
cmd,
|
|
&option_val as *const u32 as *const _,
|
|
std::mem::size_of::<u32>() as u32,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
&mut recv,
|
|
std::ptr::null_mut(),
|
|
None,
|
|
)
|
|
};
|
|
if ret == c::SOCKET_ERROR {
|
|
return Err(Self::wsa_error().into());
|
|
}
|
|
Ok(recv)
|
|
}
|
|
c::SIO_KEEPALIVE_VALS => {
|
|
let tuple: PyTupleRef = option
|
|
.downcast()
|
|
.map_err(|_| vm.new_type_error("SIO_KEEPALIVE_VALS requires a tuple"))?;
|
|
if tuple.len() != 3 {
|
|
return Err(vm
|
|
.new_type_error(
|
|
"SIO_KEEPALIVE_VALS requires (onoff, keepalivetime, keepaliveinterval)",
|
|
)
|
|
.into());
|
|
}
|
|
|
|
#[repr(C)]
|
|
struct TcpKeepalive {
|
|
onoff: u32,
|
|
keepalivetime: u32,
|
|
keepaliveinterval: u32,
|
|
}
|
|
|
|
let ka = TcpKeepalive {
|
|
onoff: TryFromObject::try_from_object(vm, tuple[0].clone())?,
|
|
keepalivetime: TryFromObject::try_from_object(vm, tuple[1].clone())?,
|
|
keepaliveinterval: TryFromObject::try_from_object(vm, tuple[2].clone())?,
|
|
};
|
|
|
|
let ret = unsafe {
|
|
c::WSAIoctl(
|
|
fd as _,
|
|
cmd,
|
|
&ka as *const TcpKeepalive as *const _,
|
|
std::mem::size_of::<TcpKeepalive>() as u32,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
&mut recv,
|
|
std::ptr::null_mut(),
|
|
None,
|
|
)
|
|
};
|
|
if ret == c::SOCKET_ERROR {
|
|
return Err(Self::wsa_error().into());
|
|
}
|
|
Ok(recv)
|
|
}
|
|
_ => Err(vm
|
|
.new_value_error(format!("invalid ioctl command {}", cmd))
|
|
.into()),
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[pymethod]
|
|
fn share(&self, process_id: u32, _vm: &VirtualMachine) -> Result<Vec<u8>, IoOrPyException> {
|
|
let sock = self.sock()?;
|
|
let fd = sock_fileno(&sock);
|
|
|
|
let mut info: MaybeUninit<c::WSAPROTOCOL_INFOW> = MaybeUninit::uninit();
|
|
|
|
let ret = unsafe { c::WSADuplicateSocketW(fd as _, process_id, info.as_mut_ptr()) };
|
|
|
|
if ret == c::SOCKET_ERROR {
|
|
return Err(Self::wsa_error().into());
|
|
}
|
|
|
|
let info = unsafe { info.assume_init() };
|
|
let bytes = unsafe {
|
|
std::slice::from_raw_parts(
|
|
&info as *const c::WSAPROTOCOL_INFOW as *const u8,
|
|
std::mem::size_of::<c::WSAPROTOCOL_INFOW>(),
|
|
)
|
|
};
|
|
|
|
Ok(bytes.to_vec())
|
|
}
|
|
|
|
#[pygetset(name = "type")]
|
|
fn kind(&self) -> i32 {
|
|
self.kind.load()
|
|
}
|
|
|
|
#[pygetset]
|
|
fn family(&self) -> i32 {
|
|
self.family.load()
|
|
}
|
|
|
|
#[pygetset]
|
|
fn proto(&self) -> i32 {
|
|
self.proto.load()
|
|
}
|
|
}
|
|
|
|
struct Address {
|
|
host: PyStrRef,
|
|
port: u16,
|
|
}
|
|
|
|
impl ToSocketAddrs for Address {
|
|
type Iter = alloc::vec::IntoIter<SocketAddr>;
|
|
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
|
|
(self.host.as_str(), self.port).to_socket_addrs()
|
|
}
|
|
}
|
|
|
|
impl TryFromObject for Address {
|
|
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
|
let tuple = PyTupleRef::try_from_object(vm, obj)?;
|
|
if tuple.len() != 2 {
|
|
Err(vm.new_type_error("Address tuple should have only 2 values"))
|
|
} else {
|
|
Self::from_tuple(&tuple, vm)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Address {
|
|
fn from_tuple(tuple: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<Self> {
|
|
let host = PyStrRef::try_from_object(vm, tuple[0].clone())?;
|
|
let port = i32::try_from_borrowed_object(vm, &tuple[1])?;
|
|
let port = port
|
|
.to_u16()
|
|
.ok_or_else(|| vm.new_overflow_error("port must be 0-65535."))?;
|
|
Ok(Self { host, port })
|
|
}
|
|
|
|
fn from_tuple_ipv6(
|
|
tuple: &[PyObjectRef],
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<(Self, u32, u32)> {
|
|
let addr = Self::from_tuple(tuple, vm)?;
|
|
let flowinfo = tuple
|
|
.get(2)
|
|
.map(|obj| u32::try_from_borrowed_object(vm, obj))
|
|
.transpose()?
|
|
.unwrap_or(0);
|
|
let scopeid = tuple
|
|
.get(3)
|
|
.map(|obj| u32::try_from_borrowed_object(vm, obj))
|
|
.transpose()?
|
|
.unwrap_or(0);
|
|
if flowinfo > 0xfffff {
|
|
return Err(vm.new_overflow_error("flowinfo must be 0-1048575."));
|
|
}
|
|
Ok((addr, flowinfo, scopeid))
|
|
}
|
|
}
|
|
|
|
fn get_ip_addr_tuple(addr: &SocketAddr, vm: &VirtualMachine) -> PyObjectRef {
|
|
match addr {
|
|
SocketAddr::V4(addr) => (addr.ip().to_string(), addr.port()).to_pyobject(vm),
|
|
SocketAddr::V6(addr) => (
|
|
addr.ip().to_string(),
|
|
addr.port(),
|
|
addr.flowinfo(),
|
|
addr.scope_id(),
|
|
)
|
|
.to_pyobject(vm),
|
|
}
|
|
}
|
|
|
|
fn get_addr_tuple(addr: &socket2::SockAddr, vm: &VirtualMachine) -> PyObjectRef {
|
|
if let Some(addr) = addr.as_socket() {
|
|
return get_ip_addr_tuple(&addr, vm);
|
|
}
|
|
#[cfg(unix)]
|
|
if addr.is_unix() {
|
|
use std::os::unix::ffi::OsStrExt;
|
|
if let Some(abstractpath) = addr.as_abstract_namespace() {
|
|
return vm.ctx.new_bytes([b"\0", abstractpath].concat()).into();
|
|
}
|
|
// necessary on macos
|
|
let path = ffi::OsStr::as_bytes(addr.as_pathname().unwrap_or("".as_ref()).as_ref());
|
|
let nul_pos = memchr::memchr(b'\0', path).unwrap_or(path.len());
|
|
let path = ffi::OsStr::from_bytes(&path[..nul_pos]);
|
|
return vm.fsdecode(path).into();
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let family = addr.family();
|
|
if family == libc::AF_CAN as libc::sa_family_t {
|
|
// AF_CAN address: (interface_name,) or (interface_name, can_id)
|
|
let can_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_can) };
|
|
let ifindex = can_addr.can_ifindex;
|
|
let ifname = if ifindex == 0 {
|
|
String::new()
|
|
} else {
|
|
let mut buf = [0u8; libc::IF_NAMESIZE];
|
|
let ret = unsafe {
|
|
libc::if_indextoname(
|
|
ifindex as libc::c_uint,
|
|
buf.as_mut_ptr() as *mut libc::c_char,
|
|
)
|
|
};
|
|
if ret.is_null() {
|
|
String::new()
|
|
} else {
|
|
let nul_pos = memchr::memchr(b'\0', &buf).unwrap_or(buf.len());
|
|
String::from_utf8_lossy(&buf[..nul_pos]).into_owned()
|
|
}
|
|
};
|
|
return vm.ctx.new_tuple(vec![vm.ctx.new_str(ifname).into()]).into();
|
|
}
|
|
if family == libc::AF_ALG as libc::sa_family_t {
|
|
// AF_ALG address: (type, name)
|
|
let alg_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_alg) };
|
|
let type_bytes = &alg_addr.salg_type;
|
|
let name_bytes = &alg_addr.salg_name;
|
|
let type_nul = memchr::memchr(b'\0', type_bytes).unwrap_or(type_bytes.len());
|
|
let name_nul = memchr::memchr(b'\0', name_bytes).unwrap_or(name_bytes.len());
|
|
let type_str = String::from_utf8_lossy(&type_bytes[..type_nul]).into_owned();
|
|
let name_str = String::from_utf8_lossy(&name_bytes[..name_nul]).into_owned();
|
|
return vm
|
|
.ctx
|
|
.new_tuple(vec![
|
|
vm.ctx.new_str(type_str).into(),
|
|
vm.ctx.new_str(name_str).into(),
|
|
])
|
|
.into();
|
|
}
|
|
}
|
|
// TODO: support more address families
|
|
(String::new(), 0).to_pyobject(vm)
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn gethostname(vm: &VirtualMachine) -> PyResult<PyStrRef> {
|
|
gethostname::gethostname()
|
|
.into_string()
|
|
.map(|hostname| vm.ctx.new_str(hostname))
|
|
.map_err(|err| vm.new_os_error(err.into_string().unwrap()))
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
#[pyfunction]
|
|
fn sethostname(hostname: PyStrRef) -> nix::Result<()> {
|
|
nix::unistd::sethostname(hostname.as_str())
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn inet_aton(ip_string: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
|
|
ip_string
|
|
.as_str()
|
|
.parse::<Ipv4Addr>()
|
|
.map(|ip_addr| Vec::<u8>::from(ip_addr.octets()))
|
|
.map_err(|_| {
|
|
vm.new_os_error("illegal IP address string passed to inet_aton".to_owned())
|
|
})
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn inet_ntoa(packed_ip: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyStrRef> {
|
|
let packed_ip = packed_ip.borrow_buf();
|
|
let packed_ip = <&[u8; 4]>::try_from(&*packed_ip)
|
|
.map_err(|_| vm.new_os_error("packed IP wrong length for inet_ntoa".to_owned()))?;
|
|
Ok(vm.ctx.new_str(Ipv4Addr::from(*packed_ip).to_string()))
|
|
}
|
|
|
|
fn cstr_opt_as_ptr(x: &OptionalArg<ffi::CString>) -> *const libc::c_char {
|
|
x.as_ref().map_or_else(core::ptr::null, |s| s.as_ptr())
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn getservbyname(
|
|
servicename: PyStrRef,
|
|
protocolname: OptionalArg<PyStrRef>,
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<u16> {
|
|
let cstr_name = servicename.to_cstring(vm)?;
|
|
let cstr_proto = protocolname
|
|
.as_ref()
|
|
.map(|s| s.to_cstring(vm))
|
|
.transpose()?;
|
|
let cstr_proto = cstr_opt_as_ptr(&cstr_proto);
|
|
let serv = unsafe { c::getservbyname(cstr_name.as_ptr() as _, cstr_proto as _) };
|
|
if serv.is_null() {
|
|
return Err(vm.new_os_error("service/proto not found".to_owned()));
|
|
}
|
|
let port = unsafe { (*serv).s_port };
|
|
Ok(u16::from_be(port as u16))
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn getservbyport(
|
|
port: i32,
|
|
protocolname: OptionalArg<PyStrRef>,
|
|
vm: &VirtualMachine,
|
|
) -> PyResult<String> {
|
|
let port = port
|
|
.to_u16()
|
|
.ok_or_else(|| vm.new_overflow_error("getservbyport: port must be 0-65535."))?;
|
|
let cstr_proto = protocolname
|
|
.as_ref()
|
|
.map(|s| s.to_cstring(vm))
|
|
.transpose()?;
|
|
let cstr_proto = cstr_opt_as_ptr(&cstr_proto);
|
|
let serv = unsafe { c::getservbyport(port.to_be() as _, cstr_proto as _) };
|
|
if serv.is_null() {
|
|
return Err(vm.new_os_error("port/proto not found".to_owned()));
|
|
}
|
|
let s = unsafe { ffi::CStr::from_ptr((*serv).s_name as _) };
|
|
Ok(s.to_string_lossy().into_owned())
|
|
}
|
|
|
|
unsafe fn slice_as_uninit<T>(v: &mut [T]) -> &mut [MaybeUninit<T>] {
|
|
unsafe { &mut *(v as *mut [T] as *mut [MaybeUninit<T>]) }
|
|
}
|
|
|
|
enum IoOrPyException {
|
|
Timeout,
|
|
Py(PyBaseExceptionRef),
|
|
Io(io::Error),
|
|
}
|
|
impl From<PyBaseExceptionRef> for IoOrPyException {
|
|
fn from(exc: PyBaseExceptionRef) -> Self {
|
|
Self::Py(exc)
|
|
}
|
|
}
|
|
impl From<PyRef<PyOSError>> for IoOrPyException {
|
|
fn from(exc: PyRef<PyOSError>) -> Self {
|
|
Self::Py(exc.upcast())
|
|
}
|
|
}
|
|
impl From<io::Error> for IoOrPyException {
|
|
fn from(err: io::Error) -> Self {
|
|
Self::Io(err)
|
|
}
|
|
}
|
|
impl IoOrPyException {
|
|
fn errno(self) -> PyResult<i32> {
|
|
match self {
|
|
Self::Timeout => Ok(errcode!(EWOULDBLOCK)),
|
|
Self::Io(err) => Ok(err.posix_errno()),
|
|
Self::Py(exc) => Err(exc),
|
|
}
|
|
}
|
|
}
|
|
impl IntoPyException for IoOrPyException {
|
|
#[inline]
|
|
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
|
|
match self {
|
|
Self::Timeout => timeout_error(vm).upcast(),
|
|
Self::Py(exc) => exc,
|
|
Self::Io(err) => err.into_pyexception(vm),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub(crate) enum SelectKind {
|
|
Read,
|
|
Write,
|
|
Connect,
|
|
}
|
|
|
|
/// returns true if timed out
|
|
pub(crate) fn sock_select(
|
|
sock: &Socket,
|
|
kind: SelectKind,
|
|
interval: Option<Duration>,
|
|
) -> io::Result<bool> {
|
|
#[cfg(unix)]
|
|
{
|
|
use nix::poll::*;
|
|
use std::os::fd::AsFd;
|
|
let events = match kind {
|
|
SelectKind::Read => PollFlags::POLLIN,
|
|
SelectKind::Write => PollFlags::POLLOUT,
|
|
SelectKind::Connect => PollFlags::POLLOUT | PollFlags::POLLERR,
|
|
};
|
|
let mut pollfd = [PollFd::new(sock.as_fd(), events)];
|
|
let timeout = match interval {
|
|
Some(d) => d.try_into().unwrap_or(PollTimeout::MAX),
|
|
None => PollTimeout::NONE,
|
|
};
|
|
let ret = poll(&mut pollfd, timeout)?;
|
|
Ok(ret == 0)
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use crate::select;
|
|
|
|
let fd = sock_fileno(sock);
|
|
|
|
let mut reads = select::FdSet::new();
|
|
let mut writes = select::FdSet::new();
|
|
let mut errs = select::FdSet::new();
|
|
|
|
let fd = fd as usize;
|
|
match kind {
|
|
SelectKind::Read => reads.insert(fd),
|
|
SelectKind::Write => writes.insert(fd),
|
|
SelectKind::Connect => {
|
|
writes.insert(fd);
|
|
errs.insert(fd);
|
|
}
|
|
}
|
|
|
|
let mut interval = interval.map(|dur| select::timeval {
|
|
tv_sec: dur.as_secs() as _,
|
|
tv_usec: dur.subsec_micros() as _,
|
|
});
|
|
|
|
select::select(
|
|
fd as i32 + 1,
|
|
&mut reads,
|
|
&mut writes,
|
|
&mut errs,
|
|
interval.as_mut(),
|
|
)
|
|
.map(|ret| ret == 0)
|
|
}
|
|
}
|
|
|
|
#[derive(FromArgs)]
|
|
struct GAIOptions {
|
|
#[pyarg(positional)]
|
|
host: Option<ArgStrOrBytesLike>,
|
|
#[pyarg(positional)]
|
|
port: Option<Either<ArgStrOrBytesLike, i32>>,
|
|
|
|
#[pyarg(positional, default = c::AF_UNSPEC)]
|
|
family: i32,
|
|
#[pyarg(positional, default = 0)]
|
|
ty: i32,
|
|
#[pyarg(positional, default = 0)]
|
|
proto: i32,
|
|
#[pyarg(positional, default = 0)]
|
|
flags: i32,
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn getaddrinfo(
|
|
opts: GAIOptions,
|
|
vm: &VirtualMachine,
|
|
) -> Result<Vec<PyObjectRef>, IoOrPyException> {
|
|
let hints = dns_lookup::AddrInfoHints {
|
|
socktype: opts.ty,
|
|
protocol: opts.proto,
|
|
address: opts.family,
|
|
flags: opts.flags,
|
|
};
|
|
|
|
// Encode host: str uses IDNA encoding, bytes must be valid UTF-8
|
|
let host_encoded: Option<String> = match opts.host.as_ref() {
|
|
Some(ArgStrOrBytesLike::Str(s)) => {
|
|
let encoded =
|
|
vm.state
|
|
.codec_registry
|
|
.encode_text(s.to_owned(), "idna", None, vm)?;
|
|
let host_str = core::str::from_utf8(encoded.as_bytes())
|
|
.map_err(|_| vm.new_runtime_error("idna output is not utf8".to_owned()))?;
|
|
Some(host_str.to_owned())
|
|
}
|
|
Some(ArgStrOrBytesLike::Buf(b)) => {
|
|
let bytes = b.borrow_buf();
|
|
let host_str = core::str::from_utf8(&bytes).map_err(|_| {
|
|
vm.new_unicode_decode_error("host bytes is not utf8".to_owned())
|
|
})?;
|
|
Some(host_str.to_owned())
|
|
}
|
|
None => None,
|
|
};
|
|
let host = host_encoded.as_deref();
|
|
|
|
// Encode port: str/bytes as service name, int as port number
|
|
let port_encoded: Option<String> = match opts.port.as_ref() {
|
|
Some(Either::A(sb)) => {
|
|
let port_str = match sb {
|
|
ArgStrOrBytesLike::Str(s) => {
|
|
// For str, check for surrogates and raise UnicodeEncodeError if found
|
|
s.to_str()
|
|
.ok_or_else(|| vm.new_unicode_encode_error("surrogates not allowed"))?
|
|
.to_owned()
|
|
}
|
|
ArgStrOrBytesLike::Buf(b) => {
|
|
// For bytes, check if it's valid UTF-8
|
|
let bytes = b.borrow_buf();
|
|
core::str::from_utf8(&bytes)
|
|
.map_err(|_| {
|
|
vm.new_unicode_decode_error("port is not utf8".to_owned())
|
|
})?
|
|
.to_owned()
|
|
}
|
|
};
|
|
Some(port_str)
|
|
}
|
|
Some(Either::B(i)) => Some(i.to_string()),
|
|
None => None,
|
|
};
|
|
let port = port_encoded.as_deref();
|
|
|
|
let addrs = dns_lookup::getaddrinfo(host, port, Some(hints))
|
|
.map_err(|err| convert_socket_error(vm, err, SocketError::GaiError))?;
|
|
|
|
let list = addrs
|
|
.map(|ai| {
|
|
ai.map(|ai| {
|
|
vm.new_tuple((
|
|
ai.address,
|
|
ai.socktype,
|
|
ai.protocol,
|
|
ai.canonname,
|
|
get_ip_addr_tuple(&ai.sockaddr, vm),
|
|
))
|
|
.into()
|
|
})
|
|
})
|
|
.collect::<io::Result<Vec<_>>>()?;
|
|
Ok(list)
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn gethostbyaddr(
|
|
addr: PyStrRef,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(String, PyListRef, PyListRef), IoOrPyException> {
|
|
let addr = get_addr(vm, addr, c::AF_UNSPEC)?;
|
|
let (hostname, _) = dns_lookup::getnameinfo(&addr, 0)
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::HError))?;
|
|
Ok((
|
|
hostname,
|
|
vm.ctx.new_list(vec![]),
|
|
vm.ctx
|
|
.new_list(vec![vm.ctx.new_str(addr.ip().to_string()).into()]),
|
|
))
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn gethostbyname(name: PyStrRef, vm: &VirtualMachine) -> Result<String, IoOrPyException> {
|
|
let addr = get_addr(vm, name, c::AF_INET)?;
|
|
match addr {
|
|
SocketAddr::V4(ip) => Ok(ip.ip().to_string()),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn gethostbyname_ex(
|
|
name: PyStrRef,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(String, PyListRef, PyListRef), IoOrPyException> {
|
|
let addr = get_addr(vm, name, c::AF_INET)?;
|
|
let (hostname, _) = dns_lookup::getnameinfo(&addr, 0)
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::HError))?;
|
|
Ok((
|
|
hostname,
|
|
vm.ctx.new_list(vec![]),
|
|
vm.ctx
|
|
.new_list(vec![vm.ctx.new_str(addr.ip().to_string()).into()]),
|
|
))
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn inet_pton(af_inet: i32, ip_string: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
|
|
static ERROR_MSG: &str = "illegal IP address string passed to inet_pton";
|
|
let ip_addr = match af_inet {
|
|
c::AF_INET => ip_string
|
|
.as_str()
|
|
.parse::<Ipv4Addr>()
|
|
.map_err(|_| vm.new_os_error(ERROR_MSG.to_owned()))?
|
|
.octets()
|
|
.to_vec(),
|
|
c::AF_INET6 => ip_string
|
|
.as_str()
|
|
.parse::<Ipv6Addr>()
|
|
.map_err(|_| vm.new_os_error(ERROR_MSG.to_owned()))?
|
|
.octets()
|
|
.to_vec(),
|
|
_ => return Err(vm.new_os_error("Address family not supported by protocol".to_owned())),
|
|
};
|
|
Ok(ip_addr)
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn inet_ntop(af_inet: i32, packed_ip: ArgBytesLike, vm: &VirtualMachine) -> PyResult<String> {
|
|
let packed_ip = packed_ip.borrow_buf();
|
|
match af_inet {
|
|
c::AF_INET => {
|
|
let packed_ip = <&[u8; 4]>::try_from(&*packed_ip).map_err(|_| {
|
|
vm.new_value_error("invalid length of packed IP address string")
|
|
})?;
|
|
Ok(Ipv4Addr::from(*packed_ip).to_string())
|
|
}
|
|
c::AF_INET6 => {
|
|
let packed_ip = <&[u8; 16]>::try_from(&*packed_ip).map_err(|_| {
|
|
vm.new_value_error("invalid length of packed IP address string")
|
|
})?;
|
|
Ok(get_ipv6_addr_str(Ipv6Addr::from(*packed_ip)))
|
|
}
|
|
_ => Err(vm.new_value_error(format!("unknown address family {af_inet}"))),
|
|
}
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn getprotobyname(name: PyStrRef, vm: &VirtualMachine) -> PyResult {
|
|
let cstr = name.to_cstring(vm)?;
|
|
let proto = unsafe { c::getprotobyname(cstr.as_ptr() as _) };
|
|
if proto.is_null() {
|
|
return Err(vm.new_os_error("protocol not found".to_owned()));
|
|
}
|
|
let num = unsafe { (*proto).p_proto };
|
|
Ok(vm.ctx.new_int(num).into())
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn getnameinfo(
|
|
address: PyTupleRef,
|
|
flags: i32,
|
|
vm: &VirtualMachine,
|
|
) -> Result<(String, String), IoOrPyException> {
|
|
match address.len() {
|
|
2..=4 => {}
|
|
_ => {
|
|
return Err(vm.new_type_error("illegal sockaddr argument").into());
|
|
}
|
|
}
|
|
let (addr, flowinfo, scopeid) = Address::from_tuple_ipv6(&address, vm)?;
|
|
let hints = dns_lookup::AddrInfoHints {
|
|
address: c::AF_UNSPEC,
|
|
socktype: c::SOCK_DGRAM,
|
|
flags: c::AI_NUMERICHOST,
|
|
protocol: 0,
|
|
};
|
|
let service = addr.port.to_string();
|
|
let mut res =
|
|
dns_lookup::getaddrinfo(Some(addr.host.as_str()), Some(&service), Some(hints))
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))?
|
|
.filter_map(Result::ok);
|
|
let mut ainfo = res.next().unwrap();
|
|
if res.next().is_some() {
|
|
return Err(vm
|
|
.new_os_error("sockaddr resolved to multiple addresses".to_owned())
|
|
.into());
|
|
}
|
|
match &mut ainfo.sockaddr {
|
|
SocketAddr::V4(_) => {
|
|
if address.len() != 2 {
|
|
return Err(vm
|
|
.new_os_error("IPv4 sockaddr must be 2 tuple".to_owned())
|
|
.into());
|
|
}
|
|
}
|
|
SocketAddr::V6(addr) => {
|
|
addr.set_flowinfo(flowinfo);
|
|
addr.set_scope_id(scopeid);
|
|
}
|
|
}
|
|
dns_lookup::getnameinfo(&ainfo.sockaddr, flags)
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[pyfunction]
|
|
fn socketpair(
|
|
family: OptionalArg<i32>,
|
|
socket_kind: OptionalArg<i32>,
|
|
proto: OptionalArg<i32>,
|
|
) -> Result<(PySocket, PySocket), IoOrPyException> {
|
|
let family = family.unwrap_or(libc::AF_UNIX);
|
|
let socket_kind = socket_kind.unwrap_or(libc::SOCK_STREAM);
|
|
let proto = proto.unwrap_or(0);
|
|
let (a, b) = Socket::pair(family.into(), socket_kind.into(), Some(proto.into()))?;
|
|
let py_a = PySocket::default();
|
|
py_a.init_inner(family, socket_kind, proto, a)?;
|
|
let py_b = PySocket::default();
|
|
py_b.init_inner(family, socket_kind, proto, b)?;
|
|
Ok((py_a, py_b))
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
type IfIndex = c::c_uint;
|
|
#[cfg(windows)]
|
|
type IfIndex = u32; // NET_IFINDEX but windows-sys 0.59 doesn't have it
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
#[pyfunction]
|
|
fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult<IfIndex> {
|
|
let name = name.to_cstring(vm)?;
|
|
// in case 'if_nametoindex' does not set errno
|
|
crate::common::os::set_errno(libc::ENODEV);
|
|
let ret = unsafe { c::if_nametoindex(name.as_ptr() as _) };
|
|
if ret == 0 {
|
|
Err(vm.new_last_errno_error())
|
|
} else {
|
|
Ok(ret)
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
#[pyfunction]
|
|
fn if_indextoname(index: IfIndex, vm: &VirtualMachine) -> PyResult<String> {
|
|
let mut buf = [0; c::IF_NAMESIZE + 1];
|
|
// in case 'if_indextoname' does not set errno
|
|
crate::common::os::set_errno(libc::ENXIO);
|
|
let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) };
|
|
if ret.is_null() {
|
|
Err(vm.new_last_errno_error())
|
|
} else {
|
|
let buf = unsafe { ffi::CStr::from_ptr(buf.as_ptr() as _) };
|
|
Ok(buf.to_string_lossy().into_owned())
|
|
}
|
|
}
|
|
|
|
#[cfg(any(
|
|
windows,
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "fuchsia",
|
|
target_os = "ios",
|
|
target_os = "linux",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
))]
|
|
#[pyfunction]
|
|
fn if_nameindex(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
|
|
#[cfg(not(windows))]
|
|
{
|
|
let list = nix::net::if_::if_nameindex()
|
|
.map_err(|err| err.into_pyexception(vm))?
|
|
.to_slice()
|
|
.iter()
|
|
.map(|iface| {
|
|
let tup: (u32, String) =
|
|
(iface.index(), iface.name().to_string_lossy().into_owned());
|
|
tup.to_pyobject(vm)
|
|
})
|
|
.collect();
|
|
|
|
Ok(list)
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::ptr;
|
|
use windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH;
|
|
|
|
let table = MibTable::get_raw().map_err(|err| err.into_pyexception(vm))?;
|
|
let list = table.as_slice().iter().map(|entry| {
|
|
let name =
|
|
get_name(&entry.InterfaceLuid).map_err(|err| err.into_pyexception(vm))?;
|
|
let tup = (entry.InterfaceIndex, name.to_string_lossy());
|
|
Ok(tup.to_pyobject(vm))
|
|
});
|
|
let list = list.collect::<PyResult<_>>()?;
|
|
return Ok(list);
|
|
|
|
fn get_name(luid: &NET_LUID_LH) -> io::Result<widestring::WideCString> {
|
|
let mut buf = [0; c::IF_NAMESIZE + 1];
|
|
let ret = unsafe {
|
|
IpHelper::ConvertInterfaceLuidToNameW(luid, buf.as_mut_ptr(), buf.len())
|
|
};
|
|
if ret == 0 {
|
|
Ok(widestring::WideCString::from_ustr_truncate(
|
|
widestring::WideStr::from_slice(&buf[..]),
|
|
))
|
|
} else {
|
|
Err(io::Error::from_raw_os_error(ret as i32))
|
|
}
|
|
}
|
|
struct MibTable {
|
|
ptr: ptr::NonNull<IpHelper::MIB_IF_TABLE2>,
|
|
}
|
|
impl MibTable {
|
|
fn get_raw() -> io::Result<Self> {
|
|
let mut ptr = ptr::null_mut();
|
|
let ret = unsafe { IpHelper::GetIfTable2Ex(IpHelper::MibIfTableRaw, &mut ptr) };
|
|
if ret == 0 {
|
|
let ptr = unsafe { ptr::NonNull::new_unchecked(ptr) };
|
|
Ok(Self { ptr })
|
|
} else {
|
|
Err(io::Error::from_raw_os_error(ret as i32))
|
|
}
|
|
}
|
|
}
|
|
impl MibTable {
|
|
fn as_slice(&self) -> &[IpHelper::MIB_IF_ROW2] {
|
|
unsafe {
|
|
let p = self.ptr.as_ptr();
|
|
let ptr = &raw const (*p).Table as *const IpHelper::MIB_IF_ROW2;
|
|
std::slice::from_raw_parts(ptr, (*p).NumEntries as usize)
|
|
}
|
|
}
|
|
}
|
|
impl Drop for MibTable {
|
|
fn drop(&mut self) {
|
|
unsafe { IpHelper::FreeMibTable(self.ptr.as_ptr() as *mut _) };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_addr(
|
|
vm: &VirtualMachine,
|
|
pyname: PyStrRef,
|
|
af: i32,
|
|
) -> Result<SocketAddr, IoOrPyException> {
|
|
let name = pyname.as_str();
|
|
if name.is_empty() {
|
|
let hints = dns_lookup::AddrInfoHints {
|
|
address: af,
|
|
socktype: c::SOCK_DGRAM,
|
|
flags: c::AI_PASSIVE,
|
|
protocol: 0,
|
|
};
|
|
let mut res = dns_lookup::getaddrinfo(None, Some("0"), Some(hints))
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))?;
|
|
let ainfo = res.next().unwrap()?;
|
|
if res.next().is_some() {
|
|
return Err(vm
|
|
.new_os_error("wildcard resolved to multiple address".to_owned())
|
|
.into());
|
|
}
|
|
return Ok(ainfo.sockaddr);
|
|
}
|
|
if name == "255.255.255.255" || name == "<broadcast>" {
|
|
match af {
|
|
c::AF_INET | c::AF_UNSPEC => {}
|
|
_ => {
|
|
return Err(vm
|
|
.new_os_error("address family mismatched".to_owned())
|
|
.into());
|
|
}
|
|
}
|
|
return Ok(SocketAddr::V4(net::SocketAddrV4::new(
|
|
c::INADDR_BROADCAST.into(),
|
|
0,
|
|
)));
|
|
}
|
|
if let c::AF_INET | c::AF_UNSPEC = af
|
|
&& let Ok(addr) = name.parse::<Ipv4Addr>()
|
|
{
|
|
return Ok(SocketAddr::V4(net::SocketAddrV4::new(addr, 0)));
|
|
}
|
|
if matches!(af, c::AF_INET | c::AF_UNSPEC)
|
|
&& !name.contains('%')
|
|
&& let Ok(addr) = name.parse::<Ipv6Addr>()
|
|
{
|
|
return Ok(SocketAddr::V6(net::SocketAddrV6::new(addr, 0, 0, 0)));
|
|
}
|
|
let hints = dns_lookup::AddrInfoHints {
|
|
address: af,
|
|
..Default::default()
|
|
};
|
|
let name = vm
|
|
.state
|
|
.codec_registry
|
|
.encode_text(pyname, "idna", None, vm)?;
|
|
let name = core::str::from_utf8(name.as_bytes())
|
|
.map_err(|_| vm.new_runtime_error("idna output is not utf8"))?;
|
|
let mut res = dns_lookup::getaddrinfo(Some(name), None, Some(hints))
|
|
.map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))?;
|
|
Ok(res.next().unwrap().map(|ainfo| ainfo.sockaddr)?)
|
|
}
|
|
|
|
fn sock_from_raw(fileno: RawSocket, vm: &VirtualMachine) -> PyResult<Socket> {
|
|
let invalid = {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(windows)] {
|
|
fileno == INVALID_SOCKET
|
|
} else {
|
|
fileno < 0
|
|
}
|
|
}
|
|
};
|
|
if invalid {
|
|
return Err(vm.new_value_error("negative file descriptor"));
|
|
}
|
|
Ok(unsafe { sock_from_raw_unchecked(fileno) })
|
|
}
|
|
/// SAFETY: fileno must not be equal to INVALID_SOCKET
|
|
unsafe fn sock_from_raw_unchecked(fileno: RawSocket) -> Socket {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::io::FromRawFd;
|
|
unsafe { Socket::from_raw_fd(fileno) }
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::os::windows::io::FromRawSocket;
|
|
unsafe { Socket::from_raw_socket(fileno) }
|
|
}
|
|
}
|
|
pub(super) fn sock_fileno(sock: &Socket) -> RawSocket {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::io::AsRawFd;
|
|
sock.as_raw_fd()
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::os::windows::io::AsRawSocket;
|
|
sock.as_raw_socket()
|
|
}
|
|
}
|
|
fn into_sock_fileno(sock: Socket) -> RawSocket {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::io::IntoRawFd;
|
|
sock.into_raw_fd()
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::os::windows::io::IntoRawSocket;
|
|
sock.into_raw_socket()
|
|
}
|
|
}
|
|
|
|
pub(super) const INVALID_SOCKET: RawSocket = {
|
|
#[cfg(unix)]
|
|
{
|
|
-1
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
windows_sys::Win32::Networking::WinSock::INVALID_SOCKET as RawSocket
|
|
}
|
|
};
|
|
|
|
fn convert_socket_error(
|
|
vm: &VirtualMachine,
|
|
err: dns_lookup::LookupError,
|
|
err_kind: SocketError,
|
|
) -> IoOrPyException {
|
|
if let dns_lookup::LookupErrorKind::System = err.kind() {
|
|
return io::Error::from(err).into();
|
|
}
|
|
let strerr = {
|
|
#[cfg(unix)]
|
|
{
|
|
let s = match err_kind {
|
|
SocketError::GaiError => unsafe {
|
|
ffi::CStr::from_ptr(libc::gai_strerror(err.error_num()))
|
|
},
|
|
SocketError::HError => unsafe {
|
|
ffi::CStr::from_ptr(libc::hstrerror(err.error_num()))
|
|
},
|
|
};
|
|
s.to_str().unwrap()
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
"getaddrinfo failed"
|
|
}
|
|
};
|
|
let exception_cls = match err_kind {
|
|
SocketError::GaiError => gaierror(vm),
|
|
SocketError::HError => herror(vm),
|
|
};
|
|
vm.new_os_subtype_error(exception_cls, Some(err.error_num()), strerr)
|
|
.into()
|
|
}
|
|
|
|
fn timeout_error(vm: &VirtualMachine) -> PyRef<PyOSError> {
|
|
timeout_error_msg(vm, "timed out".to_owned())
|
|
}
|
|
pub(crate) fn timeout_error_msg(vm: &VirtualMachine, msg: String) -> PyRef<PyOSError> {
|
|
vm.new_os_subtype_error(timeout(vm), None, msg)
|
|
}
|
|
|
|
fn get_ipv6_addr_str(ipv6: Ipv6Addr) -> String {
|
|
match ipv6.to_ipv4() {
|
|
// instead of "::0.0.ddd.ddd" it's "::xxxx"
|
|
Some(v4) if !ipv6.is_unspecified() && matches!(v4.octets(), [0, 0, _, _]) => {
|
|
format!("::{:x}", u32::from(v4))
|
|
}
|
|
_ => ipv6.to_string(),
|
|
}
|
|
}
|
|
|
|
pub(crate) struct Deadline {
|
|
deadline: Instant,
|
|
}
|
|
|
|
impl Deadline {
|
|
fn new(timeout: Duration) -> Self {
|
|
Self {
|
|
deadline: Instant::now() + timeout,
|
|
}
|
|
}
|
|
fn time_until(&self) -> Result<Duration, IoOrPyException> {
|
|
self.deadline
|
|
.checked_duration_since(Instant::now())
|
|
// past the deadline already
|
|
.ok_or(IoOrPyException::Timeout)
|
|
}
|
|
}
|
|
|
|
static DEFAULT_TIMEOUT: AtomicCell<f64> = AtomicCell::new(-1.0);
|
|
|
|
#[pyfunction]
|
|
fn getdefaulttimeout() -> Option<f64> {
|
|
let timeout = DEFAULT_TIMEOUT.load();
|
|
if timeout >= 0.0 { Some(timeout) } else { None }
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn setdefaulttimeout(timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
|
|
let val = match timeout {
|
|
Some(t) => {
|
|
let f = t.into_float();
|
|
if f.is_nan() {
|
|
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
|
|
}
|
|
if f < 0.0 || !f.is_finite() {
|
|
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
|
|
}
|
|
f
|
|
}
|
|
None => -1.0,
|
|
};
|
|
DEFAULT_TIMEOUT.store(val);
|
|
Ok(())
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn dup(x: PyObjectRef, vm: &VirtualMachine) -> Result<RawSocket, IoOrPyException> {
|
|
let sock = get_raw_sock(x, vm)?;
|
|
let sock = core::mem::ManuallyDrop::new(sock_from_raw(sock, vm)?);
|
|
let newsock = sock.try_clone()?;
|
|
let fd = into_sock_fileno(newsock);
|
|
#[cfg(windows)]
|
|
crate::vm::stdlib::nt::raw_set_handle_inheritable(fd as _, false)?;
|
|
Ok(fd)
|
|
}
|
|
|
|
#[pyfunction]
|
|
fn close(x: PyObjectRef, vm: &VirtualMachine) -> Result<(), IoOrPyException> {
|
|
Ok(close_inner(get_raw_sock(x, vm)?)?)
|
|
}
|
|
|
|
fn close_inner(x: RawSocket) -> io::Result<()> {
|
|
#[cfg(unix)]
|
|
use libc::close;
|
|
#[cfg(windows)]
|
|
use windows_sys::Win32::Networking::WinSock::closesocket as close;
|
|
let ret = unsafe { close(x as _) };
|
|
if ret < 0 {
|
|
let err = std::io::Error::last_os_error();
|
|
if err.raw_os_error() != Some(errcode!(ECONNRESET)) {
|
|
return Err(err);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
enum SocketError {
|
|
HError,
|
|
GaiError,
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
fn checked_cmsg_len(len: usize) -> Option<usize> {
|
|
// SAFETY: CMSG_LEN is always safe
|
|
let cmsg_len = |length| unsafe { libc::CMSG_LEN(length) };
|
|
if len as u64 > (i32::MAX as u64 - cmsg_len(0) as u64) {
|
|
return None;
|
|
}
|
|
let res = cmsg_len(len as _) as usize;
|
|
if res > i32::MAX as usize || res < len {
|
|
return None;
|
|
}
|
|
Some(res)
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
fn checked_cmsg_space(len: usize) -> Option<usize> {
|
|
// SAFETY: CMSG_SPACE is always safe
|
|
let cmsg_space = |length| unsafe { libc::CMSG_SPACE(length) };
|
|
if len as u64 > (i32::MAX as u64 - cmsg_space(1) as u64) {
|
|
return None;
|
|
}
|
|
let res = cmsg_space(len as _) as usize;
|
|
if res > i32::MAX as usize || res < len {
|
|
return None;
|
|
}
|
|
Some(res)
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
#[pyfunction(name = "CMSG_LEN")]
|
|
fn cmsg_len(length: usize, vm: &VirtualMachine) -> PyResult<usize> {
|
|
checked_cmsg_len(length)
|
|
.ok_or_else(|| vm.new_overflow_error("CMSG_LEN() argument out of range"))
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "redox")))]
|
|
#[pyfunction(name = "CMSG_SPACE")]
|
|
fn cmsg_space(length: usize, vm: &VirtualMachine) -> PyResult<usize> {
|
|
checked_cmsg_space(length)
|
|
.ok_or_else(|| vm.new_overflow_error("CMSG_SPACE() argument out of range"))
|
|
}
|
|
}
|