Allow NTP messages to be exchanged as a payload of PTP messages to enable full hardware timestamping on NICs that can timestamp PTP packets only. Implemented is the protocol described in this draft (version 00): https://datatracker.ietf.org/doc/draft-mlichvar-ntp-over-ptp/ This is an experimental feature. It can be changed or removed in future. The used PTP domain is 123 and the NTP TLV type is 0x2023 from the "do not propagate" experimental range. The ptpport directive enables NTP-over-PTP as a server and as a client for all sources that have the port option set to the PTP port. The port should be the PTP event port (319) to trigger timestamping in the hardware. The implementation is contained to ntp_io. It is transparent to ntp_core.
609 lines
17 KiB
C
609 lines
17 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Timo Teras 2009
|
|
* Copyright (C) Miroslav Lichvar 2009, 2013-2016, 2018-2020
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
This file deals with the IO aspects of reading and writing NTP packets
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "memory.h"
|
|
#include "ntp_io.h"
|
|
#include "ntp_core.h"
|
|
#include "ntp_sources.h"
|
|
#include "ptp.h"
|
|
#include "sched.h"
|
|
#include "socket.h"
|
|
#include "local.h"
|
|
#include "logging.h"
|
|
#include "conf.h"
|
|
#include "privops.h"
|
|
#include "util.h"
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
#include "ntp_io_linux.h"
|
|
#endif
|
|
|
|
#define INVALID_SOCK_FD -1
|
|
|
|
/* The server/peer and client sockets for IPv4 and IPv6 */
|
|
static int server_sock_fd4;
|
|
static int server_sock_fd6;
|
|
static int client_sock_fd4;
|
|
static int client_sock_fd6;
|
|
|
|
/* Reference counters for server sockets to keep them open only when needed */
|
|
static int server_sock_ref4;
|
|
static int server_sock_ref6;
|
|
|
|
/* Flag indicating we create a new connected client socket for each
|
|
server instead of sharing client_sock_fd4 and client_sock_fd6 */
|
|
static int separate_client_sockets;
|
|
|
|
/* Flag indicating the server sockets are not created dynamically when needed,
|
|
either to have a socket for client requests when separate client sockets
|
|
are disabled and client port is equal to server port, or the server port is
|
|
disabled */
|
|
static int permanent_server_sockets;
|
|
|
|
/* Flag indicating the server IPv4 socket is bound to an address */
|
|
static int bound_server_sock_fd4;
|
|
|
|
/* PTP event port, or 0 if disabled */
|
|
static int ptp_port;
|
|
|
|
/* Shared server/client sockets for NTP-over-PTP */
|
|
static int ptp_sock_fd4;
|
|
static int ptp_sock_fd6;
|
|
|
|
/* Buffer for transmitted NTP-over-PTP messages */
|
|
static PTP_NtpMessage *ptp_message;
|
|
|
|
/* Flag indicating that we have been initialised */
|
|
static int initialised=0;
|
|
|
|
/* ================================================== */
|
|
|
|
/* Forward prototypes */
|
|
static void read_from_socket(int sock_fd, int event, void *anything);
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
open_socket(int family, int local_port, int client_only, IPSockAddr *remote_addr)
|
|
{
|
|
int sock_fd, sock_flags, dscp, events = SCH_FILE_INPUT;
|
|
IPSockAddr local_addr;
|
|
char *iface;
|
|
|
|
if (!SCK_IsIpFamilyEnabled(family))
|
|
return INVALID_SOCK_FD;
|
|
|
|
if (!client_only) {
|
|
CNF_GetBindAddress(family, &local_addr.ip_addr);
|
|
iface = CNF_GetBindNtpInterface();
|
|
} else {
|
|
CNF_GetBindAcquisitionAddress(family, &local_addr.ip_addr);
|
|
iface = CNF_GetBindAcquisitionInterface();
|
|
}
|
|
|
|
local_addr.port = local_port;
|
|
|
|
sock_flags = SCK_FLAG_RX_DEST_ADDR | SCK_FLAG_PRIV_BIND;
|
|
if (!client_only)
|
|
sock_flags |= SCK_FLAG_BROADCAST;
|
|
|
|
sock_fd = SCK_OpenUdpSocket(remote_addr, &local_addr, iface, sock_flags);
|
|
if (sock_fd < 0) {
|
|
if (!client_only)
|
|
LOG(LOGS_ERR, "Could not open NTP socket on %s", UTI_IPSockAddrToString(&local_addr));
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
|
|
dscp = CNF_GetNtpDscp();
|
|
if (dscp > 0 && dscp < 64) {
|
|
#ifdef IP_TOS
|
|
if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_TOS, dscp << 2))
|
|
;
|
|
#endif
|
|
}
|
|
|
|
if (!client_only && family == IPADDR_INET4 && local_addr.port > 0)
|
|
bound_server_sock_fd4 = local_addr.ip_addr.addr.in4 != INADDR_ANY;
|
|
|
|
/* Enable kernel/HW timestamping of packets */
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, client_only, &events))
|
|
#endif
|
|
if (!SCK_EnableKernelRxTimestamping(sock_fd))
|
|
;
|
|
|
|
/* Register handler for read and possibly exception events on the socket */
|
|
SCH_AddFileHandler(sock_fd, events, read_from_socket, NULL);
|
|
|
|
return sock_fd;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
open_separate_client_socket(IPSockAddr *remote_addr)
|
|
{
|
|
return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
close_socket(int sock_fd)
|
|
{
|
|
if (sock_fd == INVALID_SOCK_FD)
|
|
return;
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_NotifySocketClosing(sock_fd);
|
|
#endif
|
|
SCH_RemoveFileHandler(sock_fd);
|
|
SCK_CloseSocket(sock_fd);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_Initialise(void)
|
|
{
|
|
int server_port, client_port;
|
|
|
|
assert(!initialised);
|
|
initialised = 1;
|
|
|
|
#ifdef PRIVOPS_BINDSOCKET
|
|
SCK_SetPrivBind(PRV_BindSocket);
|
|
#endif
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_Initialise();
|
|
#else
|
|
if (1) {
|
|
CNF_HwTsInterface *conf_iface;
|
|
if (CNF_GetHwTsInterface(0, &conf_iface))
|
|
LOG_FATAL("HW timestamping not supported");
|
|
}
|
|
#endif
|
|
|
|
server_port = CNF_GetNTPPort();
|
|
client_port = CNF_GetAcquisitionPort();
|
|
|
|
/* Use separate connected sockets if client port is negative */
|
|
separate_client_sockets = client_port < 0;
|
|
if (client_port < 0)
|
|
client_port = 0;
|
|
|
|
permanent_server_sockets = !server_port || (!separate_client_sockets &&
|
|
client_port == server_port);
|
|
|
|
server_sock_fd4 = INVALID_SOCK_FD;
|
|
server_sock_fd6 = INVALID_SOCK_FD;
|
|
client_sock_fd4 = INVALID_SOCK_FD;
|
|
client_sock_fd6 = INVALID_SOCK_FD;
|
|
server_sock_ref4 = 0;
|
|
server_sock_ref6 = 0;
|
|
|
|
if (permanent_server_sockets && server_port) {
|
|
server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL);
|
|
server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL);
|
|
}
|
|
|
|
if (!separate_client_sockets) {
|
|
if (client_port != server_port || !server_port) {
|
|
client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL);
|
|
client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL);
|
|
} else {
|
|
client_sock_fd4 = server_sock_fd4;
|
|
client_sock_fd6 = server_sock_fd6;
|
|
}
|
|
}
|
|
|
|
if ((server_port && permanent_server_sockets &&
|
|
server_sock_fd4 == INVALID_SOCK_FD && server_sock_fd6 == INVALID_SOCK_FD) ||
|
|
(!separate_client_sockets &&
|
|
client_sock_fd4 == INVALID_SOCK_FD && client_sock_fd6 == INVALID_SOCK_FD)) {
|
|
LOG_FATAL("Could not open NTP sockets");
|
|
}
|
|
|
|
ptp_port = CNF_GetPtpPort();
|
|
ptp_sock_fd4 = INVALID_SOCK_FD;
|
|
ptp_sock_fd6 = INVALID_SOCK_FD;
|
|
ptp_message = NULL;
|
|
|
|
if (ptp_port > 0) {
|
|
ptp_sock_fd4 = open_socket(IPADDR_INET4, ptp_port, 0, NULL);
|
|
ptp_sock_fd6 = open_socket(IPADDR_INET6, ptp_port, 0, NULL);
|
|
ptp_message = MallocNew(PTP_NtpMessage);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_Finalise(void)
|
|
{
|
|
if (server_sock_fd4 != client_sock_fd4)
|
|
close_socket(client_sock_fd4);
|
|
close_socket(server_sock_fd4);
|
|
server_sock_fd4 = client_sock_fd4 = INVALID_SOCK_FD;
|
|
|
|
if (server_sock_fd6 != client_sock_fd6)
|
|
close_socket(client_sock_fd6);
|
|
close_socket(server_sock_fd6);
|
|
server_sock_fd6 = client_sock_fd6 = INVALID_SOCK_FD;
|
|
|
|
close_socket(ptp_sock_fd4);
|
|
close_socket(ptp_sock_fd6);
|
|
ptp_sock_fd4 = ptp_sock_fd6 = INVALID_SOCK_FD;
|
|
Free(ptp_message);
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_Finalise();
|
|
#endif
|
|
|
|
initialised = 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_OpenClientSocket(NTP_Remote_Address *remote_addr)
|
|
{
|
|
switch (remote_addr->ip_addr.family) {
|
|
case IPADDR_INET4:
|
|
if (ptp_port > 0 && remote_addr->port == ptp_port)
|
|
return ptp_sock_fd4;
|
|
if (separate_client_sockets)
|
|
return open_separate_client_socket(remote_addr);
|
|
return client_sock_fd4;
|
|
case IPADDR_INET6:
|
|
if (ptp_port > 0 && remote_addr->port == ptp_port)
|
|
return ptp_sock_fd6;
|
|
if (separate_client_sockets)
|
|
return open_separate_client_socket(remote_addr);
|
|
return client_sock_fd6;
|
|
default:
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
|
|
{
|
|
switch (remote_addr->ip_addr.family) {
|
|
case IPADDR_INET4:
|
|
if (ptp_port > 0 && remote_addr->port == ptp_port)
|
|
return ptp_sock_fd4;
|
|
if (permanent_server_sockets)
|
|
return server_sock_fd4;
|
|
if (server_sock_fd4 == INVALID_SOCK_FD)
|
|
server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0, NULL);
|
|
if (server_sock_fd4 != INVALID_SOCK_FD)
|
|
server_sock_ref4++;
|
|
return server_sock_fd4;
|
|
case IPADDR_INET6:
|
|
if (ptp_port > 0 && remote_addr->port == ptp_port)
|
|
return ptp_sock_fd6;
|
|
if (permanent_server_sockets)
|
|
return server_sock_fd6;
|
|
if (server_sock_fd6 == INVALID_SOCK_FD)
|
|
server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0, NULL);
|
|
if (server_sock_fd6 != INVALID_SOCK_FD)
|
|
server_sock_ref6++;
|
|
return server_sock_fd6;
|
|
default:
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
is_ptp_socket(int sock_fd)
|
|
{
|
|
return ptp_port > 0 && (sock_fd == ptp_sock_fd4 || sock_fd == ptp_sock_fd6);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_CloseClientSocket(int sock_fd)
|
|
{
|
|
if (is_ptp_socket(sock_fd))
|
|
return;
|
|
|
|
if (separate_client_sockets)
|
|
close_socket(sock_fd);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_CloseServerSocket(int sock_fd)
|
|
{
|
|
if (permanent_server_sockets || sock_fd == INVALID_SOCK_FD || is_ptp_socket(sock_fd))
|
|
return;
|
|
|
|
if (sock_fd == server_sock_fd4) {
|
|
if (--server_sock_ref4 <= 0) {
|
|
close_socket(server_sock_fd4);
|
|
server_sock_fd4 = INVALID_SOCK_FD;
|
|
}
|
|
} else if (sock_fd == server_sock_fd6) {
|
|
if (--server_sock_ref6 <= 0) {
|
|
close_socket(server_sock_fd6);
|
|
server_sock_fd6 = INVALID_SOCK_FD;
|
|
}
|
|
} else {
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_IsServerSocket(int sock_fd)
|
|
{
|
|
return sock_fd != INVALID_SOCK_FD &&
|
|
(sock_fd == server_sock_fd4 || sock_fd == server_sock_fd6 || is_ptp_socket(sock_fd));
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_IsServerSocketOpen(void)
|
|
{
|
|
return server_sock_fd4 != INVALID_SOCK_FD || server_sock_fd6 != INVALID_SOCK_FD ||
|
|
ptp_sock_fd4 != INVALID_SOCK_FD || ptp_sock_fd6 != INVALID_SOCK_FD;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_IsServerConnectable(NTP_Remote_Address *remote_addr)
|
|
{
|
|
int sock_fd;
|
|
|
|
sock_fd = open_separate_client_socket(remote_addr);
|
|
if (sock_fd == INVALID_SOCK_FD)
|
|
return 0;
|
|
|
|
close_socket(sock_fd);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_message(SCK_Message *message, int sock_fd, int event)
|
|
{
|
|
NTP_Local_Address local_addr;
|
|
NTP_Local_Timestamp local_ts;
|
|
struct timespec sched_ts;
|
|
|
|
SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL);
|
|
local_ts.source = NTP_TS_DAEMON;
|
|
sched_ts = local_ts.ts;
|
|
|
|
if (message->addr_type != SCK_ADDR_IP) {
|
|
DEBUG_LOG("Unexpected address type");
|
|
return;
|
|
}
|
|
|
|
local_addr.ip_addr = message->local_addr.ip;
|
|
local_addr.if_index = message->if_index;;
|
|
local_addr.sock_fd = sock_fd;
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (NIO_Linux_ProcessMessage(message, &local_addr, &local_ts, event))
|
|
return;
|
|
#else
|
|
if (!UTI_IsZeroTimespec(&message->timestamp.kernel)) {
|
|
LCL_CookTime(&message->timestamp.kernel, &local_ts.ts, &local_ts.err);
|
|
local_ts.source = NTP_TS_KERNEL;
|
|
}
|
|
#endif
|
|
|
|
if (local_ts.source != NTP_TS_DAEMON)
|
|
DEBUG_LOG("Updated RX timestamp delay=%.9f tss=%u",
|
|
UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts), local_ts.source);
|
|
|
|
if (!NIO_UnwrapMessage(message, sock_fd))
|
|
return;
|
|
|
|
/* Just ignore the packet if it's not of a recognized length */
|
|
if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet)) {
|
|
DEBUG_LOG("Unexpected length");
|
|
return;
|
|
}
|
|
|
|
NSR_ProcessRx(&message->remote_addr.ip, &local_addr, &local_ts, message->data, message->length);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
read_from_socket(int sock_fd, int event, void *anything)
|
|
{
|
|
SCK_Message *messages;
|
|
int i, received, flags = 0;
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (NIO_Linux_ProcessEvent(sock_fd, event))
|
|
return;
|
|
#endif
|
|
|
|
if (event == SCH_FILE_EXCEPTION) {
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
flags |= SCK_FLAG_MSG_ERRQUEUE;
|
|
#else
|
|
assert(0);
|
|
#endif
|
|
}
|
|
|
|
messages = SCK_ReceiveMessages(sock_fd, flags, &received);
|
|
if (!messages)
|
|
return;
|
|
|
|
for (i = 0; i < received; i++)
|
|
process_message(&messages[i], sock_fd, event);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_UnwrapMessage(SCK_Message *message, int sock_fd)
|
|
{
|
|
PTP_NtpMessage *msg;
|
|
|
|
if (!is_ptp_socket(sock_fd))
|
|
return 1;
|
|
|
|
if (message->length <= PTP_NTP_PREFIX_LENGTH) {
|
|
DEBUG_LOG("Unexpected length");
|
|
return 0;
|
|
}
|
|
|
|
msg = message->data;
|
|
|
|
if (msg->header.type != PTP_TYPE_DELAY_REQ || msg->header.version != PTP_VERSION ||
|
|
ntohs(msg->header.length) != message->length ||
|
|
msg->header.domain != PTP_DOMAIN_NTP ||
|
|
ntohs(msg->header.flags) != PTP_FLAG_UNICAST ||
|
|
ntohs(msg->tlv_header.type) != PTP_TLV_NTP ||
|
|
ntohs(msg->tlv_header.length) != message->length - PTP_NTP_PREFIX_LENGTH) {
|
|
DEBUG_LOG("Unexpected PTP message");
|
|
return 0;
|
|
}
|
|
|
|
message->data = (char *)message->data + PTP_NTP_PREFIX_LENGTH;
|
|
message->length -= PTP_NTP_PREFIX_LENGTH;
|
|
|
|
DEBUG_LOG("Unwrapped PTP->NTP len=%d", message->length);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
wrap_message(SCK_Message *message, int sock_fd)
|
|
{
|
|
assert(PTP_NTP_PREFIX_LENGTH == 48);
|
|
|
|
if (!is_ptp_socket(sock_fd))
|
|
return 1;
|
|
|
|
if (!ptp_message)
|
|
return 0;
|
|
|
|
if (message->length < NTP_HEADER_LENGTH ||
|
|
message->length + PTP_NTP_PREFIX_LENGTH > sizeof (*ptp_message)) {
|
|
DEBUG_LOG("Unexpected length");
|
|
return 0;
|
|
}
|
|
|
|
memset(ptp_message, 0, PTP_NTP_PREFIX_LENGTH);
|
|
ptp_message->header.type = PTP_TYPE_DELAY_REQ;
|
|
ptp_message->header.version = PTP_VERSION;
|
|
ptp_message->header.length = htons(PTP_NTP_PREFIX_LENGTH + message->length);
|
|
ptp_message->header.domain = PTP_DOMAIN_NTP;
|
|
ptp_message->header.flags = htons(PTP_FLAG_UNICAST);
|
|
ptp_message->tlv_header.type = htons(PTP_TLV_NTP);
|
|
ptp_message->tlv_header.length = htons(message->length);
|
|
memcpy((char *)ptp_message + PTP_NTP_PREFIX_LENGTH, message->data, message->length);
|
|
|
|
message->data = ptp_message;
|
|
message->length += PTP_NTP_PREFIX_LENGTH;
|
|
|
|
DEBUG_LOG("Wrapped NTP->PTP len=%d", message->length - PTP_NTP_PREFIX_LENGTH);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Send a packet to remote address from local address */
|
|
|
|
int
|
|
NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
|
|
NTP_Local_Address *local_addr, int length, int process_tx)
|
|
{
|
|
SCK_Message message;
|
|
|
|
assert(initialised);
|
|
|
|
if (local_addr->sock_fd == INVALID_SOCK_FD) {
|
|
DEBUG_LOG("No socket to send to %s", UTI_IPSockAddrToString(remote_addr));
|
|
return 0;
|
|
}
|
|
|
|
SCK_InitMessage(&message, SCK_ADDR_IP);
|
|
|
|
message.data = packet;
|
|
message.length = length;
|
|
|
|
if (!wrap_message(&message, local_addr->sock_fd))
|
|
return 0;
|
|
|
|
/* Specify remote address if the socket is not connected */
|
|
if (NIO_IsServerSocket(local_addr->sock_fd) || !separate_client_sockets) {
|
|
message.remote_addr.ip.ip_addr = remote_addr->ip_addr;
|
|
message.remote_addr.ip.port = remote_addr->port;
|
|
}
|
|
|
|
message.local_addr.ip = local_addr->ip_addr;
|
|
|
|
/* Don't require responses to non-link-local addresses to use the same
|
|
interface */
|
|
message.if_index = SCK_IsLinkLocalIPAddress(&message.remote_addr.ip.ip_addr) ?
|
|
local_addr->if_index : INVALID_IF_INDEX;
|
|
|
|
#if !defined(HAVE_IN_PKTINFO) && defined(IP_SENDSRCADDR)
|
|
/* On FreeBSD a local IPv4 address cannot be specified on bound socket */
|
|
if (message.local_addr.ip.family == IPADDR_INET4 &&
|
|
(local_addr->sock_fd != server_sock_fd4 || bound_server_sock_fd4))
|
|
message.local_addr.ip.family = IPADDR_UNSPEC;
|
|
#endif
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (process_tx)
|
|
NIO_Linux_RequestTxTimestamp(&message, local_addr->sock_fd);
|
|
#endif
|
|
|
|
if (!SCK_SendMessage(local_addr->sock_fd, &message, 0))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|