! ! If no source address is given use the UDP socket trick to get an ! idea of what the kernel thinks our source address for a given ! target should be. An equal change has been committed to traceroute ! in r201806. This is needed as long as rip_output() in the ! !INP_HDRINCL always picks the primary jail address if jailed. ! The proper solution would be to do what the comment there suggests ! and call in-kernel source address selection. ! ! Requested by: many (all finding ping does not work (properly) in jail) ! Tested by: Mark Felder (feld feld me) ! TODO: bz to fix the kernel as the proper fix ! Index: sbin/ping/ping.c =================================================================== --- sbin/ping/ping.c (revision 233876) +++ sbin/ping/ping.c (working copy) @@ -212,6 +212,7 @@ static void status(int); static void stopit(int); static void tvsub(struct timeval *, const struct timeval *); static void usage(void) __dead2; +static int getsaddr(struct sockaddr_in *, struct sockaddr_in *); int main(int argc, char *const *argv) @@ -526,9 +527,30 @@ main(int argc, char *const *argv) if (options & F_PINGFILLED) { fill((char *)datap, payload); } + + bzero(&whereto, sizeof(whereto)); + to = &whereto; + to->sin_family = AF_INET; + to->sin_len = sizeof *to; + if (inet_aton(target, &to->sin_addr) != 0) { + hostname = target; + } else { + hp = gethostbyname2(target, AF_INET); + if (!hp) + errx(EX_NOHOST, "cannot resolve %s: %s", + target, hstrerror(h_errno)); + + if ((unsigned)hp->h_length > sizeof(to->sin_addr)) + errx(1, "gethostbyname2 returned an illegal address"); + memcpy(&to->sin_addr, hp->h_addr_list[0], sizeof to->sin_addr); + (void)strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1); + hnamebuf[sizeof(hnamebuf) - 1] = '\0'; + hostname = hnamebuf; + } + + bzero((char *)&sock_in, sizeof(sock_in)); + sock_in.sin_family = AF_INET; if (source) { - bzero((char *)&sock_in, sizeof(sock_in)); - sock_in.sin_family = AF_INET; if (inet_aton(source, &sock_in.sin_addr) != 0) { shostname = source; } else { @@ -548,29 +570,13 @@ main(int argc, char *const *argv) snamebuf[sizeof(snamebuf) - 1] = '\0'; shostname = snamebuf; } - if (bind(s, (struct sockaddr *)&sock_in, sizeof sock_in) == -1) - err(1, "bind"); - } - - bzero(&whereto, sizeof(whereto)); - to = &whereto; - to->sin_family = AF_INET; - to->sin_len = sizeof *to; - if (inet_aton(target, &to->sin_addr) != 0) { - hostname = target; } else { - hp = gethostbyname2(target, AF_INET); - if (!hp) - errx(EX_NOHOST, "cannot resolve %s: %s", - target, hstrerror(h_errno)); - - if ((unsigned)hp->h_length > sizeof(to->sin_addr)) - errx(1, "gethostbyname2 returned an illegal address"); - memcpy(&to->sin_addr, hp->h_addr_list[0], sizeof to->sin_addr); - (void)strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1); - hnamebuf[sizeof(hnamebuf) - 1] = '\0'; - hostname = hnamebuf; + if (getsaddr(to, &sock_in) != 0) + err(1, "getsaddr"); + /* XXX-BZ set source? */ } + if (bind(s, (struct sockaddr *)&sock_in, sizeof sock_in) == -1) + err(1, "bind"); if (options & F_FLOOD && options & F_INTERVAL) errx(EX_USAGE, "-f and -i: incompatible options"); @@ -1701,3 +1707,77 @@ usage(void) " [-z tos] mcast-group"); exit(EX_USAGE); } + +/* Derived from usr.sbin/traceroute/findsaddr-udp.c. */ +/*- + * Copyright (c) 2010,2012 Bjoern A. Zeeb + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Return the source address for the given destination address. + * + * This makes use of proper source address selection in the FreeBSD kernel + * even taking jails into account (sys/netinet/in_pcb.c:in_pcbladdr()). + * We open a UDP socket, and connect to the destination, letting the kernel + * do the bind and then read the source IPv4 address using getsockname(2). + * This has multiple advantages: no need to do PF_ROUTE operations possibly + * needing special privileges, jails properly taken into account and most + * important - getting the result the kernel would give us rather than + * best-guessing ourselves. + */ +static int +getsaddr(struct sockaddr_in *to, struct sockaddr_in *from) +{ + struct sockaddr_in cto, cfrom; + socklen_t len; + int error, s; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == -1) + return (s); + + len = sizeof(struct sockaddr_in); + memcpy(&cto, to, len); + cto.sin_port = htons(65535); /* Dummy port for connect(2). */ + error = connect(s, (struct sockaddr *)&cto, len); + if (error == -1) + goto err; + + error = getsockname(s, (struct sockaddr *)&cfrom, &len); + if (error == -1) + goto err; + + if (len != sizeof(struct sockaddr_in) || cfrom.sin_family != AF_INET) { + error = -2; + goto err; + } + + /* Update source address for traceroute. */ + from->sin_addr.s_addr = cfrom.sin_addr.s_addr; + +err: + (void) close(s); + + return (error); +}