chrony/ntp_io.c
Timo Teras 0666d04ab2 Set reply source IP from query destination IP
Currently, on multihomed host, when chrony is not bound to a specific
IP address, a query is sent to an interface and the default source IP
hint for the back route differs, the reply will have a source IP
different than where the query was destinied to. This will cause
problems because connection tracking firewalls will drop the replies
and most likely the client program will get confused too.

This patch uses the IP_PKTINFO mechanism to get the IP address where
received packets where targetted to and use that IP address as source
hint when sending a reply.
2009-08-07 17:02:11 +02:00

347 lines
9.7 KiB
C

/*
$Header: /cvs/src/chrony/ntp_io.c,v 1.24 2003/09/22 21:22:30 richard Exp $
=======================================================================
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
*
* 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.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*
**********************************************************************
=======================================================================
This file deals with the IO aspects of reading and writing NTP packets
*/
#include "sysincl.h"
#include "ntp_io.h"
#include "ntp_core.h"
#include "ntp_sources.h"
#include "sched.h"
#include "local.h"
#include "logging.h"
#include "conf.h"
#include "util.h"
#include <fcntl.h>
/* The file descriptor for the socket */
static int sock_fd;
/* Flag indicating that we have been initialised */
static int initialised=0;
/* ================================================== */
/* Forward prototypes */
static void read_from_socket(void *anything);
/* ================================================== */
static void
do_size_checks(void)
{
/* Assertions to check the sizes of certain data types
and the positions of certain record fields */
/* Check that certain invariants are true */
assert(sizeof(NTP_int32) == 4);
assert(sizeof(NTP_int64) == 8);
/* Check offsets of all fields in the NTP packet format */
assert(offsetof(NTP_Packet, lvm) == 0);
assert(offsetof(NTP_Packet, stratum) == 1);
assert(offsetof(NTP_Packet, poll) == 2);
assert(offsetof(NTP_Packet, precision) == 3);
assert(offsetof(NTP_Packet, root_delay) == 4);
assert(offsetof(NTP_Packet, root_dispersion) == 8);
assert(offsetof(NTP_Packet, reference_id) == 12);
assert(offsetof(NTP_Packet, reference_ts) == 16);
assert(offsetof(NTP_Packet, originate_ts) == 24);
assert(offsetof(NTP_Packet, receive_ts) == 32);
assert(offsetof(NTP_Packet, transmit_ts) == 40);
}
/* ================================================== */
void
NIO_Initialise(void)
{
struct sockaddr_in my_addr;
unsigned short port_number;
unsigned long bind_address;
int on_off = 1;
assert(!initialised);
initialised = 1;
do_size_checks();
port_number = CNF_GetNTPPort();
/* Open Internet domain UDP socket for NTP message transmissions */
#if 0
sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
#endif
if (sock_fd < 0) {
LOG_FATAL(LOGF_NtpIO, "Could not open socket : %s", strerror(errno));
}
/* Make the socket capable of re-using an old address */
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set reuseaddr socket options");
/* Don't quit - we might survive anyway */
}
/* Make the socket capable of sending broadcast pkts - needed for NTP broadcast mode */
if (setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set broadcast socket options");
/* Don't quit - we might survive anyway */
}
/* We want the local IP info too */
if (setsockopt(sock_fd, IPPROTO_IP, IP_PKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
LOG(LOGS_ERR, LOGF_NtpIO, "Could not request packet info using socket option");
/* Don't quit - we might survive anyway */
}
/* Bind the port */
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port_number);
CNF_GetBindAddress(&bind_address);
if (bind_address != 0UL) {
my_addr.sin_addr.s_addr = htonl(bind_address);
} else {
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
}
#if 0
LOG(LOGS_INFO, LOGF_NtpIO, "Initialising, socket fd=%d", sock_fd);
#endif
if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
LOG_FATAL(LOGF_NtpIO, "Could not bind socket : %s", strerror(errno));
}
/* Register handler for read events on the socket */
SCH_AddInputFileHandler(sock_fd, read_from_socket, NULL);
#if 0
if (fcntl(sock_fd, F_SETFL, O_NONBLOCK | O_NDELAY) < 0) {
LOG(LOGS_ERR, LOGF_NtpIO, "Could not make socket non-blocking");
}
if (ioctl(sock_fd, I_SETSIG, S_INPUT) < 0) {
LOG(LOGS_ERR, LOGF_NtpIO, "Could not enable signal");
}
#endif
return;
}
/* ================================================== */
void
NIO_Finalise(void)
{
if (sock_fd >= 0) {
SCH_RemoveInputFileHandler(sock_fd);
close(sock_fd);
}
sock_fd = -1;
initialised = 0;
return;
}
/* ================================================== */
/* ================================================== */
static void
read_from_socket(void *anything)
{
/* This should only be called when there is something
to read, otherwise it will block. */
int status;
ReceiveBuffer message;
struct sockaddr_in where_from;
unsigned int flags = 0;
struct timeval now;
NTP_Remote_Address remote_addr;
double local_clock_err;
char cmsgbuf[256];
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
assert(initialised);
LCL_ReadCookedTime(&now, &local_clock_err);
iov.iov_base = message.arbitrary;
iov.iov_len = sizeof(message);
msg.msg_name = &where_from;
msg.msg_namelen = sizeof(where_from);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (void *) cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
status = recvmsg(sock_fd, &msg, flags);
/* Don't bother checking if read failed or why if it did. More
likely than not, it will be connection refused, resulting from a
previous sendto() directing a datagram at a port that is not
listening (which appears to generate an ICMP response, and on
some architectures e.g. Linux this is translated into an error
reponse on a subsequent recvfrom). */
if (status > 0) {
remote_addr.ip_addr = ntohl(where_from.sin_addr.s_addr);
remote_addr.local_ip_addr = 0;
remote_addr.port = ntohs(where_from.sin_port);
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo ipi;
memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
remote_addr.local_ip_addr = ntohl(ipi.ipi_spec_dst.s_addr);
}
}
if (status == NTP_NORMAL_PACKET_SIZE) {
NSR_ProcessReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr);
} else if (status == sizeof(NTP_Packet)) {
NSR_ProcessAuthenticatedReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr);
} else {
/* Just ignore the packet if it's not of a recognized length */
}
}
return;
}
/* ================================================== */
/* Send a packet to given address */
static void
send_packet(void *packet, int packetlen, NTP_Remote_Address *remote_addr)
{
struct sockaddr_in remote;
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
char cmsgbuf[256];
int cmsglen;
assert(initialised);
remote.sin_family = AF_INET;
remote.sin_port = htons(remote_addr->port);
remote.sin_addr.s_addr = htonl(remote_addr->ip_addr);
iov.iov_base = packet;
iov.iov_len = packetlen;
msg.msg_name = &remote;
msg.msg_namelen = sizeof(remote);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
cmsglen = 0;
if (remote_addr->local_ip_addr) {
struct in_pktinfo *ipi;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
cmsglen += CMSG_SPACE(sizeof(struct in_pktinfo));
ipi = (struct in_pktinfo *) CMSG_DATA(cmsg);
memset(ipi, 0, sizeof(struct in_pktinfo));
ipi->ipi_spec_dst.s_addr = htonl(remote_addr->local_ip_addr);
#if 0
LOG(LOGS_INFO, LOGF_NtpIO, "sending to %s:%d from %s",
UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, UTI_IPToDottedQuad(remote_addr->local_ip_addr));
#endif
}
msg.msg_controllen = cmsglen;
if (sendmsg(sock_fd, &msg, 0) < 0) {
LOG(LOGS_WARN, LOGF_NtpIO, "Could not send to %s:%d : %s",
UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, strerror(errno));
}
return;
}
/* ================================================== */
/* Send an unauthenticated packet to a given address */
void
NIO_SendNormalPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr)
{
send_packet((void *) packet, NTP_NORMAL_PACKET_SIZE, remote_addr);
}
/* ================================================== */
/* Send an authenticated packet to a given address */
void
NIO_SendAuthenticatedPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr)
{
send_packet((void *) packet, sizeof(NTP_Packet), remote_addr);
}
/* ================================================== */
/* We ought to use getservbyname, but I can't really see this changing */
#define ECHO_PORT 7
void
NIO_SendEcho(NTP_Remote_Address *remote_addr)
{
unsigned long magic_message = 0xbe7ab1e7UL;
NTP_Remote_Address addr;
addr = *remote_addr;
addr.port = ECHO_PORT;
send_packet((void *) &magic_message, sizeof(unsigned long), &addr);
}