Add --tftp-single-port option.

This commit is contained in:
Simon Kelley
2020-01-05 16:21:24 +00:00
parent 18a6bdd541
commit 66f62650c3
6 changed files with 113 additions and 64 deletions

View File

@@ -60,6 +60,8 @@ version 2.81
Fix bug which caused very rarely caused zero-length DHCPv6 packets.
Thanks to Dereck Higgins for spotting this.
Add --tftp-single-port option.
version 2.80
Add support for RFC 4039 DHCP rapid commit. Thanks to Ashram Method

View File

@@ -1943,7 +1943,12 @@ specifies a range of ports for use by TFTP transfers. This can be
useful when TFTP has to traverse a firewall. The start of the range
cannot be lower than 1025 unless dnsmasq is running as root. The number
of concurrent TFTP connections is limited by the size of the port range.
.TP
.TP
.B --tftp-single-port
Run in a mode where the TFTP server uses ONLY the well-known port (69) for its end
of the TFTP transfer. This allows TFTP to work when there in NAT is the path between client and server. Note that
this is not strictly compliant with the RFCs specifying the TFTP protocol: use at your own risk.
.TP
.B \-C, --conf-file=<file>
Specify a configuration file. The presence of this option stops dnsmasq from reading the default configuration
file (normally /etc/dnsmasq.conf). Multiple files may be specified by repeating the option

View File

@@ -1107,7 +1107,7 @@ int main (int argc, char **argv)
#endif
/* must do this just before select(), when we know no
/* must do this just before do_poll(), when we know no
more calls to my_syslog() can occur */
set_log_writer();
@@ -1670,11 +1670,12 @@ static int set_dns_listeners(time_t now)
#ifdef HAVE_TFTP
int tftp = 0;
struct tftp_transfer *transfer;
for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
{
tftp++;
poll_listen(transfer->sockfd, POLLIN);
}
if (!option_bool(OPT_SINGLE_PORT))
for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
{
tftp++;
poll_listen(transfer->sockfd, POLLIN);
}
#endif
/* will we be able to get memory? */

View File

@@ -262,7 +262,8 @@ struct event_desc {
#define OPT_RAPID_COMMIT 57
#define OPT_UBUS 58
#define OPT_IGNORE_CLID 59
#define OPT_LAST 60
#define OPT_SINGLE_PORT 60
#define OPT_LAST 61
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -960,6 +961,8 @@ struct tftp_transfer {
unsigned int block, blocksize, expansion;
off_t offset;
union mysockaddr peer;
union all_addr source;
int if_index;
char opt_blocksize, opt_transize, netascii, carrylf;
struct tftp_file *file;
struct tftp_transfer *next;

View File

@@ -168,6 +168,7 @@ struct myoption {
#define LOPT_CAA 356
#define LOPT_SHARED_NET 357
#define LOPT_IGNORE_CLID 358
#define LOPT_SINGLE_PORT 359
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -258,6 +259,7 @@ static const struct myoption opts[] =
{ "tftp-max", 1, 0, LOPT_TFTP_MAX },
{ "tftp-mtu", 1, 0, LOPT_TFTP_MTU },
{ "tftp-lowercase", 0, 0, LOPT_TFTP_LC },
{ "tftp-single-port", 0, 0, LOPT_SINGLE_PORT },
{ "ptr-record", 1, 0, LOPT_PTR },
{ "naptr-record", 1, 0, LOPT_NAPTR },
{ "bridge-interface", 1, 0 , LOPT_BRIDGE },
@@ -458,6 +460,7 @@ static struct {
{ LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL },
{ LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL },
{ LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL },
{ LOPT_SINGLE_PORT, OPT_SINGLE_PORT, NULL, gettext_noop("Use only one port for TFTP server."), NULL },
{ LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL },
{ LOPT_MAX_LOGS, ARG_ONE, "[=<integer>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL },
{ LOPT_REBIND, OPT_NO_REBIND, NULL, gettext_noop("Stop DNS rebinding. Filter private IP ranges when resolving."), NULL },

View File

@@ -18,6 +18,7 @@
#ifdef HAVE_TFTP
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix);
static void free_transfer(struct tftp_transfer *transfer);
static ssize_t tftp_err(int err, char *packet, char *message, char *file);
@@ -50,7 +51,7 @@ void tftp_request(struct listener *listen, time_t now)
struct ifreq ifr;
int is_err = 1, if_index = 0, mtu = 0;
struct iname *tmp;
struct tftp_transfer *transfer;
struct tftp_transfer *transfer = NULL, **up;
int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
int mtuflag = IP_PMTUDISC_DONT;
@@ -239,6 +240,25 @@ void tftp_request(struct listener *listen, time_t now)
if (mtu == 0)
mtu = daemon->tftp_mtu;
/* data transfer via server listening socket */
if (option_bool(OPT_SINGLE_PORT))
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
if (sockaddr_isequal(&peer, &transfer->peer))
{
if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
{
/* Handle repeated RRQ or abandoned transfer from same host and port
by unlinking and reusing the struct transfer. */
*up = transfer->next;
break;
}
else
{
handle_tftp(now, transfer, len);
return;
}
}
if (name)
{
/* check for per-interface prefix */
@@ -264,16 +284,21 @@ void tftp_request(struct listener *listen, time_t now)
#endif
}
if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
/* May reuse struct transfer from abandoned transfer in single port mode. */
if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
return;
if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1)
if (option_bool(OPT_SINGLE_PORT))
transfer->sockfd = listen->tftpfd;
else if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1)
{
free(transfer);
return;
}
transfer->peer = peer;
transfer->source = addra;
transfer->if_index = if_index;
transfer->timeout = now + 2;
transfer->backoff = 1;
transfer->block = 1;
@@ -286,7 +311,7 @@ void tftp_request(struct listener *listen, time_t now)
prettyprint_addr(&peer, daemon->addrbuff);
/* if we have a nailed-down range, iterate until we find a free one. */
while (1)
while (!option_bool(OPT_SINGLE_PORT))
{
if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 ||
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
@@ -315,7 +340,7 @@ void tftp_request(struct listener *listen, time_t now)
p = packet + 2;
end = packet + len;
if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
!(filename = next(&p, end)) ||
!(mode = next(&p, end)) ||
@@ -439,9 +464,8 @@ void tftp_request(struct listener *listen, time_t now)
is_err = 0;
}
}
while (sendto(transfer->sockfd, packet, len, 0,
(struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR);
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
if (is_err)
free_transfer(transfer);
@@ -538,60 +562,25 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix)
void check_tftp_listeners(time_t now)
{
struct tftp_transfer *transfer, *tmp, **up;
ssize_t len;
struct ack {
unsigned short op, block;
} *mess = (struct ack *)daemon->packet;
/* Check for activity on any existing transfers */
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
{
tmp = transfer->next;
prettyprint_addr(&transfer->peer, daemon->addrbuff);
/* In single port mode, all packets come via port 69 and tftp_request() */
if (!option_bool(OPT_SINGLE_PORT))
for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
if (poll_check(transfer->sockfd, POLLIN))
{
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
{
if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
{
/* Got ack, ensure we take the (re)transmit path */
transfer->timeout = now;
transfer->backoff = 0;
if (transfer->block++ != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
}
else if (ntohs(mess->op) == OP_ERR)
{
char *p = daemon->packet + sizeof(struct ack);
char *end = daemon->packet + len;
char *err = next(&p, end);
/* Sanitise error message */
if (!err)
err = "";
else
sanitise(err);
my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
(int)ntohs(mess->block), err,
daemon->addrbuff);
/* Got err, ensure we take abort */
transfer->timeout = now;
transfer->backoff = 100;
}
}
handle_tftp(now, transfer, recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0));
}
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
{
tmp = transfer->next;
if (difftime(now, transfer->timeout) >= 0.0)
{
int endcon = 0;
ssize_t len;
/* timeout, retransmit */
transfer->timeout += 1 + (1<<transfer->backoff);
@@ -613,13 +602,14 @@ void check_tftp_listeners(time_t now)
}
if (len != 0)
while(sendto(transfer->sockfd, daemon->packet, len, 0,
(struct sockaddr *)&transfer->peer, sa_len(&transfer->peer)) == -1 && errno == EINTR);
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
if (endcon || len == 0)
{
strcpy(daemon->namebuff, transfer->file->filename);
sanitise(daemon->namebuff);
prettyprint_addr(&transfer->peer, daemon->addrbuff);
my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
/* unlink */
*up = tmp;
@@ -638,15 +628,60 @@ void check_tftp_listeners(time_t now)
up = &transfer->next;
}
}
/* packet in daemon->packet as this is called. */
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
{
struct ack {
unsigned short op, block;
} *mess = (struct ack *)daemon->packet;
if (len >= (ssize_t)sizeof(struct ack))
{
if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
{
/* Got ack, ensure we take the (re)transmit path */
transfer->timeout = now;
transfer->backoff = 0;
if (transfer->block++ != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
}
else if (ntohs(mess->op) == OP_ERR)
{
char *p = daemon->packet + sizeof(struct ack);
char *end = daemon->packet + len;
char *err = next(&p, end);
prettyprint_addr(&transfer->peer, daemon->addrbuff);
/* Sanitise error message */
if (!err)
err = "";
else
sanitise(err);
my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
(int)ntohs(mess->block), err,
daemon->addrbuff);
/* Got err, ensure we take abort */
transfer->timeout = now;
transfer->backoff = 100;
}
}
}
static void free_transfer(struct tftp_transfer *transfer)
{
close(transfer->sockfd);
if (!option_bool(OPT_SINGLE_PORT))
close(transfer->sockfd);
if (transfer->file && (--transfer->file->refcount) == 0)
{
close(transfer->file->fd);
free(transfer->file);
}
free(transfer);
}