diff options
author | Denis Vlasenko | 2006-12-27 04:35:09 +0000 |
---|---|---|
committer | Denis Vlasenko | 2006-12-27 04:35:09 +0000 |
commit | 7b76233290bd9dead1848f28ed6d0edfcceb8e09 (patch) | |
tree | b963999fc54eddb65f1929b894f868e24851fc9c /networking | |
download | busybox-1_3_0.zip busybox-1_3_0.tar.gz |
Correcting tag name to be like previous ones1_3_0
Diffstat (limited to 'networking')
81 files changed, 26725 insertions, 0 deletions
diff --git a/networking/Config.in b/networking/Config.in new file mode 100644 index 0000000..e671107 --- /dev/null +++ b/networking/Config.in @@ -0,0 +1,720 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Networking Utilities" + +config FEATURE_IPV6 + bool "Enable IPv6 support" + default n + help + Enable IPv6 support in busybox. + This adds IPv6 support in the networking applets. + +config ARPING + bool "arping" + default n + help + Ping hosts by ARP packets + +config DNSD + bool "dnsd" + default n + help + Small and static DNS server daemon. + +config ETHER_WAKE + bool "ether-wake" + default n + help + Send a magic packet to wake up sleeping machines. + +config FAKEIDENTD + bool "fakeidentd" + default n + select FEATURE_SYSLOG + help + fakeidentd listens on the ident port and returns a predefined + fake value on any query. + +config FTPGET + bool "ftpget" + default n + help + Retrieve a remote file via FTP. + +config FTPPUT + bool "ftpput" + default n + help + Store a remote file via FTP. + +config FEATURE_FTPGETPUT_LONG_OPTIONS + bool "Enable long options in ftpget/ftpput" + default n + depends on GETOPT_LONG && (FTPGET || FTPPUT) + help + Support long options for the ftpget/ftpput applet. + +config HOSTNAME + bool "hostname" + default n + help + Show or set the system's host name + +config HTTPD + bool "httpd" + default n + help + Serve web pages via an HTTP server. + +config FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + bool "Support reloading the global config file using hup signal" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option enables processing of SIGHUP to reload cached + configuration settings. + +config FEATURE_HTTPD_SETUID + bool "Enable support -u <user> option" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option allows the server to run as a specific user + rather than defaulting to the user that starts the server. + Use of this option requires special privileges to change to a + different user. + +config FEATURE_HTTPD_BASIC_AUTH + bool "Enable Basic http Authentication" + default y + depends on HTTPD + help + Utilizes password settings from /etc/httpd.conf for basic + authentication on a per url basis. + +config FEATURE_HTTPD_AUTH_MD5 + bool "Support MD5 crypted passwords for http Authentication" + default n + depends on FEATURE_HTTPD_BASIC_AUTH + help + Enables basic per URL authentication from /etc/httpd.conf + using md5 passwords. + +config FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + bool "Support loading additional MIME types at run-time" + default n + depends on HTTPD + help + This option enables support for additional MIME types at + run-time to be specified in the configuration file. + +config FEATURE_HTTPD_CGI + bool "Support Common Gateway Interface (CGI)" + default y + depends on HTTPD + help + This option allows scripts and executables to be invoked + when specific URLs are requested. + +config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + bool "Enable support for running scripts through an interpreter" + default n + depends on FEATURE_HTTPD_CGI + help + This option enables support for running scripts through an + interpreter. Turn this on if you want PHP scripts to work + properly. You need to supply an addition line in your httpd + config file: + *.php:/path/to/your/php + +config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + bool "Support the REMOTE_PORT environment variable for CGI" + default n + depends on FEATURE_HTTPD_CGI + help + Use of this option can assist scripts in generating + references that contain a unique port number. + +config FEATURE_HTTPD_ENCODE_URL_STR + bool "Enable the -e option for shell script CGI simplification." + default y + depends on HTTPD + help + This option allows html encoding arbitrary + strings for display of the browser. Output goes to stdout. + For example, httpd -e "<Hello World>" as + "<Hello World>". + +config IFCONFIG + bool "ifconfig" + default n + help + Ifconfig is used to configure the kernel-resident network interfaces. + +config FEATURE_IFCONFIG_STATUS + bool "Enable status reporting output (+7k)" + default y + depends on IFCONFIG + help + If ifconfig is called with no arguments it will display the status + of the currently active interfaces. + +config FEATURE_IFCONFIG_SLIP + bool "Enable slip-specific options \"keepalive\" and \"outfill\"" + default n + depends on IFCONFIG + help + Allow "keepalive" and "outfill" support for SLIP. If you're not + planning on using serial lines, leave this unchecked. + +config FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + bool "Enable options \"mem_start\", \"io_addr\", and \"irq\"" + default n + depends on IFCONFIG + help + Allow the start address for shared memory, start address for I/O, + and/or the interrupt line used by the specified device. + +config FEATURE_IFCONFIG_HW + bool "Enable option \"hw\" (ether only)" + default y + depends on IFCONFIG + help + Set the hardware address of this interface, if the device driver + supports this operation. Currently, we only support the 'ether' + class. + +config FEATURE_IFCONFIG_BROADCAST_PLUS + bool "Set the broadcast automatically" + default n + depends on IFCONFIG + help + Setting this will make ifconfig attempt to find the broadcast + automatically if the value '+' is used. + +config IFUPDOWN + bool "ifupdown" + default n + select RUN_PARTS + help + Activate or deactivate the specified interfaces. This applet makes + use of either "ifconfig" and "route" or the "ip" command to actually + configure network interfaces. Therefore, you will probably also want + to enable either IFCONFIG and ROUTE, or enable + FEATURE_IFUPDOWN_IP and the various IP options. Of + course you could use non-busybox versions of these programs, so + against my better judgement (since this will surely result in plenty + of support questions on the mailing list), I do not force you to + enable these additional options. It is up to you to supply either + "ifconfig" and "route" or the "ip" command, either via busybox or via + standalone utilities. + +config FEATURE_IFUPDOWN_IP + bool "Use ip applet" + default n + depends on IFUPDOWN + help + Use the iproute "ip" command to implement "ifup" and "ifdown", rather + than the default of using the older 'ifconfig' and 'route' utilities. + +config FEATURE_IFUPDOWN_IP_BUILTIN + bool "Use busybox ip applet" + default y + depends on FEATURE_IFUPDOWN_IP + select IP + select FEATURE_IP_ADDRESS + select FEATURE_IP_LINK + select FEATURE_IP_ROUTE + help + Use the busybox iproute "ip" applet to implement "ifupdown". + + If left disabled, you must install the full-blown iproute2 + utility or the "ifup" and "ifdown" applets will not work. + +config FEATURE_IFUPDOWN_IFCONFIG_BUILTIN + bool "Use busybox ifconfig and route applets" + default y + depends on IFUPDOWN && !FEATURE_IFUPDOWN_IP + select IFCONFIG + select ROUTE + help + Use the busybox iproute "ifconfig" and "route" applets to + implement the "ifup" and "ifdown" utilities. + + If left disabled, you must install the full-blown ifconfig + and route utilities, or the "ifup" and "ifdown" applets will not + work. + +config FEATURE_IFUPDOWN_IPV4 + bool "Enable support for IPv4" + default y + depends on IFUPDOWN + help + If you want busybox to talk IPv4, leave this on. + +config FEATURE_IFUPDOWN_IPV6 + bool "Enable support for IPv6" + default n + depends on IFUPDOWN && FEATURE_IPV6 + help + If you need support for IPv6, turn this option on. + +config FEATURE_IFUPDOWN_IPX + bool "Enable support for IPX" + default n + depends on IFUPDOWN + help + If this option is selected you can use busybox to work with IPX + networks. + +config FEATURE_IFUPDOWN_MAPPING + bool "Enable mapping support" + default n + depends on IFUPDOWN + help + This enables support for the "mapping" stanza, unless you have + a weird network setup you don't need it. + +config INETD + bool "inetd" + default n + select FEATURE_SYSLOG + help + Internet superserver daemon + +config FEATURE_INETD_SUPPORT_BUILTIN_ECHO + bool "Support echo service" + default y + depends on INETD + help + Echo received data internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + bool "Support discard service" + default y + depends on INETD + help + Internet /dev/null internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_TIME + bool "Support time service" + default y + depends on INETD + help + Return 32 bit time since 1900 internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + bool "Support daytime service" + default y + depends on INETD + help + Return human-readable time internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + bool "Support chargen service" + default y + depends on INETD + help + Familiar character generator internal inetd service + +config FEATURE_INETD_RPC + bool "Support RPC services" + default n + depends on INETD + depends on FEATURE_HAVE_RPC + help + Support Sun-RPC based services + +config IP + bool "ip" + default n + help + The "ip" applet is a TCP/IP interface configuration and routing + utility. You generally don't need "ip" to use busybox with + TCP/IP. + +config FEATURE_IP_ADDRESS + bool "ip address" + default y + depends on IP + help + Address manipulation support for the "ip" applet. + +config FEATURE_IP_LINK + bool "ip link" + default y + depends on IP + help + Configure network devices with "ip". + +config FEATURE_IP_ROUTE + bool "ip route" + default y + depends on IP + help + Add support for routing table management to "ip". + +config FEATURE_IP_TUNNEL + bool "ip tunnel" + default n + depends on IP + help + Add support for tunneling commands to "ip". + +config FEATURE_IP_RULE + bool "ip rule" + default n + depends on IP + help + Add support for rule commands to "ip". + +config FEATURE_IP_SHORT_FORMS + bool "Support short forms of ip commands." + default n + depends on IP + help + Also support short-form of ip <OBJECT> commands: + ip addr -> ipaddr + ip link -> iplink + ip route -> iproute + ip tunnel -> iptunnel + + Say N unless you desparately need the short form of the ip + object commands. + +config IPADDR + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ADDRESS + +config IPLINK + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_LINK + +config IPROUTE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ROUTE + +config IPTUNNEL + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_TUNNEL + +config IPRULE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE + +config IPCALC + bool "ipcalc" + default n + help + ipcalc takes an IP address and netmask and calculates the + resulting broadcast, network, and host range. + +config FEATURE_IPCALC_FANCY + bool "Fancy IPCALC, more options, adds 1 kbyte" + default y + depends on IPCALC + help + Adds the options hostname, prefix and silent to the output of "ipcalc". + +config FEATURE_IPCALC_LONG_OPTIONS + bool "Enable long options" + default n + depends on IPCALC && GETOPT_LONG + help + Support long options for the ipcalc applet. + +config NAMEIF + bool "nameif" + default n + select FEATURE_SYSLOG + help + nameif is used to rename network interface by its MAC address. + Renamed interfaces MUST be in the down state. + It is possible to use a file (default: /etc/mactab) + with list of new interface names and MACs. + Maximum interface name length: IF_NAMESIZE = 16 + File fields are separated by space or tab. + File format: + # Comment + new_interface_name XX:XX:XX:XX:XX:XX + +config NC + bool "nc" + default n + help + A simple Unix utility which reads and writes data across network + connections. + +config NC_SERVER + bool "Netcat server options (-lp)" + default n + depends on NC + help + Allow netcat to act as a server. + +config NC_EXTRA + bool "Netcat extensions (-eiw and filename)" + default n + depends on NC + help + Add -e (support for executing the rest of the command line after + making or receiving a successful connection), -i (delay interval for + lines sent), -w (timeout for initial connection). + +config NETSTAT + bool "netstat" + default n + help + netstat prints information about the Linux networking subsystem. + +config NSLOOKUP + bool "nslookup" + default n + help + nslookup is a tool to query Internet name servers. + +config PING + bool "ping" + default n + help + ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to + elicit an ICMP ECHO_RESPONSE from a host or gateway. + +config FEATURE_FANCY_PING + bool "Enable fancy ping output" + default y + depends on PING + help + Make the output from the ping applet include statistics, and at the + same time provide full support for ICMP packets. + +config PING6 + bool "ping6" + default n + depends on FEATURE_IPV6 + help + This will give you a ping that can talk IPv6. + +config FEATURE_FANCY_PING6 + bool "Enable fancy ping6 output" + default y + depends on PING6 + help + Make the output from the ping6 applet include statistics, and at the + same time provide full support for ICMP packets. + +config ROUTE + bool "route" + default n + help + Route displays or manipulates the kernel's IP routing tables. + +config TELNET + bool "telnet" + default n + help + Telnet is an interface to the TELNET protocol, but is also commonly + used to test other simple protocols. + +config FEATURE_TELNET_TTYPE + bool "Pass TERM type to remote host" + default y + depends on TELNET + help + Setting this option will forward the TERM environment variable to the + remote host you are connecting to. This is useful to make sure that + things like ANSI colors and other control sequences behave. + +config FEATURE_TELNET_AUTOLOGIN + bool "Pass USER type to remote host" + default y + depends on TELNET + help + Setting this option will forward the USER environment variable to the + remote host you are connecting to. This is useful when you need to + log into a machine without telling the username (autologin). This + option enables `-a' and `-l USER' arguments. + +config TELNETD + bool "telnetd" + default n + select FEATURE_SYSLOG + help + A daemon for the TELNET protocol, allowing you to log onto the host + running the daemon. Please keep in mind that the TELNET protocol + sends passwords in plain text. If you can't afford the space for an + SSH daemon and you trust your network, you may say 'y' here. As a + more secure alternative, you should seriously consider installing the + very small Dropbear SSH daemon instead: + http://matt.ucc.asn.au/dropbear/dropbear.html + + Note that for busybox telnetd to work you need several things: + First of all, your kernel needs: + UNIX98_PTYS=y + DEVPTS_FS=y + + Next, you need a /dev/pts directory on your root filesystem: + + $ ls -ld /dev/pts + drwxr-xr-x 2 root root 0 Sep 23 13:21 /dev/pts/ + + Next you need the pseudo terminal master multiplexer /dev/ptmx: + + $ ls -la /dev/ptmx + crw-rw-rw- 1 root tty 5, 2 Sep 23 13:55 /dev/ptmx + + Any /dev/ttyp[0-9]* files you may have can be removed. + Next, you need to mount the devpts filesystem on /dev/pts using: + + mount -t devpts devpts /dev/pts + + You need to be sure that Busybox has LOGIN and + FEATURE_SUID enabled. And finally, you should make + certain that Busybox has been installed setuid root: + + chown root.root /bin/busybox + chmod 4755 /bin/busybox + + with all that done, telnetd _should_ work.... + + +config FEATURE_TELNETD_STANDALONE + bool "Support standalone telnetd (not inetd only)" + default n + depends on TELNETD + help + Selecting this will make telnetd able to run standalone. + +config TFTP + bool "tftp" + default n + help + This enables the Trivial File Transfer Protocol client program. TFTP + is usually used for simple, small transfers such as a root image + for a network-enabled bootloader. + +config FEATURE_TFTP_GET + bool "Enable \"get\" command" + default y + depends on TFTP + help + Add support for the GET command within the TFTP client. This allows + a client to retrieve a file from a TFTP server. + +config FEATURE_TFTP_PUT + bool "Enable \"put\" command" + default y + depends on TFTP + help + Add support for the PUT command within the TFTP client. This allows + a client to transfer a file to a TFTP server. + +config FEATURE_TFTP_BLOCKSIZE + bool "Enable \"blocksize\" command" + default n + depends on TFTP + help + Allow the client to specify the desired block size for transfers. + +config DEBUG_TFTP + bool "Enable debug" + default n + depends on TFTP + help + Enable debug settings for tftp. This is useful if you're running + into problems with tftp as the protocol doesn't help you much when + you run into problems. + +config TRACEROUTE + bool "traceroute" + default n + help + Utility to trace the route of IP packets + +config FEATURE_TRACEROUTE_VERBOSE + bool "Enable verbose output" + default n + depends on TRACEROUTE + help + Add some verbosity to traceroute. This includes amongst other things + hostnames and ICMP response types. + +config FEATURE_TRACEROUTE_SOURCE_ROUTE + bool "Enable loose source route" + default n + depends on TRACEROUTE + help + Add option to specify a loose source route gateway + (8 maximum). + +config FEATURE_TRACEROUTE_USE_ICMP + bool "Use ICMP instead of UDP" + default n + depends on TRACEROUTE + help + Add feature to allow for ICMP ECHO instead of UDP datagrams. + +source networking/udhcp/Config.in + +config VCONFIG + bool "vconfig" + default n + help + Creates, removes, and configures VLAN interfaces + +config WGET + bool "wget" + default n + help + wget is a utility for non-interactive download of files from HTTP, + HTTPS, and FTP servers. + +config FEATURE_WGET_STATUSBAR + bool "Enable a nifty process meter (+2k)" + default y + depends on WGET + help + Enable the transfer progress bar for wget transfers. + +config FEATURE_WGET_AUTHENTICATION + bool "Enable HTTP authentication" + default y + depends on WGET + help + Support authenticated HTTP transfers. + +config FEATURE_WGET_IP6_LITERAL + bool "Enable IPv6 literal addresses" + default y + depends on WGET && FEATURE_IPV6 + help + Support IPv6 address literal notation in URLs. + +config FEATURE_WGET_LONG_OPTIONS + bool "Enable long options" + default n + depends on WGET && GETOPT_LONG + help + Support long options for the wget applet. + +config ZCIP + bool "zcip" + default n + select FEATURE_SYSLOG + help + ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927. + It's a daemon that allocates and defends a dynamically assigned + address on the 169.254/16 network, requiring no system administrator. + + See http://www.zeroconf.org for further details, and "zcip.script" + in the busybox examples. + +endmenu diff --git a/networking/Kbuild b/networking/Kbuild new file mode 100644 index 0000000..a9a51fc --- /dev/null +++ b/networking/Kbuild @@ -0,0 +1,39 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ARPING) += arping.o +lib-$(CONFIG_DNSD) += dnsd.o +lib-$(CONFIG_ETHER_WAKE) += ether-wake.o +lib-$(CONFIG_FAKEIDENTD) += fakeidentd.o +lib-$(CONFIG_FTPGET) += ftpgetput.o +lib-$(CONFIG_FTPPUT) += ftpgetput.o +lib-$(CONFIG_HOSTNAME) += hostname.o +lib-$(CONFIG_HTTPD) += httpd.o +lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o +lib-$(CONFIG_IFUPDOWN) += ifupdown.o +lib-$(CONFIG_INETD) += inetd.o +lib-$(CONFIG_IP) += ip.o +lib-$(CONFIG_IPCALC) += ipcalc.o +lib-$(CONFIG_IPADDR) += ipaddr.o +lib-$(CONFIG_IPLINK) += iplink.o +lib-$(CONFIG_IPROUTE) += iproute.o +lib-$(CONFIG_IPRULE) += iprule.o +lib-$(CONFIG_IPTUNNEL) += iptunnel.o +lib-$(CONFIG_NAMEIF) += nameif.o +lib-$(CONFIG_NC) += nc.o +lib-$(CONFIG_NETSTAT) += netstat.o +lib-$(CONFIG_NSLOOKUP) += nslookup.o +lib-$(CONFIG_PING) += ping.o +lib-$(CONFIG_PING6) += ping6.o +lib-$(CONFIG_ROUTE) += route.o +lib-$(CONFIG_TELNET) += telnet.o +lib-$(CONFIG_TELNETD) += telnetd.o +lib-$(CONFIG_TFTP) += tftp.o +lib-$(CONFIG_TRACEROUTE) += traceroute.o +lib-$(CONFIG_VCONFIG) += vconfig.o +lib-$(CONFIG_WGET) += wget.o +lib-$(CONFIG_ZCIP) += zcip.o diff --git a/networking/arping.c b/networking/arping.c new file mode 100644 index 0000000..2d92bf4 --- /dev/null +++ b/networking/arping.c @@ -0,0 +1,446 @@ +/* vi: set sw=4 ts=4: */ +/* + * arping.c - Ping hosts by ARP requests/replies + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> + * Busybox port: Nick Fedchik <nick@fedchik.org.ua> + */ + +#include <sys/ioctl.h> +#include <signal.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <netpacket/packet.h> + +#include "busybox.h" + +static struct in_addr src; +static struct in_addr dst; +static struct sockaddr_ll me; +static struct sockaddr_ll he; +static struct timeval last; + +enum cfg_e { + dad = 1, + unsolicited = 2, + advert = 4, + quiet = 8, + quit_on_reply = 16, + broadcast_only = 32, + unicasting = 64 +}; +static int cfg; + +static int s; +static unsigned count = UINT_MAX; +static unsigned timeout; +static int sent; +static int brd_sent; +static int received; +static int brd_recv; +static int req_recv; + + +#define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \ + ((tv1).tv_usec-(tv2).tv_usec)/1000 ) +static int send_pack(int sock, struct in_addr *src_addr, + struct in_addr *dst_addr, struct sockaddr_ll *ME, + struct sockaddr_ll *HE) +{ + int err; + struct timeval now; + RESERVE_CONFIG_UBUFFER(buf, 256); + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + + ah->ar_hrd = htons(ME->sll_hatype); + ah->ar_hrd = htons(ARPHRD_ETHER); + ah->ar_pro = htons(ETH_P_IP); + ah->ar_hln = ME->sll_halen; + ah->ar_pln = 4; + ah->ar_op = cfg&advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST); + + memcpy(p, &ME->sll_addr, ah->ar_hln); + p += ME->sll_halen; + + memcpy(p, src_addr, 4); + p += 4; + + if (cfg&advert) + memcpy(p, &ME->sll_addr, ah->ar_hln); + else + memcpy(p, &HE->sll_addr, ah->ar_hln); + p += ah->ar_hln; + + memcpy(p, dst_addr, 4); + p += 4; + + gettimeofday(&now, NULL); + err = sendto(sock, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE)); + if (err == p - buf) { + last = now; + sent++; + if (!(cfg&unicasting)) + brd_sent++; + } + RELEASE_CONFIG_BUFFER(buf); + return err; +} + +static void finish(void) +{ + if (!(cfg&quiet)) { + printf("Sent %d probes (%d broadcast(s))\n" + "Received %d repl%s", + sent, brd_sent, + received, (received > 1) ? "ies" : "y"); + if (brd_recv || req_recv) { + printf(" ("); + if (req_recv) + printf("%d request(s)", req_recv); + if (brd_recv) + printf("%s%d broadcast(s)", req_recv ? ", " : "", brd_recv); + putchar(')'); + } + putchar('\n'); + fflush(stdout); + } + if (cfg&dad) + exit(!!received); + if (cfg&unsolicited) + exit(0); + exit(!received); +} + +static void catcher(void) +{ + struct timeval tv; + static struct timeval start; + + gettimeofday(&tv, NULL); + + if (start.tv_sec == 0) + start = tv; + + if (count-- == 0 + || (timeout && MS_TDIFF(tv, start) > timeout * 1000 + 500)) + finish(); + + if (last.tv_sec == 0 || MS_TDIFF(tv, last) > 500) { + send_pack(s, &src, &dst, &me, &he); + if (count == 0 && cfg&unsolicited) + finish(); + } + alarm(1); +} + +static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) +{ + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + struct in_addr src_ip, dst_ip; + + /* Filter out wild packets */ + if (FROM->sll_pkttype != PACKET_HOST && + FROM->sll_pkttype != PACKET_BROADCAST && + FROM->sll_pkttype != PACKET_MULTICAST) + return 0; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY)) + return 0; + + /* ARPHRD check and this darned FDDI hack here :-( */ + if (ah->ar_hrd != htons(FROM->sll_hatype) && + (FROM->sll_hatype != ARPHRD_FDDI + || ah->ar_hrd != htons(ARPHRD_ETHER))) + return 0; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons(ETH_P_IP)) + return 0; + if (ah->ar_pln != 4) + return 0; + if (ah->ar_hln != me.sll_halen) + return 0; + if (len < sizeof(*ah) + 2 * (4 + ah->ar_hln)) + return 0; + memcpy(&src_ip, p + ah->ar_hln, 4); + memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4); + if (!(cfg&dad)) { + if (src_ip.s_addr != dst.s_addr) + return 0; + if (src.s_addr != dst_ip.s_addr) + return 0; + if (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln)) + return 0; + } else { + /* DAD packet was: + src_ip = 0 (or some src) + src_hw = ME + dst_ip = tested address + dst_hw = <unspec> + + We fail, if receive request/reply with: + src_ip = tested_address + src_hw != ME + if src_ip in request was not zero, check + also that it matches to dst_ip, otherwise + dst_ip/dst_hw do not matter. + */ + if (src_ip.s_addr != dst.s_addr) + return 0; + if (memcmp(p, &me.sll_addr, me.sll_halen) == 0) + return 0; + if (src.s_addr && src.s_addr != dst_ip.s_addr) + return 0; + } + if (!(cfg&quiet)) { + int s_printed = 0; + struct timeval tv; + + gettimeofday(&tv, NULL); + + printf("%s %s from %s [%s]", + FROM->sll_pkttype == PACKET_HOST ? "Unicast" : "Broadcast", + ah->ar_op == htons(ARPOP_REPLY) ? "reply" : "request", + inet_ntoa(src_ip), + ether_ntoa((struct ether_addr *) p)); + if (dst_ip.s_addr != src.s_addr) { + printf("for %s ", inet_ntoa(dst_ip)); + s_printed = 1; + } + if (memcmp(p + ah->ar_hln + 4, me.sll_addr, ah->ar_hln)) { + if (!s_printed) + printf("for "); + printf("[%s]", + ether_ntoa((struct ether_addr *) p + ah->ar_hln + 4)); + } + + if (last.tv_sec) { + long usecs = (tv.tv_sec - last.tv_sec) * 1000000 + + tv.tv_usec - last.tv_usec; + long msecs = (usecs + 500) / 1000; + + usecs -= msecs * 1000 - 500; + printf(" %ld.%03ldms\n", msecs, usecs); + } else { + printf(" UNSOLICITED?\n"); + } + fflush(stdout); + } + received++; + if (FROM->sll_pkttype != PACKET_HOST) + brd_recv++; + if (ah->ar_op == htons(ARPOP_REQUEST)) + req_recv++; + if (cfg&quit_on_reply) + finish(); + if (!(cfg&broadcast_only)) { + memcpy(he.sll_addr, p, me.sll_halen); + cfg |= unicasting; + } + return 1; +} + +int arping_main(int argc, char **argv) +{ + char *device = "eth0"; + int ifindex; + char *source = NULL; + char *target; + + s = xsocket(PF_PACKET, SOCK_DGRAM, 0); + + // Drop suid root privileges + xsetuid(getuid()); + + { + unsigned opt; + char *_count, *_timeout; + + /* Dad also sets quit_on_reply. + * Advert also sets unsolicited. + */ + opt_complementary = "Df:AU"; + opt = getopt32(argc, argv, "DUAqfbc:w:i:s:", + &_count, &_timeout, &device, &source); + cfg |= opt & 0x3f; /* set respective flags */ + if (opt & 0x40) /* -c: count */ + count = xatou(_count); + if (opt & 0x80) /* -w: timeout */ + timeout = xatoul_range(_timeout, 0, INT_MAX/2000); + if (opt & 0x100) { /* -i: interface */ + if (strlen(device) > IF_NAMESIZE) { + bb_error_msg_and_die("interface name '%s' is too long", + device); + } + } + //if (opt & 0x200) /* -s: source */ + } + argc -= optind; + argv += optind; + + if (argc != 1) + bb_show_usage(); + + target = *argv; + + xfunc_error_retval = 2; + + { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, IFNAMSIZ - 1); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + bb_error_msg_and_die("interface %s not found", device); + } + ifindex = ifr.ifr_ifindex; + + if (ioctl(s, SIOCGIFFLAGS, (char *) &ifr)) { + bb_error_msg_and_die("SIOCGIFFLAGS"); + } + if (!(ifr.ifr_flags & IFF_UP)) { + bb_error_msg_and_die("interface %s is down", device); + } + if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) { + bb_error_msg("interface %s is not ARPable", device); + exit(cfg&dad ? 0 : 2); + } + } + + if (!inet_aton(target, &dst)) { + struct hostent *hp; + + hp = gethostbyname2(target, AF_INET); + if (!hp) { + bb_error_msg_and_die("invalid or unknown target %s", target); + } + memcpy(&dst, hp->h_addr, 4); + } + + if (source && !inet_aton(source, &src)) { + bb_error_msg_and_die("invalid source address %s", source); + } + + if (!(cfg&dad) && cfg&unsolicited && src.s_addr == 0) + src = dst; + + if (!(cfg&dad) || src.s_addr) { + struct sockaddr_in saddr; + int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (device) { + if (setsockopt + (probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, + strlen(device) + 1) == -1) + bb_error_msg("warning: interface %s is ignored", device); + } + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if (src.s_addr) { + saddr.sin_addr = src; + if (bind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) { + bb_error_msg_and_die("bind"); + } + } else if (!(cfg&dad)) { + static const int on = 1; + socklen_t alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = dst; + + if (setsockopt + (probe_fd, SOL_SOCKET, SO_DONTROUTE, (char *) &on, + sizeof(on)) == -1) + bb_perror_msg("warning: setsockopt(SO_DONTROUTE)"); + if (connect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) + == -1) { + bb_error_msg_and_die("connect"); + } + if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == + -1) { + bb_error_msg_and_die("getsockname"); + } + src = saddr.sin_addr; + } + close(probe_fd); + }; + + me.sll_family = AF_PACKET; + me.sll_ifindex = ifindex; + me.sll_protocol = htons(ETH_P_ARP); + if (bind(s, (struct sockaddr *) &me, sizeof(me)) == -1) { + bb_error_msg_and_die("bind"); + } + + { + socklen_t alen = sizeof(me); + + if (getsockname(s, (struct sockaddr *) &me, &alen) == -1) { + bb_error_msg_and_die("getsockname"); + } + } + if (me.sll_halen == 0) { + bb_error_msg("interface \"%s\" is not ARPable (no ll address)", device); + exit(cfg&dad ? 0 : 2); + } + he = me; + memset(he.sll_addr, -1, he.sll_halen); + + if (!(cfg&quiet)) { + printf("ARPING to %s from %s via %s\n", + inet_ntoa(dst), inet_ntoa(src), + device ? device : "unknown"); + } + + if (!src.s_addr && !(cfg&dad)) { + bb_error_msg_and_die("no src address in the non-DAD mode"); + } + + { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_RESTART; + + sa.sa_handler = (void (*)(int)) finish; + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = (void (*)(int)) catcher; + sigaction(SIGALRM, &sa, NULL); + } + + catcher(); + + while (1) { + sigset_t sset, osset; + RESERVE_CONFIG_UBUFFER(packet, 4096); + struct sockaddr_ll from; + socklen_t alen = sizeof(from); + int cc; + + if ((cc = recvfrom(s, packet, 4096, 0, + (struct sockaddr *) &from, &alen)) < 0) { + bb_perror_msg("recvfrom"); + continue; + } + sigemptyset(&sset); + sigaddset(&sset, SIGALRM); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + recv_pack(packet, cc, &from); + sigprocmask(SIG_SETMASK, &osset, NULL); + RELEASE_CONFIG_BUFFER(packet); + } +} diff --git a/networking/dnsd.c b/networking/dnsd.c new file mode 100644 index 0000000..5e9cf52 --- /dev/null +++ b/networking/dnsd.c @@ -0,0 +1,456 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini DNS server implementation for busybox + * + * Copyright (C) 2005 Roberto A. Foglietta (me@roberto.foglietta.name) + * Copyright (C) 2005 Odd Arild Olsen (oao at fibula dot no) + * Copyright (C) 2003 Paul Sheer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Odd Arild Olsen started out with the sheerdns [1] of Paul Sheer and rewrote + * it into a shape which I believe is both easier to understand and maintain. + * I also reused the input buffer for output and removed services he did not + * need. [1] http://threading.2038bug.com/sheerdns/ + * + * Some bugfix and minor changes was applied by Roberto A. Foglietta who made + * the first porting of oao' scdns to busybox also. + */ + +#include "busybox.h" + +static char *fileconf = "/etc/dnsd.conf"; +#define LOCK_FILE "/var/run/dnsd.lock" +#define LOG_FILE "/var/log/dnsd.log" + +// Must matct getopt32 call +#define OPT_daemon (option_mask32 & 0x10) +#define OPT_verbose (option_mask32 & 0x20) + +//#define DEBUG 1 + +enum { + MAX_HOST_LEN = 16, // longest host name allowed is 15 + IP_STRING_LEN = 18, // .xxx.xxx.xxx.xxx\0 + +//must be strlen('.in-addr.arpa') larger than IP_STRING_LEN + MAX_NAME_LEN = (IP_STRING_LEN + 13), + +/* Cannot get bigger packets than 512 per RFC1035 + In practice this can be set considerably smaller: + Length of response packet is header (12B) + 2*type(4B) + 2*class(4B) + + ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) + + 2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte +*/ + MAX_PACK_LEN = 512 + 1, + + DEFAULT_TTL = 30, // increase this when not testing? + + REQ_A = 1, + REQ_PTR = 12 +}; + +struct dns_repl { // resource record, add 0 or 1 to accepted dns_msg in resp + uint16_t rlen; + uint8_t *r; // resource + uint16_t flags; +}; + +struct dns_head { // the message from client and first part of response mag + uint16_t id; + uint16_t flags; + uint16_t nquer; // accepts 0 + uint16_t nansw; // 1 in response + uint16_t nauth; // 0 + uint16_t nadd; // 0 +}; +struct dns_prop { + uint16_t type; + uint16_t class; +}; +struct dns_entry { // element of known name, ip address and reversed ip address + struct dns_entry *next; + char ip[IP_STRING_LEN]; // dotted decimal IP + char rip[IP_STRING_LEN]; // length decimal reversed IP + char name[MAX_HOST_LEN]; +}; + +static struct dns_entry *dnsentry = NULL; +// FIXME! unused! :( +static int daemonmode; +static uint32_t ttl = DEFAULT_TTL; + +/* + * Convert host name from C-string to dns length/string. + */ +static void convname(char *a, uint8_t *q) +{ + int i = (q[0] == '.') ? 0 : 1; + for (; i < MAX_HOST_LEN-1 && *q; i++, q++) + a[i] = tolower(*q); + a[0] = i - 1; + a[i] = 0; +} + +/* + * Insert length of substrings instead of dots + */ +static void undot(uint8_t * rip) +{ + int i = 0, s = 0; + while (rip[i]) + i++; + for (--i; i >= 0; i--) { + if (rip[i] == '.') { + rip[i] = s; + s = 0; + } else s++; + } +} + +/* + * Append message to log file + */ +static void log_message(char *filename, char *message) +{ + FILE *logfile; + if (!daemonmode) + return; + logfile = fopen(filename, "a"); + if (!logfile) + return; + fprintf(logfile, "%s\n", message); + fclose(logfile); +} + +/* + * Read one line of hostname/IP from file + * Returns 0 for each valid entry read, -1 at EOF + * Assumes all host names are lower case only + * Hostnames with more than one label is not handled correctly. + * Presently the dot is copied into name without + * converting to a length/string substring for that label. + */ + +static int getfileentry(FILE * fp, struct dns_entry *s) +{ + unsigned int a,b,c,d; + char *r, *name; + + restart: + r = xmalloc_fgets(fp); + if (!r) + return -1; + while (*r == ' ' || *r == '\t') { + r++; + if (!*r || *r == '#' || *r == '\n') + goto restart; /* skipping empty/blank and commented lines */ + } + name = r; + while (*r != ' ' && *r != '\t') + r++; + *r++ = 0; + if (sscanf(r, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + goto restart; /* skipping wrong lines */ + + sprintf(s->ip, "%u.%u.%u.%u", a, b, c, d); + sprintf(s->rip, ".%u.%u.%u.%u", d, c, b, a); + undot((uint8_t*)s->rip); + convname(s->name,(uint8_t*)name); + + if (OPT_verbose) + fprintf(stderr, "\tname:%s, ip:%s\n", &(s->name[1]),s->ip); + + return 0; +} + +/* + * Read hostname/IP records from file + */ +static void dnsentryinit(void) +{ + FILE *fp; + struct dns_entry *m, *prev; + prev = dnsentry = NULL; + + fp = xfopen(fileconf, "r"); + + while (1) { + m = xmalloc(sizeof(struct dns_entry)); + + m->next = NULL; + if (getfileentry(fp, m)) + break; + + if (prev == NULL) + dnsentry = m; + else + prev->next = m; + prev = m; + } + fclose(fp); +} + + +/* + * Set up UDP socket + */ +static int listen_socket(char *iface_addr, int listen_port) +{ + struct sockaddr_in a; + char msg[100]; + int s; + s = xsocket(PF_INET, SOCK_DGRAM, 0); + if (setsockopt_reuseaddr(s) < 0) + bb_perror_msg_and_die("setsockopt() failed"); + memset(&a, 0, sizeof(a)); + a.sin_port = htons(listen_port); + a.sin_family = AF_INET; + if (!inet_aton(iface_addr, &a.sin_addr)) + bb_perror_msg_and_die("bad iface address"); + xbind(s, (struct sockaddr *)&a, sizeof(a)); + xlisten(s, 50); + sprintf(msg, "accepting UDP packets on addr:port %s:%d\n", + iface_addr, (int)listen_port); + log_message(LOG_FILE, msg); + return s; +} + +/* + * Look query up in dns records and return answer if found + * qs is the query string, first byte the string length + */ +static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs) +{ + int i; + struct dns_entry *d=dnsentry; + + do { +#ifdef DEBUG + char *p,*q; + q = (char *)&(qs[1]); + p = &(d->name[1]); + fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d", + __FUNCTION__, strlen(p), (int)(d->name[0]), p, q, strlen(q)); +#endif + if (type == REQ_A) { /* search by host name */ + for (i = 1; i <= (int)(d->name[0]); i++) + if (tolower(qs[i]) != d->name[i]) + break; + if (i > (int)(d->name[0])) { +#ifdef DEBUG + fprintf(stderr, " OK"); +#endif + strcpy((char *)as, d->ip); +#ifdef DEBUG + fprintf(stderr, " as:%s\n", as); +#endif + return 0; + } + } else + if (type == REQ_PTR) { /* search by IP-address */ + if (!strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) { + strcpy((char *)as, d->name); + return 0; + } + } + d = d->next; + } while (d); + return -1; +} + + +/* + * Decode message and generate answer + */ +#define eret(s) do { fputs(s, stderr); return -1; } while (0) +static int process_packet(uint8_t * buf) +{ + struct dns_head *head; + struct dns_prop *qprop; + struct dns_repl outr; + void *next, *from, *answb; + + uint8_t answstr[MAX_NAME_LEN + 1]; + int lookup_result, type, len, packet_len; + uint16_t flags; + + answstr[0] = '\0'; + + head = (struct dns_head *)buf; + if (head->nquer == 0) + eret("no queries\n"); + + if (head->flags & 0x8000) + eret("ignoring response packet\n"); + + from = (void *)&head[1]; // start of query string + next = answb = from + strlen((char *)from) + 1 + sizeof(struct dns_prop); // where to append answer block + + outr.rlen = 0; // may change later + outr.r = NULL; + outr.flags = 0; + + qprop = (struct dns_prop *)(answb - 4); + type = ntohs(qprop->type); + + // only let REQ_A and REQ_PTR pass + if (!(type == REQ_A || type == REQ_PTR)) { + goto empty_packet; /* we can't handle the query type */ + } + + if (ntohs(qprop->class) != 1 /* class INET */ ) { + outr.flags = 4; /* not supported */ + goto empty_packet; + } + /* we only support standard queries */ + + if ((ntohs(head->flags) & 0x7800) != 0) + goto empty_packet; + + // We have a standard query + log_message(LOG_FILE, (char *)from); + lookup_result = table_lookup(type, answstr, (uint8_t*)from); + if (lookup_result != 0) { + outr.flags = 3 | 0x0400; //name do not exist and auth + goto empty_packet; + } + if (type == REQ_A) { // return an address + struct in_addr a; + if (!inet_aton((char*)answstr, &a)) {//dotted dec to long conv + outr.flags = 1; /* Frmt err */ + goto empty_packet; + } + memcpy(answstr, &a.s_addr, 4); // save before a disappears + outr.rlen = 4; // uint32_t IP + } + else + outr.rlen = strlen((char *)answstr) + 1; // a host name + outr.r = answstr; // 32 bit ip or a host name + outr.flags |= 0x0400; /* authority-bit */ + // we have an answer + head->nansw = htons(1); + + // copy query block to answer block + len = answb - from; + memcpy(answb, from, len); + next += len; + + // and append answer rr + *(uint32_t *) next = htonl(ttl); + next += 4; + *(uint16_t *) next = htons(outr.rlen); + next += 2; + memcpy(next, (void *)answstr, outr.rlen); + next += outr.rlen; + + empty_packet: + + flags = ntohs(head->flags); + // clear rcode and RA, set responsebit and our new flags + flags |= (outr.flags & 0xff80) | 0x8000; + head->flags = htons(flags); + head->nauth = head->nadd = htons(0); + head->nquer = htons(1); + + packet_len = next - (void *)buf; + return packet_len; +} + +/* + * Exit on signal + */ +static void interrupt(int x) +{ + unlink(LOCK_FILE); + write(2, "interrupt exiting\n", 18); + exit(2); +} + +int dnsd_main(int argc, char **argv) +{ + int udps; + uint16_t port = 53; + uint8_t buf[MAX_PACK_LEN]; + char *listen_interface = "0.0.0.0"; + char *sttl, *sport; + + getopt32(argc, argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport); + //if (option_mask32 & 0x1) // -i + //if (option_mask32 & 0x2) // -c + if (option_mask32 & 0x4) // -t + if (!(ttl = atol(sttl))) + bb_show_usage(); + if (option_mask32 & 0x8) // -p + if (!(port = atol(sport))) + bb_show_usage(); + + if (OPT_verbose) { + bb_info_msg("listen_interface: %s", listen_interface); + bb_info_msg("ttl: %d, port: %d", ttl, port); + bb_info_msg("fileconf: %s", fileconf); + } + + if (OPT_daemon) +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(1, 0, argc, argv, "-d"); +#else + xdaemon(1, 0); +#endif + + dnsentryinit(); + + signal(SIGINT, interrupt); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif +#ifdef SIGURG + signal(SIGURG, SIG_IGN); +#endif + + udps = listen_socket(listen_interface, port); + if (udps < 0) + exit(1); + + while (1) { + fd_set fdset; + int r; + + FD_ZERO(&fdset); + FD_SET(udps, &fdset); + // Block until a message arrives + r = select(udps + 1, &fdset, NULL, NULL, NULL); + if (r < 0) + bb_perror_msg_and_die("select error"); + if (r == 0) + bb_perror_msg_and_die("select spurious return"); + + /* Can this test ever be false? - yes */ + if (FD_ISSET(udps, &fdset)) { + struct sockaddr_in from; + int fromlen = sizeof(from); + r = recvfrom(udps, buf, sizeof(buf), 0, + (struct sockaddr *)&from, + (void *)&fromlen); + if (OPT_verbose) + fprintf(stderr, "\n--- Got UDP "); + log_message(LOG_FILE, "\n--- Got UDP "); + + if (r < 12 || r > 512) { + bb_error_msg("invalid packet size"); + continue; + } + if (r > 0) { + r = process_packet(buf); + if (r > 0) + sendto(udps, buf, + r, 0, (struct sockaddr *)&from, + fromlen); + } + } // end if + } // end while + return 0; +} diff --git a/networking/ether-wake.c b/networking/ether-wake.c new file mode 100644 index 0000000..e205ffc --- /dev/null +++ b/networking/ether-wake.c @@ -0,0 +1,284 @@ +/* vi: set sw=4 ts=4: */ +/* + * ether-wake.c - Send a magic packet to wake up sleeping machines. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Donald Becker, http://www.scyld.com/"; http://www.scyld.com/wakeonlan.html + * Busybox port: Christian Volkmann <haveaniceday@online.de> + * Used version of ether-wake.c: v1.09 11/12/2003 Donald Becker, http://www.scyld.com/"; + */ + +/* full usage according Donald Becker + * usage: ether-wake [-i <ifname>] [-p aa:bb:cc:dd[:ee:ff]] 00:11:22:33:44:55\n" + * + * This program generates and transmits a Wake-On-LAN (WOL)\n" + * \"Magic Packet\", used for restarting machines that have been\n" + * soft-powered-down (ACPI D3-warm state).\n" + * It currently generates the standard AMD Magic Packet format, with\n" + * an optional password appended.\n" + * + * The single required parameter is the Ethernet MAC (station) address\n" + * of the machine to wake or a host ID with known NSS 'ethers' entry.\n" + * The MAC address may be found with the 'arp' program while the target\n" + * machine is awake.\n" + * + * Options:\n" + * -b Send wake-up packet to the broadcast address.\n" + * -D Increase the debug level.\n" + * -i ifname Use interface IFNAME instead of the default 'eth0'.\n" + * -p <pw> Append the four or six byte password PW to the packet.\n" + * A password is only required for a few adapter types.\n" + * The password may be specified in ethernet hex format\n" + * or dotted decimal (Internet address)\n" + * -p 00:22:44:66:88:aa\n" + * -p 192.168.1.1\n"; + * + * + * This program generates and transmits a Wake-On-LAN (WOL) "Magic Packet", + * used for restarting machines that have been soft-powered-down + * (ACPI D3-warm state). It currently generates the standard AMD Magic Packet + * format, with an optional password appended. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * Contact the author for use under other terms. + * + * This source file was originally part of the network tricks package, and + * is now distributed to support the Scyld Beowulf system. + * Copyright 1999-2003 Donald Becker and Scyld Computing Corporation. + * + * The author may be reached as becker@scyld, or C/O + * Scyld Computing Corporation + * 914 Bay Ridge Road, Suite 220 + * Annapolis MD 21403 + * + * Notes: + * On some systems dropping root capability allows the process to be + * dumped, traced or debugged. + * If someone traces this program, they get control of a raw socket. + * Linux handles this safely, but beware when porting this program. + * + * An alternative to needing 'root' is using a UDP broadcast socket, however + * doing so only works with adapters configured for unicast+broadcast Rx + * filter. That configuration consumes more power. +*/ + + +#include <netpacket/packet.h> +#include <net/ethernet.h> +#include <netinet/ether.h> +#include <linux/if.h> + +#include "busybox.h" + +/* Note: PF_INET, SOCK_DGRAM, IPPROTO_UDP would allow SIOCGIFHWADDR to + * work as non-root, but we need SOCK_PACKET to specify the Ethernet + * destination address. + */ +#ifdef PF_PACKET +# define whereto_t sockaddr_ll +# define make_socket() xsocket(PF_PACKET, SOCK_RAW, 0) +#else +# define whereto_t sockaddr +# define make_socket() xsocket(AF_INET, SOCK_PACKET, SOCK_PACKET) +#endif + +#ifdef DEBUG +# define bb_debug_msg(fmt, args...) fprintf(stderr, fmt, ## args) +void bb_debug_dump_packet(unsigned char *outpack, int pktsize) +{ + int i; + printf("packet dump:\n"); + for (i = 0; i < pktsize; ++i) { + printf("%2.2x ", outpack[i]); + if (i % 20 == 19) puts(""); + } + printf("\n\n"); +} +#else +# define bb_debug_msg(fmt, args...) +# define bb_debug_dump_packet(outpack, pktsize) +#endif + +static inline void get_dest_addr(const char *arg, struct ether_addr *eaddr); +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast); +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd); + +int ether_wake_main(int argc, char *argv[]) +{ + char *ifname = "eth0", *pass = NULL; + unsigned long flags; + unsigned char wol_passwd[6]; + int wol_passwd_sz = 0; + + int s; /* Raw socket */ + int pktsize; + unsigned char outpack[1000]; + + struct ether_addr eaddr; + struct whereto_t whereto; /* who to wake up */ + + /* handle misc user options */ + flags = getopt32(argc, argv, "bi:p:", &ifname, &pass); + if (optind == argc) + bb_show_usage(); + if (pass) + wol_passwd_sz = get_wol_pw(pass, wol_passwd); + + /* create the raw socket */ + s = make_socket(); + + /* now that we have a raw socket we can drop root */ + xsetuid(getuid()); + + /* look up the dest mac address */ + get_dest_addr(argv[optind], &eaddr); + + /* fill out the header of the packet */ + pktsize = get_fill(outpack, &eaddr, flags /*& 1 [OPT_BROADCAST]*/); + + bb_debug_dump_packet(outpack, pktsize); + + /* Fill in the source address, if possible. */ +#ifdef __linux__ + { + struct ifreq if_hwaddr; + + strncpy(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &if_hwaddr) < 0) + bb_perror_msg_and_die("SIOCGIFHWADDR on %s failed", ifname); + + memcpy(outpack+6, if_hwaddr.ifr_hwaddr.sa_data, 6); + +# ifdef DEBUG + { + unsigned char *hwaddr = if_hwaddr.ifr_hwaddr.sa_data; + printf("The hardware address (SIOCGIFHWADDR) of %s is type %d " + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n\n", ifname, + if_hwaddr.ifr_hwaddr.sa_family, hwaddr[0], hwaddr[1], + hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); + } +# endif + } +#endif /* __linux__ */ + + bb_debug_dump_packet(outpack, pktsize); + + /* append the password if specified */ + if (wol_passwd_sz > 0) { + memcpy(outpack+pktsize, wol_passwd, wol_passwd_sz); + pktsize += wol_passwd_sz; + } + + bb_debug_dump_packet(outpack, pktsize); + + /* This is necessary for broadcasts to work */ + if (flags /*& 1 [OPT_BROADCAST]*/) { + if (setsockopt_broadcast(s) < 0) + bb_perror_msg("SO_BROADCAST"); + } + +#if defined(PF_PACKET) + { + struct ifreq ifr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) == -1) + bb_perror_msg_and_die("SIOCGIFINDEX"); + memset(&whereto, 0, sizeof(whereto)); + whereto.sll_family = AF_PACKET; + whereto.sll_ifindex = ifr.ifr_ifindex; + /* The manual page incorrectly claims the address must be filled. + We do so because the code may change to match the docs. */ + whereto.sll_halen = ETH_ALEN; + memcpy(whereto.sll_addr, outpack, ETH_ALEN); + } +#else + whereto.sa_family = 0; + strcpy(whereto.sa_data, ifname); +#endif + + if (sendto(s, outpack, pktsize, 0, (struct sockaddr *)&whereto, sizeof(whereto)) < 0) + bb_perror_msg(bb_msg_write_error); + + close(s); + + return EXIT_SUCCESS; +} + +/* Convert the host ID string to a MAC address. + * The string may be a: + * Host name + * IP address string + * MAC address string +*/ +static inline void get_dest_addr(const char *hostid, struct ether_addr *eaddr) +{ + struct ether_addr *eap; + + eap = ether_aton(hostid); + if (eap) { + *eaddr = *eap; + bb_debug_msg("The target station address is %s\n\n", ether_ntoa(eaddr)); +#if !defined(__UCLIBC__) + } else if (ether_hostton(hostid, eaddr) == 0) { + bb_debug_msg("Station address for hostname %s is %s\n\n", hostid, ether_ntoa(eaddr)); +#else +# warning Need to implement ether_hostton() for uClibc +#endif + } else + bb_show_usage(); +} + +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast) +{ + int offset, i; + unsigned char *station_addr = eaddr->ether_addr_octet; + + if (broadcast) + memset(pkt+0, 0xff, 6); + else + memcpy(pkt, station_addr, 6); + memcpy(pkt+6, station_addr, 6); + pkt[12] = 0x08; /* Or 0x0806 for ARP, 0x8035 for RARP */ + pkt[13] = 0x42; + offset = 14; + + memset(pkt+offset, 0xff, 6); + offset += 6; + + for (i = 0; i < 16; ++i) { + memcpy(pkt+offset, station_addr, 6); + offset += 6; + } + + return offset; +} + +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd) +{ + int passwd[6]; + int byte_cnt, i; + + /* handle MAC format */ + byte_cnt = sscanf(ethoptarg, "%2x:%2x:%2x:%2x:%2x:%2x", + &passwd[0], &passwd[1], &passwd[2], + &passwd[3], &passwd[4], &passwd[5]); + /* handle IP format */ + if (byte_cnt < 4) + byte_cnt = sscanf(ethoptarg, "%d.%d.%d.%d", + &passwd[0], &passwd[1], &passwd[2], &passwd[3]); + if (byte_cnt < 4) { + bb_error_msg("cannot read Wake-On-LAN pass"); + return 0; + } + + for (i = 0; i < byte_cnt; ++i) + wol_passwd[i] = passwd[i]; + + bb_debug_msg("password: %2.2x %2.2x %2.2x %2.2x (%d)\n\n", + wol_passwd[0], wol_passwd[1], wol_passwd[2], wol_passwd[3], + byte_cnt); + + return byte_cnt; +} diff --git a/networking/fakeidentd.c b/networking/fakeidentd.c new file mode 100644 index 0000000..04138cc --- /dev/null +++ b/networking/fakeidentd.c @@ -0,0 +1,387 @@ +/* vi: set sw=4 ts=4: */ +/* + * A fake identd server + * + * Adapted to busybox by Thomas Lundquist <thomasez@zelow.no> + * Original Author: Tomi Ollila <too@iki.fi> + * http://www.guru-group.fi/~too/sw/ + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <sys/syslog.h> +#include <sys/uio.h> + + +#define IDENT_PORT 113 +#define MAXCONNS 20 +#define MAXIDLETIME 45 + +static const char ident_substr[] = " : USERID : UNIX : "; +enum { ident_substr_len = sizeof(ident_substr) - 1 }; +#define PIDFILE "/var/run/identd.pid" + +/* + * We have to track the 'first connection socket' so that we + * don't go around closing file descriptors for non-clients. + * + * descriptor setup normally + * 0 = server socket + * 1 = syslog fd (hopefully -- otherwise this won't work) + * 2 = connection socket after detached from tty. standard error before that + * 3 - 2 + MAXCONNS = rest connection sockets + * + * To try to make sure that syslog fd is what is "requested", the that fd + * is closed before openlog() call. It can only severely fail if fd 0 + * is initially closed. + */ +#define FCS 2 + +/* + * FD of the connection is always the index of the connection structure + * in `conns' array + FCS + */ +static struct { + char buf[20]; + int len; + time_t lasttime; +} conns[MAXCONNS]; + +/* When using global variables, bind those at least to a structure. */ +static struct { + const char *identuser; + fd_set readfds; + int conncnt; +} G; + +/* + * Prototypes + */ +static void reply(int s, char *buf); +static void replyError(int s, char *buf); + +static const char *nobodystr = "nobody"; /* this needs to be declared like this */ +static char *bind_ip_address = "0.0.0.0"; + +static void movefd(int from, int to) +{ + if (from != to) { + dup2(from, to); + close(from); + } +} + +static void inetbind(void) +{ + int s, port; + struct sockaddr_in addr; + int len = sizeof(addr); + struct servent *se; + + se = getservbyname("identd", "tcp"); + port = IDENT_PORT; + if (se) + port = se->s_port; + + s = xsocket(AF_INET, SOCK_STREAM, 0); + + setsockopt_reuseaddr(s); + + memset(&addr, 0, sizeof(addr)); + addr.sin_addr.s_addr = inet_addr(bind_ip_address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + xbind(s, (struct sockaddr *)&addr, len); + xlisten(s, 5); + + movefd(s, 0); +} + +static void handlexitsigs(int signum) +{ + if (unlink(PIDFILE) < 0) + close(open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)); + exit(0); +} + +/* May succeed. If not, won't care. */ +static void writepid(uid_t nobody, uid_t nogrp) +{ + char buf[sizeof(int)*3 + 2]; + int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0664); + + if (fd < 0) + return; + + sprintf(buf, "%d\n", getpid()); + write(fd, buf, strlen(buf)); + fchown(fd, nobody, nogrp); + close(fd); + + /* should this handle ILL, ... (see signal(7)) */ + signal(SIGTERM, handlexitsigs); + signal(SIGINT, handlexitsigs); + signal(SIGQUIT, handlexitsigs); +} + +/* return 0 as parent, 1 as child */ +static int godaemon(void) +{ + uid_t nobody, nogrp; + struct passwd *pw; + + switch (fork()) { + case -1: + bb_perror_msg_and_die("fork"); + + case 0: + pw = getpwnam(nobodystr); + if (pw == NULL) + bb_error_msg_and_die("cannot find uid/gid of user '%s'", nobodystr); + nobody = pw->pw_uid; + nogrp = pw->pw_gid; + writepid(nobody, nogrp); + + close(0); + inetbind(); + xsetgid(nogrp); + xsetuid(nobody); + close(1); + close(2); + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); /* connection closed when writing (raises ???) */ + + setsid(); + + return 1; + } + + return 0; +} + +static void deleteConn(int s) +{ + int i = s - FCS; + + close(s); + + G.conncnt--; + + /* + * Most of the time there is 0 connections. Most often that there + * is connections, there is just one connection. When this one connection + * closes, i == G.conncnt = 0 -> no copying. + * When there is more than one connection, the oldest connections closes + * earlier on average. When this happens, the code below starts copying + * the connection structure w/ highest index to the place which which is + * just deleted. This means that the connection structures are no longer + * in chronological order. I'd quess this means that when there is more + * than 1 connection, on average every other connection structure needs + * to be copied over the time all these connections are deleted. + */ + if (i != G.conncnt) { + memcpy(&conns[i], &conns[G.conncnt], sizeof(conns[0])); + movefd(G.conncnt + FCS, s); + } + + FD_CLR(G.conncnt + FCS, &G.readfds); +} + +static int closeOldest(void) +{ + time_t min = conns[0].lasttime; + int idx = 0; + int i; + + for (i = 1; i < MAXCONNS; i++) + if (conns[i].lasttime < min) + idx = i; + + replyError(idx + FCS, "X-SERVER-TOO-BUSY"); + close(idx + FCS); + + return idx; +} + +static int checkInput(char *buf, int len, int l) +{ + int i; + for (i = len; i < len + l; ++i) + if (buf[i] == '\n') + return 1; + return 0; +} + +int fakeidentd_main(int argc, char **argv) +{ + /* This applet is an inetd-style daemon */ + openlog(applet_name, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + + memset(conns, 0, sizeof(conns)); + memset(&G, 0, sizeof(G)); + FD_ZERO(&G.readfds); + FD_SET(0, &G.readfds); + + /* handle -b <ip> parameter */ + getopt32(argc, argv, "b:", &bind_ip_address); + /* handle optional REPLY STRING */ + if (optind < argc) + G.identuser = argv[optind]; + else + G.identuser = nobodystr; + + /* daemonize and have the parent return */ + if (godaemon() == 0) + return 0; + + /* main loop where we process all events and never exit */ + while (1) { + fd_set rfds = G.readfds; + struct timeval tv = { 15, 0 }; + int i; + int tim = time(NULL); + + select(G.conncnt + FCS, &rfds, NULL, NULL, G.conncnt? &tv: NULL); + + for (i = G.conncnt - 1; i >= 0; i--) { + int s = i + FCS; + + if (FD_ISSET(s, &rfds)) { + char *buf = conns[i].buf; + unsigned int len = conns[i].len; + unsigned int l; + + if ((l = read(s, buf + len, sizeof(conns[0].buf) - len)) > 0) { + if (checkInput(buf, len, l)) { + reply(s, buf); + goto deleteconn; + } else if (len + l >= sizeof(conns[0].buf)) { + replyError(s, "X-INVALID-REQUEST"); + goto deleteconn; + } else { + conns[i].len += l; + } + } else { + goto deleteconn; + } + + conns[i].lasttime = tim; + continue; + +deleteconn: + deleteConn(s); + } else { + /* implement as time_after() in linux kernel sources ... */ + if (conns[i].lasttime + MAXIDLETIME <= tim) { + replyError(s, "X-TIMEOUT"); + deleteConn(s); + } + } + } + + if (FD_ISSET(0, &rfds)) { + int s = accept(0, NULL, 0); + + if (s < 0) { + if (errno != EINTR) /* EINTR */ + bb_perror_msg("accept"); + } else { + if (G.conncnt == MAXCONNS) + i = closeOldest(); + else + i = G.conncnt++; + + movefd(s, i + FCS); /* move if not already there */ + FD_SET(i + FCS, &G.readfds); + + conns[i].len = 0; + conns[i].lasttime = time(NULL); + } + } + } /* end of while(1) */ + + return 0; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr); +static void reply(int s, char *buf) +{ + char *myaddr, *heraddr; + + myaddr = heraddr = NULL; + + if (parseAddrs(buf, &myaddr, &heraddr)) + replyError(s, "X-INVALID-REQUEST"); + else { + struct iovec iv[6]; + iv[0].iov_base = myaddr; iv[0].iov_len = strlen(myaddr); + iv[1].iov_base = ", "; iv[1].iov_len = 2; + iv[2].iov_base = heraddr; iv[2].iov_len = strlen(heraddr); + iv[3].iov_base = (void *)ident_substr; iv[3].iov_len = ident_substr_len; + iv[4].iov_base = (void *)G.identuser; iv[4].iov_len = strlen(G.identuser); + iv[5].iov_base = "\r\n"; iv[5].iov_len = 2; + writev(s, iv, 6); + } +} + +static void replyError(int s, char *buf) +{ + struct iovec iv[3]; + iv[0].iov_base = "0, 0 : ERROR : "; iv[0].iov_len = 15; + iv[1].iov_base = buf; iv[1].iov_len = strlen(buf); + iv[2].iov_base = "\r\n"; iv[2].iov_len = 2; + writev(s, iv, 3); +} + +static int chmatch(char c, char *chars) +{ + for (; *chars; chars++) + if (c == *chars) + return 1; + return 0; +} + +static int skipchars(char **p, char *chars) +{ + while (chmatch(**p, chars)) + (*p)++; + if (**p == '\r' || **p == '\n') + return 0; + return 1; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr) +{ + /* parse <port-on-server> , <port-on-client> */ + + if (!skipchars(&ptr, " \t")) + return -1; + + *myaddr = ptr; + + if (!skipchars(&ptr, "1234567890")) + return -1; + + if (!chmatch(*ptr, " \t,")) + return -1; + + *ptr++ = '\0'; + + if (!skipchars(&ptr, " \t,") ) + return -1; + + *heraddr = ptr; + + skipchars(&ptr, "1234567890"); + + if (!chmatch(*ptr, " \n\r")) + return -1; + + *ptr = '\0'; + + return 0; +} diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c new file mode 100644 index 0000000..223d243 --- /dev/null +++ b/networking/ftpgetput.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* + * ftpget + * + * Mini implementation of FTP to retrieve a remote file. + * + * Copyright (C) 2002 Jeff Angielski, The PTR Group <jeff@theptrgroup.com> + * Copyright (C) 2002 Glenn McGrath <bug1@iinet.net.au> + * + * Based on wget.c by Chip Rosenthal Covad Communications + * <chip@laserlink.net> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <getopt.h> + +typedef struct ftp_host_info_s { + char *user; + char *password; + struct sockaddr_in *s_in; +} ftp_host_info_t; + +static char verbose_flag = 0; +static char do_continue = 0; + +static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf) +{ + if (verbose_flag) { + bb_error_msg("cmd %s%s", s1, s2); + } + + if (s1) { + if (s2) { + fprintf(stream, "%s%s\r\n", s1, s2); + } else { + fprintf(stream, "%s\r\n", s1); + } + } + do { + char *buf_ptr; + + if (fgets(buf, 510, stream) == NULL) { + bb_perror_msg_and_die("fgets"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + return xatou(buf); +} + +static int xconnect_ftpdata(ftp_host_info_t *server, const char *buf) +{ + char *buf_ptr; + unsigned short port_num; + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num = xatoul_range(buf_ptr + 1, 0, 255); + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256; + + server->s_in->sin_port = htons(port_num); + return xconnect_tcp_v4(server->s_in); +} + +static FILE *ftp_login(ftp_host_info_t *server) +{ + FILE *control_stream; + char buf[512]; + + /* Connect to the command socket */ + control_stream = fdopen(xconnect_tcp_v4(server->s_in), "r+"); + if (control_stream == NULL) { + bb_perror_msg_and_die("cannot open control stream"); + } + + if (ftpcmd(NULL, NULL, control_stream, buf) != 220) { + bb_error_msg_and_die("%s", buf + 4); + } + + /* Login to the server */ + switch (ftpcmd("USER ", server->user, control_stream, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", server->password, control_stream, buf) != 230) { + bb_error_msg_and_die("PASS error: %s", buf + 4); + } + break; + default: + bb_error_msg_and_die("USER error: %s", buf + 4); + } + + ftpcmd("TYPE I", NULL, control_stream, buf); + + return control_stream; +} + +#if !ENABLE_FTPGET +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path); +#else +static +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path) +{ + char buf[512]; + off_t filesize = 0; + int fd_data; + int fd_local = -1; + off_t beg_range = 0; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + if (ftpcmd("SIZE ", server_path, control_stream, buf) == 213) { + filesize = BB_STRTOOFF(buf + 4, NULL, 10); + if (errno || filesize < 0) + bb_error_msg_and_die("SIZE error: %s", buf + 4); + } else { + filesize = -1; + do_continue = 0; + } + + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDOUT_FILENO; + do_continue = 0; + } + + if (do_continue) { + struct stat sbuf; + if (lstat(local_path, &sbuf) < 0) { + bb_perror_msg_and_die("lstat"); + } + if (sbuf.st_size > 0) { + beg_range = sbuf.st_size; + } else { + do_continue = 0; + } + } + + if (do_continue) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, control_stream, buf) != 350) { + do_continue = 0; + } else { + filesize -= beg_range; + } + } + + if (ftpcmd("RETR ", server_path, control_stream, buf) > 150) { + bb_error_msg_and_die("RETR error: %s", buf + 4); + } + + /* only make a local file if we know that one exists on the remote server */ + if (fd_local == -1) { + if (do_continue) { + fd_local = xopen(local_path, O_APPEND | O_WRONLY); + } else { + fd_local = xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY); + } + } + + /* Copy the file */ + if (filesize != -1) { + if (-1 == bb_copyfd_size(fd_data, fd_local, filesize)) + exit(EXIT_FAILURE); + } else { + if (-1 == bb_copyfd_eof(fd_data, fd_local)) + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("ftp error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#if !ENABLE_FTPPUT +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path); +#else +static +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path) +{ + struct stat sbuf; + char buf[512]; + int fd_data; + int fd_local; + int response; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + /* get the local file */ + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDIN_FILENO; + } else { + fd_local = xopen(local_path, O_RDONLY); + fstat(fd_local, &sbuf); + + sprintf(buf, "ALLO %lu", (unsigned long)sbuf.st_size); + response = ftpcmd(buf, NULL, control_stream, buf); + switch (response) { + case 200: + case 202: + break; + default: + close(fd_local); + bb_error_msg_and_die("ALLO error: %s", buf + 4); + break; + } + } + response = ftpcmd("STOR ", server_path, control_stream, buf); + switch (response) { + case 125: + case 150: + break; + default: + close(fd_local); + bb_error_msg_and_die("STOR error: %s", buf + 4); + } + + /* transfer the file */ + if (bb_copyfd_eof(fd_local, fd_data) == -1) { + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#define FTPGETPUT_OPT_CONTINUE 1 +#define FTPGETPUT_OPT_VERBOSE 2 +#define FTPGETPUT_OPT_USER 4 +#define FTPGETPUT_OPT_PASSWORD 8 +#define FTPGETPUT_OPT_PORT 16 + +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS +static const struct option ftpgetput_long_options[] = { + { "continue", 1, NULL, 'c' }, + { "verbose", 0, NULL, 'v' }, + { "username", 1, NULL, 'u' }, + { "password", 1, NULL, 'p' }, + { "port", 1, NULL, 'P' }, + { 0, 0, 0, 0 } +}; +#endif + +int ftpgetput_main(int argc, char **argv) +{ + /* content-length of the file */ + unsigned opt; + char *port = "ftp"; + + /* socket to ftp server */ + FILE *control_stream; + struct sockaddr_in s_in; + + /* continue a prev transfer (-c) */ + ftp_host_info_t *server; + + int (*ftp_action)(ftp_host_info_t *, FILE *, const char *, char *) = NULL; + + /* Check to see if the command is ftpget or ftput */ + if (ENABLE_FTPPUT && (!ENABLE_FTPGET || applet_name[3] == 'p')) { + ftp_action = ftp_send; + } + if (ENABLE_FTPGET && (!ENABLE_FTPPUT || applet_name[3] == 'g')) { + ftp_action = ftp_receive; + } + + /* Set default values */ + server = xmalloc(sizeof(ftp_host_info_t)); + server->user = "anonymous"; + server->password = "busybox@"; + verbose_flag = 0; + + /* + * Decipher the command line + */ +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS + applet_long_options = ftpgetput_long_options; +#endif + opt = getopt32(argc, argv, "cvu:p:P:", &server->user, &server->password, &port); + + /* Process the non-option command line arguments */ + if (argc - optind != 3) { + bb_show_usage(); + } + + if (opt & FTPGETPUT_OPT_CONTINUE) { + do_continue = 1; + } + if (opt & FTPGETPUT_OPT_VERBOSE) { + verbose_flag = 1; + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + server->s_in = &s_in; + bb_lookup_host(&s_in, argv[optind]); + s_in.sin_port = bb_lookup_port(port, "tcp", 21); + if (verbose_flag) { + printf("Connecting to %s[%s]:%d\n", + argv[optind], inet_ntoa(s_in.sin_addr), ntohs(s_in.sin_port)); + } + + /* Connect/Setup/Configure the FTP session */ + control_stream = ftp_login(server); + + return ftp_action(server, control_stream, argv[optind + 1], argv[optind + 2]); +} diff --git a/networking/hostname.c b/networking/hostname.c new file mode 100644 index 0000000..4fe28d7 --- /dev/null +++ b/networking/hostname.c @@ -0,0 +1,102 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: hostname.c,v 1.36 2003/07/14 21:21:01 andersen Exp $ + * Mini hostname implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * adjusted by Erik Andersen <andersen@codepoet.org> to remove + * use of long options and GNU getopt. Improved the usage info. + * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void do_sethostname(char *s, int isfile) +{ + FILE *f; + char buf[256]; + + if (!s) + return; + if (!isfile) { + if (sethostname(s, strlen(s)) < 0) { + if (errno == EPERM) + bb_error_msg_and_die("you must be root to change the hostname"); + else + bb_perror_msg_and_die("sethostname"); + } + } else { + f = xfopen(s, "r"); + while (fgets(buf, sizeof(buf), f) != NULL) { + if (buf[0] =='#') { + continue; + } + chomp(buf); + do_sethostname(buf, 0); + } +#ifdef CONFIG_FEATURE_CLEAN_UP + fclose(f); +#endif + } +} + +int hostname_main(int argc, char **argv) +{ + enum { + OPT_d = 0x1, + OPT_f = 0x2, + OPT_i = 0x4, + OPT_s = 0x8, + OPT_dfis = 0xf, + }; + + char buf[256]; + unsigned opt; + char *hostname_str = NULL; + + if (argc < 1) + bb_show_usage(); + + opt = getopt32(argc, argv, "dfisF:", &hostname_str); + + /* Output in desired format */ + if (opt & OPT_dfis) { + struct hostent *hp; + char *p; + gethostname(buf, sizeof(buf)); + hp = xgethostbyname(buf); + p = strchr(hp->h_name, '.'); + if (opt & OPT_f) { + puts(hp->h_name); + } else if (opt & OPT_s) { + if (p != NULL) { + *p = 0; + } + puts(hp->h_name); + } else if (opt & OPT_d) { + if (p) puts(p + 1); + } else if (opt & OPT_i) { + while (hp->h_addr_list[0]) { + printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++))); + } + puts(""); + } + } + /* Set the hostname */ + else if (hostname_str != NULL) { + do_sethostname(hostname_str, 1); + } else if (optind < argc) { + do_sethostname(argv[optind], 0); + } + /* Or if all else fails, + * just print the current hostname */ + else { + gethostname(buf, sizeof(buf)); + puts(buf); + } + return 0; +} diff --git a/networking/httpd.c b/networking/httpd.c new file mode 100644 index 0000000..49c2c76 --- /dev/null +++ b/networking/httpd.c @@ -0,0 +1,1970 @@ +/* vi: set sw=4 ts=4: */ +/* + * httpd implementation for busybox + * + * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org> + * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru> + * + * simplify patch stolen from libbb without using strdup + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + ***************************************************************************** + * + * Typical usage: + * for non root user + * httpd -p 8080 -h $HOME/public_html + * or for daemon start from rc script with uid=0: + * httpd -u www + * This is equivalent if www user have uid=80 to + * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication" + * + * + * When a url contains "cgi-bin" it is assumed to be a cgi script. The + * server changes directory to the location of the script and executes it + * after setting QUERY_STRING and other environment variables. + * + * Doc: + * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html + * + * The server can also be invoked as a url arg decoder and html text encoder + * as follows: + * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" + * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>" + * Note that url encoding for arguments is not the same as html encoding for + * presentation. -d decodes a url-encoded argument while -e encodes in html + * for page display. + * + * httpd.conf has the following format: + * + * A:172.20. # Allow address from 172.20.0.0/16 + * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 + * A:10.0.0.0/255.255.255.128 # Allow any address that previous set + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ + * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ + * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ + * .au:audio/basic # additional mime type for audio.au files + * *.php:/path/php # running cgi.php scripts through an interpreter + * + * A/D may be as a/d or allow/deny - first char case insensitive + * Deny IP rules take precedence over allow rules. + * + * + * The Deny/Allow IP logic: + * + * - Default is to allow all. No addresses are denied unless + * denied with a D: rule. + * - Order of Deny/Allow rules is significant + * - Deny rules take precedence over allow rules. + * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched + * addresses. + * - Specification of Allow all (A:*) is a no-op + * + * Example: + * 1. Allow only specified addresses + * A:172.20 # Allow any address that begins with 172.20. + * A:10.10. # Allow any address that begins with 10.10. + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * + * 2. Only deny specified addresses + * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255 + * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255 + * A:* # (optional line added for clarity) + * + * If a sub directory contains a config file it is parsed and merged with + * any existing settings as if it was appended to the original configuration. + * + * subdir paths are relative to the containing subdir and thus cannot + * affect the parent rules. + * + * Note that since the sub dir is parsed in the forked thread servicing the + * subdir http request, any merge is discarded when the process exits. As a + * result, the subdir settings only have a lifetime of a single request. + * + * + * If -c is not set, an attempt will be made to open the default + * root configuration file. If -c is set and the file is not found, the + * server exits with an error. + * +*/ + + +#include "busybox.h" + + +static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004"; +static const char default_path_httpd_conf[] = "/etc"; +static const char httpd_conf[] = "httpd.conf"; +static const char home[] = "./"; + +#define TIMEOUT 60 + +// Note: busybox xfuncs are not used because we want the server to keep running +// if something bad happens due to a malformed user request. +// As a result, all memory allocation after daemonize +// is checked rigorously + +//#define DEBUG 1 + +#ifndef DEBUG +# define DEBUG 0 +#endif + +#define MAX_MEMORY_BUFF 8192 /* IO buffer */ + +typedef struct HT_ACCESS { + char *after_colon; + struct HT_ACCESS *next; + char before_colon[1]; /* really bigger, must last */ +} Htaccess; + +typedef struct HT_ACCESS_IP { + unsigned int ip; + unsigned int mask; + int allow_deny; + struct HT_ACCESS_IP *next; +} Htaccess_IP; + +typedef struct { + char buf[MAX_MEMORY_BUFF]; + + USE_FEATURE_HTTPD_BASIC_AUTH(const char *realm;) + USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) + + const char *query; + + USE_FEATURE_HTTPD_CGI(char *referer;) + + const char *configFile; + + unsigned int rmt_ip; +#if ENABLE_FEATURE_HTTPD_CGI || DEBUG + char rmt_ip_str[16]; /* for set env REMOTE_ADDR */ +#endif + unsigned port; /* server initial port and for + set env REMOTE_PORT */ + const char *found_mime_type; + const char *found_moved_temporarily; + + off_t ContentLength; /* -1 - unknown */ + time_t last_mod; + + Htaccess_IP *ip_a_d; /* config allow/deny lines */ + int flg_deny_all; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *auth; /* config user:password lines */ +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *mime_a; /* config mime types */ +#endif + + int server_socket; + int accepted_socket; + volatile int alarm_signaled; + +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + Htaccess *script_i; /* config script interpreters */ +#endif +} HttpdConfig; + +static HttpdConfig *config; + +static const char request_GET[] = "GET"; /* size algorithmic optimize */ + +static const char* const suffixTable [] = { +/* Warning: shorted equivalent suffix in one line must be first */ + ".htm.html", "text/html", + ".jpg.jpeg", "image/jpeg", + ".gif", "image/gif", + ".png", "image/png", + ".txt.h.c.cc.cpp", "text/plain", + ".css", "text/css", + ".wav", "audio/wav", + ".avi", "video/x-msvideo", + ".qt.mov", "video/quicktime", + ".mpe.mpeg", "video/mpeg", + ".mid.midi", "audio/midi", + ".mp3", "audio/mpeg", +#if 0 /* unpopular */ + ".au", "audio/basic", + ".pac", "application/x-ns-proxy-autoconfig", + ".vrml.wrl", "model/vrml", +#endif + 0, "application/octet-stream" /* default */ +}; + +typedef enum { + HTTP_OK = 200, + HTTP_MOVED_TEMPORARILY = 302, + HTTP_BAD_REQUEST = 400, /* malformed syntax */ + HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ + HTTP_NOT_FOUND = 404, + HTTP_FORBIDDEN = 403, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ + HTTP_INTERNAL_SERVER_ERROR = 500, +#if 0 /* future use */ + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFO = 203, + HTTP_NO_CONTENT = 204, + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_NOT_MODIFIED = 304, + HTTP_PAYMENT_REQUIRED = 402, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ + HTTP_RESPONSE_SETSIZE = 0xffffffff +#endif +} HttpResponseNum; + +typedef struct { + HttpResponseNum type; + const char *name; + const char *info; +} HttpEnumString; + +static const HttpEnumString httpResponseNames[] = { + { HTTP_OK, "OK", NULL }, + { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, + { HTTP_REQUEST_TIMEOUT, "Request Timeout", + "No request appeared within a reasonable time period." }, + { HTTP_NOT_IMPLEMENTED, "Not Implemented", + "The requested method is not recognized by this server." }, +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + { HTTP_UNAUTHORIZED, "Unauthorized", "" }, +#endif + { HTTP_NOT_FOUND, "Not Found", + "The requested URL was not found on this server." }, + { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, + { HTTP_FORBIDDEN, "Forbidden", "" }, + { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", + "Internal Server Error" }, +#if 0 /* not implemented */ + { HTTP_CREATED, "Created" }, + { HTTP_ACCEPTED, "Accepted" }, + { HTTP_NO_CONTENT, "No Content" }, + { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, + { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, + { HTTP_NOT_MODIFIED, "Not Modified" }, + { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, + { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, +#endif +}; + + +static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; + + +#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) + + +static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc) +{ + const char *p = *ep; + int auto_mask = 8; + int j; + + *ip = 0; + for (j = 0; j < 4; j++) { + unsigned int octet; + + if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0) + return -auto_mask; + octet = 0; + while (*p >= '0' && *p <= '9') { + octet *= 10; + octet += *p - '0'; + if (octet > 255) + return -auto_mask; + p++; + } + if (*p == '.') + p++; + if (*p != '/' && *p != 0) + auto_mask += 8; + *ip = ((*ip) << 8) | octet; + } + if (*p != 0) { + if (*p != endc) + return -auto_mask; + p++; + if (*p == 0) + return -auto_mask; + } + *ep = p; + return auto_mask; +} + +static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask) +{ + int i; + unsigned int msk; + + i = scan_ip(&ipm, ip, '/'); + if (i < 0) + return i; + if (*ipm) { + const char *p = ipm; + + i = 0; + while (*p) { + if (*p < '0' || *p > '9') { + if (*p == '.') { + i = scan_ip(&ipm, mask, 0); + return i != 32; + } + return -1; + } + i *= 10; + i += *p - '0'; + p++; + } + } + if (i > 32 || i < 0) + return -1; + msk = 0x80000000; + *mask = 0; + while (i > 0) { + *mask |= msk; + msk >>= 1; + i--; + } + return 0; +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +static void free_config_lines(Htaccess **pprev) +{ + Htaccess *prev = *pprev; + + while (prev) { + Htaccess *cur = prev; + + prev = cur->next; + free(cur); + } + *pprev = NULL; +} +#endif + +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 +#define FIND_FROM_HTTPD_ROOT 3 +/**************************************************************************** + * + > $Function: parse_conf() + * + * $Description: parse configuration file into in-memory linked list. + * + * The first non-white character is examined to determine if the config line + * is one of the following: + * .ext:mime/type # new mime type not compiled into httpd + * [adAD]:from # ip address allow/deny, * for wildcard + * /path:user:pass # username/password + * + * Any previous IP rules are discarded. + * If the flag argument is not SUBDIR_PARSE then all /path and mime rules + * are also discarded. That is, previous settings are retained if flag is + * SUBDIR_PARSE. + * + * $Parameters: + * (const char *) path . . null for ip address checks, path for password + * checks. + * (int) flag . . . . . . the source of the parse request. + * + * $Return: (None) + * + ****************************************************************************/ +static void parse_conf(const char *path, int flag) +{ + FILE *f; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *prev, *cur; +#elif ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; +#endif + + const char *cf = config->configFile; + char buf[160]; + char *p0 = NULL; + char *c, *p; + + /* free previous ip setup if present */ + Htaccess_IP *pip = config->ip_a_d; + + while (pip) { + Htaccess_IP *cur_ipl = pip; + + pip = cur_ipl->next; + free(cur_ipl); + } + config->ip_a_d = NULL; + + config->flg_deny_all = 0; + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* retain previous auth and mime config only for subdir parse */ + if (flag != SUBDIR_PARSE) { +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free_config_lines(&config->auth); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + free_config_lines(&config->mime_a); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + free_config_lines(&config->script_i); +#endif + } +#endif + + if (flag == SUBDIR_PARSE || cf == NULL) { + cf = alloca(strlen(path) + sizeof(httpd_conf) + 2); + if (cf == NULL) { + if (flag == FIRST_PARSE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return; + } + sprintf((char *)cf, "%s/%s", path, httpd_conf); + } + + while ((f = fopen(cf, "r")) == NULL) { + if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ + return; + } + if (config->configFile && flag == FIRST_PARSE) /* if -c option given */ + bb_perror_msg_and_die("%s", cf); + flag = FIND_FROM_HTTPD_ROOT; + cf = httpd_conf; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + prev = config->auth; +#endif + /* This could stand some work */ + while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { + c = NULL; + for (p = p0; *p0 != 0 && *p0 != '#'; p0++) { + if (!isspace(*p0)) { + *p++ = *p0; + if (*p0 == ':' && c == NULL) + c = p; + } + } + *p = 0; + + /* test for empty or strange line */ + if (c == NULL || *c == 0) + continue; + p0 = buf; + if (*p0 == 'd') + *p0 = 'D'; + if (*c == '*') { + if (*p0 == 'D') { + /* memorize deny all */ + config->flg_deny_all++; + } + /* skip default other "word:*" config lines */ + continue; + } + + if (*p0 == 'a') + *p0 = 'A'; + else if (*p0 != 'D' && *p0 != 'A' +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + && *p0 != '/' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + && *p0 != '.' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + && *p0 != '*' +#endif + ) + continue; + if (*p0 == 'A' || *p0 == 'D') { + /* storing current config IP line */ + pip = calloc(1, sizeof(Htaccess_IP)); + if (pip) { + if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) { + /* syntax IP{/mask} error detected, protect all */ + *p0 = 'D'; + pip->mask = 0; + } + pip->allow_deny = *p0; + if (*p0 == 'D') { + /* Deny:form_IP move top */ + pip->next = config->ip_a_d; + config->ip_a_d = pip; + } else { + /* add to bottom A:form_IP config line */ + Htaccess_IP *prev_IP = config->ip_a_d; + + if (prev_IP == NULL) { + config->ip_a_d = pip; + } else { + while (prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + } + continue; + } +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (*p0 == '/') { + /* make full path from httpd root / curent_path / config_line_path */ + cf = flag == SUBDIR_PARSE ? path : ""; + p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c)); + if (p0 == NULL) + continue; + c[-1] = 0; + sprintf(p0, "/%s%s", cf, buf); + + /* another call bb_simplify_path */ + cf = p = p0; + + do { + if (*p == '/') { + if (*cf == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*cf == '.') { + if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */ + continue; + } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) { + ++cf; + if (p > p0) { + while (*--p != '/') /* omit previous dir */; + } + continue; + } + } + } + *++p = *cf; + } while (*++cf); + + if ((p == p0) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + sprintf(p0, "%s:%s", p0, c); + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* storing current config line */ + cur = calloc(1, sizeof(Htaccess) + strlen(p0)); + if (cur) { + cf = strcpy(cur->before_colon, p0); + c = strchr(cf, ':'); + *c++ = 0; + cur->after_colon = c; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (*cf == '.') { + /* config .mime line move top for overwrite previous */ + cur->next = config->mime_a; + config->mime_a = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (*cf == '*' && cf[1] == '.') { + /* config script interpreter line move top for overwrite previous */ + cur->next = config->script_i; + config->script_i = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free(p0); + if (prev == NULL) { + /* first line */ + config->auth = prev = cur; + } else { + /* sort path, if current lenght eq or bigger then move up */ + Htaccess *prev_hti = config->auth; + size_t l = strlen(cf); + Htaccess *hti; + + for (hti = prev_hti; hti; hti = hti->next) { + if (l >= strlen(hti->before_colon)) { + /* insert before hti */ + cur->next = hti; + if (prev_hti != hti) { + prev_hti->next = cur; + } else { + /* insert as top */ + config->auth = cur; + } + break; + } + if (prev_hti != hti) + prev_hti = prev_hti->next; + } + if (!hti) { /* not inserted, add to bottom */ + prev->next = cur; + prev = cur; + } + } +#endif + } +#endif + } + fclose(f); +} + +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR +/**************************************************************************** + * + > $Function: encodeString() + * + * $Description: Given a string, html encode special characters. + * This is used for the -e command line option to provide an easy way + * for scripts to encode result data without confusing browsers. The + * returned string pointer is memory allocated by malloc(). + * + * $Parameters: + * (const char *) string . . The first string to encode. + * + * $Return: (char *) . . . .. . . A pointer to the encoded string. + * + * $Errors: Returns a null string ("") if memory is not available. + * + ****************************************************************************/ +static char *encodeString(const char *string) +{ + /* take the simple route and encode everything */ + /* could possibly scan once to get length. */ + int len = strlen(string); + char *out = malloc(len * 6 + 1); + char *p = out; + char ch; + + if (!out) return ""; + while ((ch = *string++)) { + // very simple check for what to encode + if (isalnum(ch)) *p++ = ch; + else p += sprintf(p, "&#%d;", (unsigned char) ch); + } + *p = 0; + return out; +} +#endif /* FEATURE_HTTPD_ENCODE_URL_STR */ + +/**************************************************************************** + * + > $Function: decodeString() + * + * $Description: Given a URL encoded string, convert it to plain ascii. + * Since decoding always makes strings smaller, the decode is done in-place. + * Thus, callers should strdup() the argument if they do not want the + * argument modified. The return is the original pointer, allowing this + * function to be easily used as arguments to other functions. + * + * $Parameters: + * (char *) string . . . The first string to decode. + * (int) option_d . . 1 if called for httpd -d + * + * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * + * $Errors: None + * + ****************************************************************************/ +static char *decodeString(char *orig, int option_d) +{ + /* note that decoded string is always shorter than original */ + char *string = orig; + char *ptr = string; + char c; + + while ((c = *ptr++) != '\0') { + unsigned value1, value2; + + if (option_d && c == '+') { + *string++ = ' '; + continue; + } + if (c != '%') { + *string++ = c; + continue; + } + if (sscanf(ptr, "%1X", &value1) != 1 + || sscanf(ptr+1, "%1X", &value2) != 1 + ) { + if (!option_d) + return NULL; + *string++ = '%'; + continue; + } + value1 = value1 * 16 + value2; + if (!option_d && (value1 == '/' || value1 == '\0')) { + /* caller takes it as indication of invalid + * (dangerous wrt exploits) chars */ + return orig + 1; + } + *string++ = value1; + ptr += 2; + } + *string = '\0'; + return orig; +} + + +#if ENABLE_FEATURE_HTTPD_CGI +/**************************************************************************** + * setenv helpers + ****************************************************************************/ +static void setenv1(const char *name, const char *value) +{ + if (!value) + value = ""; + setenv(name, value, 1); +} +static void setenv_long(const char *name, long value) +{ + char buf[sizeof(value)*3 + 1]; + sprintf(buf, "%ld", value); + setenv(name, buf, 1); +} +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +/**************************************************************************** + * + > $Function: decodeBase64() + * + > $Description: Decode a base 64 data stream as per rfc1521. + * Note that the rfc states that none base64 chars are to be ignored. + * Since the decode always results in a shorter size than the input, it is + * OK to pass the input arg as an output arg. + * + * $Parameter: + * (char *) Data . . . . A pointer to a base64 encoded string. + * Where to place the decoded data. + * + * $Return: void + * + * $Errors: None + * + ****************************************************************************/ +static void decodeBase64(char *Data) +{ + + const unsigned char *in = (const unsigned char *)Data; + // The decoded size will be at most 3/4 the size of the encoded + unsigned long ch = 0; + int i = 0; + + while (*in) { + int t = *in++; + + if (t >= '0' && t <= '9') + t = t - '0' + 52; + else if (t >= 'A' && t <= 'Z') + t = t - 'A'; + else if (t >= 'a' && t <= 'z') + t = t - 'a' + 26; + else if (t == '+') + t = 62; + else if (t == '/') + t = 63; + else if (t == '=') + t = 0; + else + continue; + + ch = (ch << 6) | t; + i++; + if (i == 4) { + *Data++ = (char) (ch >> 16); + *Data++ = (char) (ch >> 8); + *Data++ = (char) ch; + i = 0; + } + } + *Data = 0; +} +#endif + + +/**************************************************************************** + * + > $Function: openServer() + * + * $Description: create a listen server socket on the designated port. + * + * $Return: (int) . . . A connection socket. -1 for errors. + * + * $Errors: None + * + ****************************************************************************/ +static int openServer(void) +{ + struct sockaddr_in lsocket; + int fd; + + /* create the socket right now */ + /* inet_addr() returns a value that is already in network order */ + memset(&lsocket, 0, sizeof(lsocket)); + lsocket.sin_family = AF_INET; + lsocket.sin_addr.s_addr = INADDR_ANY; + lsocket.sin_port = htons(config->port); + fd = xsocket(AF_INET, SOCK_STREAM, 0); + /* tell the OS it's OK to reuse a previous address even though */ + /* it may still be in a close down state. Allows bind to succeed. */ +#ifdef SO_REUSEPORT + { + static const int on = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, + (void *)&on, sizeof(on)); + } +#else + setsockopt_reuseaddr(fd); +#endif + xbind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)); + xlisten(fd, 9); + signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */ + return fd; +} + +/**************************************************************************** + * + > $Function: sendHeaders() + * + * $Description: Create and send HTTP response headers. + * The arguments are combined and sent as one write operation. Note that + * IE will puke big-time if the headers are not sent in one packet and the + * second packet is delayed for any reason. + * + * $Parameter: + * (HttpResponseNum) responseNum . . . The result code to send. + * + * $Return: (int) . . . . writing errors + * + ****************************************************************************/ +static int sendHeaders(HttpResponseNum responseNum) +{ + char *buf = config->buf; + const char *responseString = ""; + const char *infoString = 0; + const char *mime_type; + unsigned int i; + time_t timer = time(0); + char timeStr[80]; + int len; + enum { + numNames = sizeof(httpResponseNames) / sizeof(httpResponseNames[0]) + }; + + for (i = 0; i < numNames; i++) { + if (httpResponseNames[i].type == responseNum) { + responseString = httpResponseNames[i].name; + infoString = httpResponseNames[i].info; + break; + } + } + /* error message is HTML */ + mime_type = responseNum == HTTP_OK ? + config->found_mime_type : "text/html"; + + /* emit the current date */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); + len = sprintf(buf, + "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, mime_type, timeStr); + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", + config->realm); + } +#endif + if (responseNum == HTTP_MOVED_TEMPORARILY) { + len += sprintf(buf+len, "Location: %s/%s%s\r\n", + config->found_moved_temporarily, + (config->query ? "?" : ""), + (config->query ? config->query : "")); + } + + if (config->ContentLength != -1) { /* file */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); + len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", + timeStr, "Content-length:", config->ContentLength); + } + strcat(buf, "\r\n"); + len += 2; + if (infoString) { + len += sprintf(buf+len, + "<HEAD><TITLE>%d %s</TITLE></HEAD>\n" + "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n", + responseNum, responseString, + responseNum, responseString, infoString); + } + if (DEBUG) + fprintf(stderr, "headers: '%s'\n", buf); + return full_write(config->accepted_socket, buf, len); +} + +/**************************************************************************** + * + > $Function: getLine() + * + * $Description: Read from the socket until an end of line char found. + * + * Characters are read one at a time until an eol sequence is found. + * + * $Return: (int) . . . . number of characters read. -1 if error. + * + ****************************************************************************/ +static int getLine(void) +{ + int count = 0; + char *buf = config->buf; + + while (read(config->accepted_socket, buf + count, 1) == 1) { + if (buf[count] == '\r') continue; + if (buf[count] == '\n') { + buf[count] = 0; + return count; + } + if (count < (MAX_MEMORY_BUFF-1)) /* check overflow */ + count++; + } + if (count) return count; + else return -1; +} + +#if ENABLE_FEATURE_HTTPD_CGI +/**************************************************************************** + * + > $Function: sendCgi() + * + * $Description: Execute a CGI script and send it's stdout back + * + * Environment variables are set up and the script is invoked with pipes + * for stdin/stdout. If a post is being done the script is fed the POST + * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). + * + * $Parameters: + * (const char *) url . . . . . . The requested URL (with leading /). + * (int bodyLen) . . . . . . . . Length of the post body. + * (const char *cookie) . . . . . For set HTTP_COOKIE. + * (const char *content_type) . . For set CONTENT_TYPE. + + * + * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * + * $Errors: None + * + ****************************************************************************/ +static int sendCgi(const char *url, + const char *request, int bodyLen, const char *cookie, + const char *content_type) +{ + int fromCgi[2]; /* pipe for reading data from CGI */ + int toCgi[2]; /* pipe for sending data to CGI */ + + static char * argp[] = { 0, 0 }; + int pid = 0; + int inFd; + int outFd; + int firstLine = 1; + int status; + size_t post_readed_size, post_readed_idx; + + if (pipe(fromCgi) != 0) + return 0; + if (pipe(toCgi) != 0) + return 0; + + pid = fork(); + if (pid < 0) + return 0; + + if (!pid) { + /* child process */ + char *script; + char *purl = strdup(url); + char realpath_buff[MAXPATHLEN]; + + if (purl == NULL) + _exit(242); + + inFd = toCgi[0]; + outFd = fromCgi[1]; + + dup2(inFd, 0); // replace stdin with the pipe + dup2(outFd, 1); // replace stdout with the pipe + if (!DEBUG) + dup2(outFd, 2); // replace stderr with the pipe + + close(toCgi[0]); + close(toCgi[1]); + close(fromCgi[0]); + close(fromCgi[1]); + + close(config->accepted_socket); + close(config->server_socket); + + /* + * Find PATH_INFO. + */ + script = purl; + while ((script = strchr(script + 1, '/')) != NULL) { + /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */ + struct stat sb; + + *script = '\0'; + if (is_directory(purl + 1, 1, &sb) == 0) { + /* not directory, found script.cgi/PATH_INFO */ + *script = '/'; + break; + } + *script = '/'; /* is directory, find next '/' */ + } + setenv1("PATH_INFO", script); /* set /PATH_INFO or "" */ + /* setenv1("PATH", getenv("PATH")); redundant */ + setenv1("REQUEST_METHOD", request); + if (config->query) { + char *uri = alloca(strlen(purl) + 2 + strlen(config->query)); + if (uri) + sprintf(uri, "%s?%s", purl, config->query); + setenv1("REQUEST_URI", uri); + } else { + setenv1("REQUEST_URI", purl); + } + if (script != NULL) + *script = '\0'; /* cut off /PATH_INFO */ + /* SCRIPT_FILENAME required by PHP in CGI mode */ + if (!realpath(purl + 1, realpath_buff)) + goto error_execing_cgi; + setenv1("SCRIPT_FILENAME", realpath_buff); + /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ + setenv1("SCRIPT_NAME", purl); + /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html: + * QUERY_STRING: The information which follows the ? in the URL + * which referenced this script. This is the query information. + * It should not be decoded in any fashion. This variable + * should always be set when there is query information, + * regardless of command line decoding. */ + /* (Older versions of bbox seemed to do some decoding) */ + setenv1("QUERY_STRING", config->query); + setenv1("SERVER_SOFTWARE", httpdVersion); + putenv("SERVER_PROTOCOL=HTTP/1.0"); + putenv("GATEWAY_INTERFACE=CGI/1.1"); + setenv1("REMOTE_ADDR", config->rmt_ip_str); +#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + setenv_long("REMOTE_PORT", config->port); +#endif + if (bodyLen) + setenv_long("CONTENT_LENGTH", bodyLen); + if (cookie) + setenv1("HTTP_COOKIE", cookie); + if (content_type) + setenv1("CONTENT_TYPE", content_type); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (config->remoteuser) { + setenv1("REMOTE_USER", config->remoteuser); + putenv("AUTH_TYPE=Basic"); + } +#endif + if (config->referer) + setenv1("HTTP_REFERER", config->referer); + + /* set execve argp[0] without path */ + argp[0] = strrchr(purl, '/') + 1; + /* but script argp[0] must have absolute path and chdiring to this */ + script = strrchr(realpath_buff, '/'); + if (!script) + goto error_execing_cgi; + *script = '\0'; + if (chdir(realpath_buff) == 0) { + // now run the program. If it fails, + // use _exit() so no destructors + // get called and make a mess. +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + char *interpr = NULL; + char *suffix = strrchr(purl, '.'); + + if (suffix) { + Htaccess *cur; + for (cur = config->script_i; cur; cur = cur->next) { + if (strcmp(cur->before_colon + 1, suffix) == 0) { + interpr = cur->after_colon; + break; + } + } + } +#endif + *script = '/'; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (interpr) + execv(interpr, argp); + else +#endif + execv(realpath_buff, argp); + } + error_execing_cgi: + /* send to stdout (even if we are not from inetd) */ + config->accepted_socket = 1; + sendHeaders(HTTP_NOT_FOUND); + _exit(242); + } /* end child */ + + /* parent process */ + + post_readed_size = 0; + post_readed_idx = 0; + inFd = fromCgi[0]; + outFd = toCgi[1]; + close(fromCgi[1]); + close(toCgi[0]); + signal(SIGPIPE, SIG_IGN); + + while (1) { + fd_set readSet; + fd_set writeSet; + char wbuf[128]; + int nfound; + int count; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(inFd, &readSet); + if (bodyLen > 0 || post_readed_size > 0) { + FD_SET(outFd, &writeSet); + nfound = outFd > inFd ? outFd : inFd; + if (post_readed_size == 0) { + FD_SET(config->accepted_socket, &readSet); + if (nfound < config->accepted_socket) + nfound = config->accepted_socket; + } + /* Now wait on the set of sockets! */ + nfound = select(nfound + 1, &readSet, &writeSet, 0, NULL); + } else { + if (!bodyLen) { + close(outFd); + bodyLen = -1; + } + nfound = select(inFd + 1, &readSet, 0, 0, NULL); + } + + if (nfound <= 0) { + if (waitpid(pid, &status, WNOHANG) > 0) { + close(inFd); + if (DEBUG && WIFEXITED(status)) + bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status)); + if (DEBUG && WIFSIGNALED(status)) + bb_error_msg("piped has exited with signal=%d", WTERMSIG(status)); + break; + } + } else if (post_readed_size > 0 && FD_ISSET(outFd, &writeSet)) { + count = full_write(outFd, wbuf + post_readed_idx, post_readed_size); + if (count > 0) { + post_readed_size -= count; + post_readed_idx += count; + if (post_readed_size == 0) + post_readed_idx = 0; + } else { + post_readed_size = post_readed_idx = bodyLen = 0; /* broken pipe to CGI */ + } + } else if (bodyLen > 0 && post_readed_size == 0 && FD_ISSET(config->accepted_socket, &readSet)) { + count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen; + count = safe_read(config->accepted_socket, wbuf, count); + if (count > 0) { + post_readed_size += count; + bodyLen -= count; + } else { + bodyLen = 0; /* closed */ + } + } + if (FD_ISSET(inFd, &readSet)) { + int s = config->accepted_socket; + char *rbuf = config->buf; + +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif +#if PIPESIZE >= MAX_MEMORY_BUFF +# error "PIPESIZE >= MAX_MEMORY_BUFF" +#endif + + /* There is something to read */ + count = safe_read(inFd, rbuf, PIPESIZE); + if (count == 0) + break; /* closed */ + if (count > 0) { + if (firstLine) { + rbuf[count] = 0; + /* check to see if the user script added headers */ + if (strncmp(rbuf, "HTTP/1.0 200 OK\r\n", 4) != 0) { + full_write(s, "HTTP/1.0 200 OK\r\n", 17); + } + /* Sometimes CGI is writing to pipe in small chunks + * and we don't see Content-type (because the read + * is too short) and we emit bogus "text/plain"! + * Is it a bug or CGI *has to* write it in one piece? */ + if (strstr(rbuf, "ontent-") == 0) { + full_write(s, "Content-type: text/plain\r\n\r\n", 28); + } + firstLine = 0; + } + if (full_write(s, rbuf, count) != count) + break; + + if (DEBUG) + fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); + } + } + } + return 0; +} +#endif /* FEATURE_HTTPD_CGI */ + +/**************************************************************************** + * + > $Function: sendFile() + * + * $Description: Send a file response to a HTTP request + * + * $Parameter: + * (const char *) url . . The URL requested. + * + * $Return: (int) . . . . . . Always 0. + * + ****************************************************************************/ +static int sendFile(const char *url) +{ + char * suffix; + int f; + const char * const * table; + const char * try_suffix; + + suffix = strrchr(url, '.'); + + for (table = suffixTable; *table; table += 2) + if (suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) { + try_suffix += strlen(suffix); + if (*try_suffix == 0 || *try_suffix == '.') + break; + } + /* also, if not found, set default as "application/octet-stream"; */ + config->found_mime_type = table[1]; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (suffix) { + Htaccess * cur; + + for (cur = config->mime_a; cur; cur = cur->next) { + if (strcmp(cur->before_colon, suffix) == 0) { + config->found_mime_type = cur->after_colon; + break; + } + } + } +#endif /* FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */ + + if (DEBUG) + fprintf(stderr, "sending file '%s' content-type: %s\n", + url, config->found_mime_type); + + f = open(url, O_RDONLY); + if (f >= 0) { + int count; + char *buf = config->buf; + + sendHeaders(HTTP_OK); + /* TODO: sendfile() */ + while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) { + if (full_write(config->accepted_socket, buf, count) != count) + break; + } + close(f); + } else { + if (DEBUG) + bb_perror_msg("cannot open '%s'", url); + sendHeaders(HTTP_NOT_FOUND); + } + + return 0; +} + +static int checkPermIP(void) +{ + Htaccess_IP * cur; + + /* This could stand some work */ + for (cur = config->ip_a_d; cur; cur = cur->next) { + if (DEBUG) + fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str); + if (DEBUG) + fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n", + (unsigned char)(cur->ip >> 24), + (unsigned char)(cur->ip >> 16), + (unsigned char)(cur->ip >> 8), + cur->ip & 0xff, + (unsigned char)(cur->mask >> 24), + (unsigned char)(cur->mask >> 16), + (unsigned char)(cur->mask >> 8), + cur->mask & 0xff); + if ((config->rmt_ip & cur->mask) == cur->ip) + return cur->allow_deny == 'A'; /* Allow/Deny */ + } + + /* if unconfigured, return 1 - access from all */ + return !config->flg_deny_all; +} + +/**************************************************************************** + * + > $Function: checkPerm() + * + * $Description: Check the permission file for access password protected. + * + * If config file isn't present, everything is allowed. + * Entries are of the form you can see example from header source + * + * $Parameters: + * (const char *) path . . . . The file path. + * (const char *) request . . . User information to validate. + * + * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. + * + ****************************************************************************/ + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +static int checkPerm(const char *path, const char *request) +{ + Htaccess * cur; + const char *p; + const char *p0; + + const char *prev = NULL; + + /* This could stand some work */ + for (cur = config->auth; cur; cur = cur->next) { + size_t l; + + p0 = cur->before_colon; + if (prev != NULL && strcmp(prev, p0) != 0) + continue; /* find next identical */ + p = cur->after_colon; + if (DEBUG) + fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request); + + l = strlen(p0); + if (strncmp(p0, path, l) == 0 + && (l == 1 || path[l] == '/' || path[l] == '\0') + ) { + char *u; + /* path match found. Check request */ + /* for check next /path:user:password */ + prev = p0; + u = strchr(request, ':'); + if (u == NULL) { + /* bad request, ':' required */ + break; + } + + if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { + char *cipher; + char *pp; + + if (strncmp(p, request, u-request) != 0) { + /* user uncompared */ + continue; + } + pp = strchr(p, ':'); + if (pp && pp[1] == '$' && pp[2] == '1' && + pp[3] == '$' && pp[4]) { + pp++; + cipher = pw_encrypt(u+1, pp); + if (strcmp(cipher, pp) == 0) + goto set_remoteuser_var; /* Ok */ + /* unauthorized */ + continue; + } + } + + if (strcmp(p, request) == 0) { +set_remoteuser_var: + config->remoteuser = strdup(request); + if (config->remoteuser) + config->remoteuser[(u - request)] = 0; + return 1; /* Ok */ + } + /* unauthorized */ + } + } /* for */ + + return prev == NULL; +} + +#endif /* FEATURE_HTTPD_BASIC_AUTH */ + +/**************************************************************************** + * + > $Function: handle_sigalrm() + * + * $Description: Handle timeouts + * + ****************************************************************************/ + +static void handle_sigalrm(int sig) +{ + sendHeaders(HTTP_REQUEST_TIMEOUT); + config->alarm_signaled = sig; +} + +/**************************************************************************** + * + > $Function: handleIncoming() + * + * $Description: Handle an incoming http request. + * + ****************************************************************************/ +static void handleIncoming(void) +{ + char *buf = config->buf; + char *url; + char *purl; + int blank = -1; + char *test; + struct stat sb; + int ip_allowed; +#if ENABLE_FEATURE_HTTPD_CGI + const char *prequest = request_GET; + unsigned long length = 0; + char *cookie = 0; + char *content_type = 0; +#endif + fd_set s_fd; + struct timeval tv; + int retval; + struct sigaction sa; + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + int credentials = -1; /* if not required this is Ok */ +#endif + + sa.sa_handler = handle_sigalrm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* no SA_RESTART */ + sigaction(SIGALRM, &sa, NULL); + + do { + int count; + + (void) alarm(TIMEOUT); + if (getLine() <= 0) + break; /* closed */ + + purl = strpbrk(buf, " \t"); + if (purl == NULL) { + BAD_REQUEST: + sendHeaders(HTTP_BAD_REQUEST); + break; + } + *purl = '\0'; +#if ENABLE_FEATURE_HTTPD_CGI + if (strcasecmp(buf, prequest) != 0) { + prequest = "POST"; + if (strcasecmp(buf, prequest) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } + } +#else + if (strcasecmp(buf, request_GET) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } +#endif + *purl = ' '; + count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank); + + if (count < 1 || buf[0] != '/') { + /* Garbled request/URL */ + goto BAD_REQUEST; + } + url = alloca(strlen(buf) + sizeof("/index.html")); + if (url == NULL) { + sendHeaders(HTTP_INTERNAL_SERVER_ERROR); + break; + } + strcpy(url, buf); + /* extract url args if present */ + test = strchr(url, '?'); + config->query = NULL; + if (test) { + *test++ = '\0'; + config->query = test; + } + + test = decodeString(url, 0); + if (test == NULL) + goto BAD_REQUEST; + if (test == url+1) { + /* '/' or NUL is encoded */ + sendHeaders(HTTP_NOT_FOUND); + break; + } + + /* algorithm stolen from libbb bb_simplify_path(), + but don't strdup and reducing trailing slash and protect out root */ + purl = test = url; + do { + if (*purl == '/') { + /* skip duplicate (or initial) slash */ + if (*test == '/') { + continue; + } + if (*test == '.') { + /* skip extra '.' */ + if (test[1] == '/' || test[1] == 0) { + continue; + } else + /* '..': be careful */ + if (test[1] == '.' && (test[2] == '/' || test[2] == 0)) { + ++test; + if (purl == url) { + /* protect out root */ + goto BAD_REQUEST; + } + while (*--purl != '/') /* omit previous dir */; + continue; + } + } + } + *++purl = *test; + } while (*++test); + *++purl = '\0'; /* so keep last character */ + test = purl; /* end ptr */ + + /* If URL is directory, adding '/' */ + if (test[-1] != '/') { + if (is_directory(url + 1, 1, &sb)) { + config->found_moved_temporarily = url; + } + } + if (DEBUG) + fprintf(stderr, "url='%s', args=%s\n", url, config->query); + + test = url; + ip_allowed = checkPermIP(); + while (ip_allowed && (test = strchr(test + 1, '/')) != NULL) { + /* have path1/path2 */ + *test = '\0'; + if (is_directory(url + 1, 1, &sb)) { + /* may be having subdir config */ + parse_conf(url + 1, SUBDIR_PARSE); + ip_allowed = checkPermIP(); + } + *test = '/'; + } + if (blank >= 0) { + /* read until blank line for HTTP version specified, else parse immediate */ + while (1) { + alarm(TIMEOUT); + count = getLine(); + if (count <= 0) + break; + + if (DEBUG) + fprintf(stderr, "header: '%s'\n", buf); + +#if ENABLE_FEATURE_HTTPD_CGI + /* try and do our best to parse more lines */ + if ((STRNCASECMP(buf, "Content-length:") == 0)) { + /* extra read only for POST */ + if (prequest != request_GET) { + test = buf + sizeof("Content-length:")-1; + if (!test[0]) goto bail_out; + errno = 0; + /* not using strtoul: it ignores leading munis! */ + length = strtol(test, &test, 10); + /* length is "ulong", but we need to pass it to int later */ + /* so we check for negative or too large values in one go: */ + /* (long -> ulong conv caused negatives to be seen as > INT_MAX) */ + if (test[0] || errno || length > INT_MAX) + goto bail_out; + } + } else if ((STRNCASECMP(buf, "Cookie:") == 0)) { + cookie = strdup(skip_whitespace(buf + sizeof("Cookie:")-1)); + } else if ((STRNCASECMP(buf, "Content-Type:") == 0)) { + content_type = strdup(skip_whitespace(buf + sizeof("Content-Type:")-1)); + } else if ((STRNCASECMP(buf, "Referer:") == 0)) { + config->referer = strdup(skip_whitespace(buf + sizeof("Referer:")-1)); + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (STRNCASECMP(buf, "Authorization:") == 0) { + /* We only allow Basic credentials. + * It shows up as "Authorization: Basic <userid:password>" where + * the userid:password is base64 encoded. + */ + test = skip_whitespace(buf + sizeof("Authorization:")-1); + if (STRNCASECMP(test, "Basic") != 0) + continue; + test += sizeof("Basic")-1; + /* decodeBase64() skips whitespace itself */ + decodeBase64(test); + credentials = checkPerm(url, test); + } +#endif /* FEATURE_HTTPD_BASIC_AUTH */ + + } /* while extra header reading */ + } + alarm(0); + if (config->alarm_signaled) + break; + + if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) { + /* protect listing [/path]/httpd_conf or IP deny */ +#if ENABLE_FEATURE_HTTPD_CGI + FORBIDDEN: /* protect listing /cgi-bin */ +#endif + sendHeaders(HTTP_FORBIDDEN); + break; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (credentials <= 0 && checkPerm(url, ":") == 0) { + sendHeaders(HTTP_UNAUTHORIZED); + break; + } +#endif + + if (config->found_moved_temporarily) { + sendHeaders(HTTP_MOVED_TEMPORARILY); + /* clear unforked memory flag */ + config->found_moved_temporarily = NULL; + break; + } + + test = url + 1; /* skip first '/' */ + +#if ENABLE_FEATURE_HTTPD_CGI + if (strncmp(test, "cgi-bin", 7) == 0) { + if (test[7] == '/' && test[8] == 0) + goto FORBIDDEN; /* protect listing cgi-bin/ */ + sendCgi(url, prequest, length, cookie, content_type); + break; + } + if (prequest != request_GET) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } +#endif /* FEATURE_HTTPD_CGI */ + if (purl[-1] == '/') + strcpy(purl, "index.html"); + if (stat(test, &sb) == 0) { + /* It's a dir URL and there is index.html */ + config->ContentLength = sb.st_size; + config->last_mod = sb.st_mtime; + } +#if ENABLE_FEATURE_HTTPD_CGI + else if (purl[-1] == '/') { + /* It's a dir URL and there is no index.html + * Try cgi-bin/index.cgi */ + if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { + purl[0] = '\0'; + config->query = url; + sendCgi("/cgi-bin/index.cgi", prequest, length, cookie, content_type); + break; + } + } +#endif /* FEATURE_HTTPD_CGI */ + sendFile(test); + config->ContentLength = -1; + } while (0); + + bail_out: + + if (DEBUG) + fprintf(stderr, "closing socket\n\n"); +#if ENABLE_FEATURE_HTTPD_CGI + free(cookie); + free(content_type); + free(config->referer); + config->referer = NULL; +# if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free(config->remoteuser); + config->remoteuser = NULL; +# endif +#endif + shutdown(config->accepted_socket, SHUT_WR); + + /* Properly wait for remote to closed */ + FD_ZERO(&s_fd); + FD_SET(config->accepted_socket, &s_fd); + + do { + tv.tv_sec = 2; + tv.tv_usec = 0; + retval = select(config->accepted_socket + 1, &s_fd, NULL, NULL, &tv); + } while (retval > 0 && read(config->accepted_socket, buf, sizeof(config->buf) > 0)); + + shutdown(config->accepted_socket, SHUT_RD); + /* In inetd case, we close fd 1 (stdout) here. We will exit soon anyway */ + close(config->accepted_socket); +} + +/**************************************************************************** + * + > $Function: miniHttpd() + * + * $Description: The main http server function. + * + * Given an open socket fildes, listen for new connections and farm out + * the processing as a forked process. + * + * $Parameters: + * (int) server. . . The server socket fildes. + * + * $Return: (int) . . . . Always 0. + * + ****************************************************************************/ +static int miniHttpd(int server) +{ + fd_set readfd, portfd; + + FD_ZERO(&portfd); + FD_SET(server, &portfd); + + /* copy the ports we are watching to the readfd set */ + while (1) { + int on, s; + socklen_t fromAddrLen; + struct sockaddr_in fromAddr; + + /* Now wait INDEFINITELY on the set of sockets! */ + readfd = portfd; + if (select(server + 1, &readfd, 0, 0, 0) <= 0) + continue; + if (!FD_ISSET(server, &readfd)) + continue; + fromAddrLen = sizeof(fromAddr); + s = accept(server, (struct sockaddr *)&fromAddr, &fromAddrLen); + if (s < 0) + continue; + config->accepted_socket = s; + config->rmt_ip = ntohl(fromAddr.sin_addr.s_addr); +#if ENABLE_FEATURE_HTTPD_CGI || DEBUG + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); + config->port = ntohs(fromAddr.sin_port); +#if DEBUG + bb_error_msg("connection from IP=%s, port %u", + config->rmt_ip_str, config->port); +#endif +#endif /* FEATURE_HTTPD_CGI */ + + /* set the KEEPALIVE option to cull dead connections */ + on = 1; + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + + if (DEBUG || fork() == 0) { + /* child */ +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + /* protect reload config, may be confuse checking */ + signal(SIGHUP, SIG_IGN); +#endif + handleIncoming(); + if (!DEBUG) + exit(0); + } + close(s); + } /* while (1) */ + return 0; +} + +/* from inetd */ +static int miniHttpd_inetd(void) +{ + struct sockaddr_in fromAddrLen; + socklen_t sinlen = sizeof(struct sockaddr_in); + + getpeername(0, (struct sockaddr *)&fromAddrLen, &sinlen); + config->rmt_ip = ntohl(fromAddrLen.sin_addr.s_addr); +#if ENABLE_FEATURE_HTTPD_CGI + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); +#endif + config->port = ntohs(fromAddrLen.sin_port); + handleIncoming(); + return 0; +} + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +static void sighup_handler(int sig) +{ + /* set and reset */ + struct sigaction sa; + + parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); + sa.sa_handler = sighup_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGHUP, &sa, NULL); +} +#endif + +enum { + c_opt_config_file = 0, + d_opt_decode_url, + h_opt_home_httpd, + USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,) + USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,) + USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) + USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,) + p_opt_port , + p_opt_inetd , + p_opt_foreground, + OPT_CONFIG_FILE = 1 << c_opt_config_file, + OPT_DECODE_URL = 1 << d_opt_decode_url, + OPT_HOME_HTTPD = 1 << h_opt_home_httpd, + OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0, + OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0, + OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, + OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, + OPT_PORT = 1 << p_opt_port, + OPT_INETD = 1 << p_opt_inetd, + OPT_FOREGROUND = 1 << p_opt_foreground, +}; + +static const char httpd_opts[] = "c:d:h:" + USE_FEATURE_HTTPD_ENCODE_URL_STR("e:") + USE_FEATURE_HTTPD_BASIC_AUTH("r:") + USE_FEATURE_HTTPD_AUTH_MD5("m:") + USE_FEATURE_HTTPD_SETUID("u:") + "p:if"; + + +int httpd_main(int argc, char *argv[]) +{ + unsigned opt; + const char *home_httpd = home; + char *url_for_decode; + USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;) + const char *s_port; + USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;) + USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) + USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;) + +#if ENABLE_LOCALE_SUPPORT + /* Undo busybox.c: we want to speak English in http (dates etc) */ + setlocale(LC_TIME, "C"); +#endif + + config = xzalloc(sizeof(*config)); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + config->realm = "Web Server Authentication"; +#endif + config->port = 80; + config->ContentLength = -1; + + opt = getopt32(argc, argv, httpd_opts, + &(config->configFile), &url_for_decode, &home_httpd + USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) + USE_FEATURE_HTTPD_BASIC_AUTH(, &(config->realm)) + USE_FEATURE_HTTPD_AUTH_MD5(, &pass) + USE_FEATURE_HTTPD_SETUID(, &s_ugid) + , &s_port + ); + if (opt & OPT_DECODE_URL) { + printf("%s", decodeString(url_for_decode, 1)); + return 0; + } +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR + if (opt & OPT_ENCODE_URL) { + printf("%s", encodeString(url_for_encode)); + return 0; + } +#endif +#if ENABLE_FEATURE_HTTPD_AUTH_MD5 + if (opt & OPT_MD5) { + puts(pw_encrypt(pass, "$1$")); + return 0; + } +#endif + if (opt & OPT_PORT) + config->port = xatou16(s_port); + +#if ENABLE_FEATURE_HTTPD_SETUID + if (opt & OPT_SETUID) { + char *e; + // FIXME: what the default group should be? + ugid.gid = -1; + ugid.uid = bb_strtoul(s_ugid, &e, 0); + if (*e == ':') { + e++; + ugid.gid = bb_strtoul(e, NULL, 0); + } + if (errno) { + /* not integer */ + if (!uidgid_get(&ugid, s_ugid)) + bb_error_msg_and_die("unrecognized user[:group] " + "name '%s'", s_ugid); + } + } +#endif + + xchdir(home_httpd); + if (!(opt & OPT_INETD)) { + config->server_socket = openServer(); +#if ENABLE_FEATURE_HTTPD_SETUID + /* drop privileges */ + if (opt & OPT_SETUID) { + if (ugid.gid != (gid_t)-1) { + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + } + xsetuid(ugid.uid); + } +#endif + } + +#if ENABLE_FEATURE_HTTPD_CGI + { + char *p = getenv("PATH"); + p = xstrdup(p); /* if gets NULL, returns NULL */ + clearenv(); + if (p) + setenv1("PATH", p); + if (!(opt & OPT_INETD)) + setenv_long("SERVER_PORT", config->port); + } +#endif + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + sighup_handler(0); +#else + parse_conf(default_path_httpd_conf, FIRST_PARSE); +#endif + + if (opt & OPT_INETD) + return miniHttpd_inetd(); + + if (!(opt & OPT_FOREGROUND)) + xdaemon(1, 0); /* don't change current directory */ + return miniHttpd(config->server_socket); +} diff --git a/networking/httpd_index_cgi_example b/networking/httpd_index_cgi_example new file mode 100644 index 0000000..31e768c --- /dev/null +++ b/networking/httpd_index_cgi_example @@ -0,0 +1,55 @@ +#!/bin/sh +# This CGI creates directory index. +# Put it into cgi-bin/index.cgi and chmod 0755. +# +# Problems: +# * Unsafe wrt weird filenames with <>"'& etc... +# * Not efficient: calls stat (program, not syscall) for each file +# * Probably requires bash +# +# If you want speed and safety, you need to code it in C + +# Must start with '/' +test "${QUERY_STRING:0:1}" = "/" || exit 1 +# /../ is not allowed +test "${QUERY_STRING%/../*}" = "$QUERY_STRING" || exit 1 +test "${QUERY_STRING%/..}" = "$QUERY_STRING" || exit 1 + +# Outta cgi-bin... +cd .. 2>/dev/null || exit 1 +# Strip leading '/', go to target dir +cd "${QUERY_STRING:1}" 2>/dev/null || exit 1 + +f=`dirname "$QUERY_STRING"` +test "$f" = "/" && f="" + +# Pipe thru dd (need to write header as single write(), +# or else httpd doesn't see "Content-type: text/html" +# in first read() and decides that it is not html) +{ +printf "%s" \ +$'HTTP/1.0 200 OK\r\n'\ +$'Content-type: text/html\r\n\r\n'\ +"<html><head><title>Index of $QUERY_STRING</title></head>"$'\r\n'\ +"<body><h1>Index of $QUERY_STRING</h1><pre>"$'\r\n'\ +$'<table width=100%>\r\n'\ +$'<col><col><col width=0*>\r\n'\ +$'<tr><th>Name<th align=right>Last modified<th align=right>Size\r\n'\ +\ +"<tr><td><a href='$f/'>..</a><td><td>"$'\r\n' + +IFS='#' +for f in *; do + # Guard against empty dirs... + test -e "$f" && \ + stat -c "%F#%s#%z" "$f" | { + read type size cdt junk + dir='' + test "$type" = "directory" && dir='/' + cdt="${cdt//.*}" # no fractional seconds + cdt="${cdt// / }" # prevent wrapping around space + printf "%s" "<tr><td><a href='$f$dir'>$f</a><td align=right>$cdt<td align=right>$size"$'\r\n' + } +done +printf "</table></pre><hr></body></html>"$'\r\n' +} | dd bs=4k diff --git a/networking/ifconfig.c b/networking/ifconfig.c new file mode 100644 index 0000000..ae5b468 --- /dev/null +++ b/networking/ifconfig.c @@ -0,0 +1,548 @@ +/* vi: set sw=4 ts=4: */ +/* ifconfig + * + * Similar to the standard Unix ifconfig, but with only the necessary + * parts for AF_INET, and without any printing of if info (for now). + * + * Bjorn Wesen, Axis Communications AB + * + * + * Authors of the original ifconfig was: + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * Heavily modified by Manuel Novoa III Mar 6, 2001 + * + * From initial port to busybox, removed most of the redundancy by + * converting to a table-driven approach. Added several (optional) + * args missing from initial port. + * + * Still missing: media, tunnel. + * + * 2002-04-20 + * IPV6 support added by Bart Visscher <magick@linux-fan.com> + */ + +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <sys/types.h> +#include <netinet/if_ether.h> +#endif +#include "inet_common.h" +#include "busybox.h" + +#if ENABLE_FEATURE_IFCONFIG_SLIP +# include <net/if_slip.h> +#endif + +/* I don't know if this is needed for busybox or not. Anyone? */ +#define QUESTIONABLE_ALIAS_CASE + + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +# define SIOCSIFTXQLEN 0x8943 +# define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +# define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef IFF_DYNAMIC +# define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +#if ENABLE_FEATURE_IPV6 +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; +}; +#endif + +/* + * Here are the bit masks for the "flags" member of struct options below. + * N_ signifies no arg prefix; M_ signifies arg prefixed by '-'. + * CLR clears the flag; SET sets the flag; ARG signifies (optional) arg. + */ +#define N_CLR 0x01 +#define M_CLR 0x02 +#define N_SET 0x04 +#define M_SET 0x08 +#define N_ARG 0x10 +#define M_ARG 0x20 + +#define M_MASK (M_CLR | M_SET | M_ARG) +#define N_MASK (N_CLR | N_SET | N_ARG) +#define SET_MASK (N_SET | M_SET) +#define CLR_MASK (N_CLR | M_CLR) +#define SET_CLR_MASK (SET_MASK | CLR_MASK) +#define ARG_MASK (M_ARG | N_ARG) + +/* + * Here are the bit masks for the "arg_flags" member of struct options below. + */ + +/* + * cast type: + * 00 int + * 01 char * + * 02 HOST_COPY in_ether + * 03 HOST_COPY INET_resolve + */ +#define A_CAST_TYPE 0x03 +/* + * map type: + * 00 not a map type (mem_start, io_addr, irq) + * 04 memstart (unsigned long) + * 08 io_addr (unsigned short) + * 0C irq (unsigned char) + */ +#define A_MAP_TYPE 0x0C +#define A_ARG_REQ 0x10 /* Set if an arg is required. */ +#define A_NETMASK 0x20 /* Set if netmask (check for multiple sets). */ +#define A_SET_AFTER 0x40 /* Set a flag at the end. */ +#define A_COLON_CHK 0x80 /* Is this needed? See below. */ +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS +#define A_HOSTNAME 0x100 /* Set if it is ip addr. */ +#define A_BROADCAST 0x200 /* Set if it is broadcast addr. */ +#else +#define A_HOSTNAME 0 +#define A_BROADCAST 0 +#endif + +/* + * These defines are for dealing with the A_CAST_TYPE field. + */ +#define A_CAST_CHAR_PTR 0x01 +#define A_CAST_RESOLVE 0x01 +#define A_CAST_HOST_COPY 0x02 +#define A_CAST_HOST_COPY_IN_ETHER A_CAST_HOST_COPY +#define A_CAST_HOST_COPY_RESOLVE (A_CAST_HOST_COPY | A_CAST_RESOLVE) + +/* + * These defines are for dealing with the A_MAP_TYPE field. + */ +#define A_MAP_ULONG 0x04 /* memstart */ +#define A_MAP_USHORT 0x08 /* io_addr */ +#define A_MAP_UCHAR 0x0C /* irq */ + +/* + * Define the bit masks signifying which operations to perform for each arg. + */ + +#define ARG_METRIC (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MTU (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_TXQUEUELEN (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MEM_START (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IO_ADDR (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IRQ (A_ARG_REQ | A_MAP_UCHAR) +#define ARG_DSTADDR (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE) +#define ARG_NETMASK (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_NETMASK) +#define ARG_BROADCAST (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_BROADCAST) +#define ARG_HW (A_ARG_REQ | A_CAST_HOST_COPY_IN_ETHER) +#define ARG_POINTOPOINT (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) +#define ARG_KEEPALIVE (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_OUTFILL (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_HOSTNAME (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_COLON_CHK | A_HOSTNAME) +#define ARG_ADD_DEL (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) + + +/* + * Set up the tables. Warning! They must have corresponding order! + */ + +struct arg1opt { + const char *name; + int selector; + unsigned short ifr_offset; +}; + +struct options { + const char *name; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + const unsigned int flags:6; + const unsigned int arg_flags:10; +#else + const unsigned char flags; + const unsigned char arg_flags; +#endif + const unsigned short selector; +}; + +#define ifreq_offsetof(x) offsetof(struct ifreq, x) + +static const struct arg1opt Arg1Opt[] = { + {"SIOCSIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric)}, + {"SIOCSIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu)}, + {"SIOCSIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen)}, + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, + {"SIOCSIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask)}, + {"SIOCSIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr)}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"SIOCSIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr)}, +#endif + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, +#ifdef SIOCSKEEPALIVE + {"SIOCSKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data)}, +#endif +#ifdef SIOCSOUTFILL + {"SIOCSOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data)}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq)}, +#endif + /* Last entry if for unmatched (possibly hostname) arg. */ +#if ENABLE_FEATURE_IPV6 + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ + {"SIOCDIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ +#endif + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, +}; + +static const struct options OptArray[] = { + {"metric", N_ARG, ARG_METRIC, 0}, + {"mtu", N_ARG, ARG_MTU, 0}, + {"txqueuelen", N_ARG, ARG_TXQUEUELEN, 0}, + {"dstaddr", N_ARG, ARG_DSTADDR, 0}, + {"netmask", N_ARG, ARG_NETMASK, 0}, + {"broadcast", N_ARG | M_CLR, ARG_BROADCAST, IFF_BROADCAST}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"hw", N_ARG, ARG_HW, 0}, +#endif + {"pointopoint", N_ARG | M_CLR, ARG_POINTOPOINT, IFF_POINTOPOINT}, +#ifdef SIOCSKEEPALIVE + {"keepalive", N_ARG, ARG_KEEPALIVE, 0}, +#endif +#ifdef SIOCSOUTFILL + {"outfill", N_ARG, ARG_OUTFILL, 0}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"mem_start", N_ARG, ARG_MEM_START, 0}, + {"io_addr", N_ARG, ARG_IO_ADDR, 0}, + {"irq", N_ARG, ARG_IRQ, 0}, +#endif +#if ENABLE_FEATURE_IPV6 + {"add", N_ARG, ARG_ADD_DEL, 0}, + {"del", N_ARG, ARG_ADD_DEL, 0}, +#endif + {"arp", N_CLR | M_SET, 0, IFF_NOARP}, + {"trailers", N_CLR | M_SET, 0, IFF_NOTRAILERS}, + {"promisc", N_SET | M_CLR, 0, IFF_PROMISC}, + {"multicast", N_SET | M_CLR, 0, IFF_MULTICAST}, + {"allmulti", N_SET | M_CLR, 0, IFF_ALLMULTI}, + {"dynamic", N_SET | M_CLR, 0, IFF_DYNAMIC}, + {"up", N_SET, 0, (IFF_UP | IFF_RUNNING)}, + {"down", N_CLR, 0, IFF_UP}, + {NULL, 0, ARG_HOSTNAME, (IFF_UP | IFF_RUNNING)} +}; + +/* + * A couple of prototypes. + */ + +#if ENABLE_FEATURE_IFCONFIG_HW +static int in_ether(const char *bufp, struct sockaddr *sap); +#endif + +#if ENABLE_FEATURE_IFCONFIG_STATUS +extern int interface_opt_a; +extern int display_interfaces(char *ifname); +#endif + +/* + * Our main function. + */ + +int ifconfig_main(int argc, char **argv) +{ + struct ifreq ifr; + struct sockaddr_in sai; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 sai6; +#endif +#if ENABLE_FEATURE_IFCONFIG_HW + struct sockaddr sa; +#endif + const struct arg1opt *a1op; + const struct options *op; + int sockfd; /* socket fd we use to manipulate stuff with */ + int selector; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + unsigned int mask; + unsigned int did_flags; + unsigned int sai_hostname, sai_netmask; +#else + unsigned char mask; + unsigned char did_flags; +#endif + char *p; + /*char host[128];*/ + const char *host = NULL; /* make gcc happy */ + + did_flags = 0; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + sai_hostname = 0; + sai_netmask = 0; +#endif + + /* skip argv[0] */ + ++argv; + --argc; + +#if ENABLE_FEATURE_IFCONFIG_STATUS + if (argc > 0 && (argv[0][0] == '-' && argv[0][1] == 'a' && !argv[0][2])) { + interface_opt_a = 1; + --argc; + ++argv; + } +#endif + + if (argc <= 1) { +#if ENABLE_FEATURE_IFCONFIG_STATUS + return display_interfaces(argc ? *argv : NULL); +#else + bb_error_msg_and_die("no support for status display"); +#endif + } + + /* Create a channel to the NET kernel. */ + sockfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + /* get interface name */ + safe_strncpy(ifr.ifr_name, *argv, IFNAMSIZ); + + /* Process the remaining arguments. */ + while (*++argv != (char *) NULL) { + p = *argv; + mask = N_MASK; + if (*p == '-') { /* If the arg starts with '-'... */ + ++p; /* advance past it and */ + mask = M_MASK; /* set the appropriate mask. */ + } + for (op = OptArray; op->name; op++) { /* Find table entry. */ + if (strcmp(p, op->name) == 0) { /* If name matches... */ + mask &= op->flags; + if (mask) /* set the mask and go. */ + goto FOUND_ARG; + /* If we get here, there was a valid arg with an */ + /* invalid '-' prefix. */ + bb_error_msg_and_die("bad: '%s'", p-1); + } + } + + /* We fell through, so treat as possible hostname. */ + a1op = Arg1Opt + (sizeof(Arg1Opt) / sizeof(Arg1Opt[0])) - 1; + mask = op->arg_flags; + goto HOSTNAME; + + FOUND_ARG: + if (mask & ARG_MASK) { + mask = op->arg_flags; + a1op = Arg1Opt + (op - OptArray); + if (mask & A_NETMASK & did_flags) + bb_show_usage(); + if (*++argv == NULL) { + if (mask & A_ARG_REQ) + bb_show_usage(); + --argv; + mask &= A_SET_AFTER; /* just for broadcast */ + } else { /* got an arg so process it */ + HOSTNAME: + did_flags |= (mask & (A_NETMASK|A_HOSTNAME)); + if (mask & A_CAST_HOST_COPY) { +#if ENABLE_FEATURE_IFCONFIG_HW + if (mask & A_CAST_RESOLVE) { +#endif +#if ENABLE_FEATURE_IPV6 + char *prefix; + int prefix_len = 0; +#endif + /*safe_strncpy(host, *argv, (sizeof host));*/ + host = *argv; +#if ENABLE_FEATURE_IPV6 + prefix = strchr(host, '/'); + if (prefix) { + prefix_len = xatou_range(prefix + 1, 0, 128); + *prefix = '\0'; + } +#endif + sai.sin_family = AF_INET; + sai.sin_port = 0; + if (!strcmp(host, bb_str_default)) { + /* Default is special, meaning 0.0.0.0. */ + sai.sin_addr.s_addr = INADDR_ANY; + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + else if ((host[0] == '+' && !host[1]) && (mask & A_BROADCAST) + && (did_flags & (A_NETMASK|A_HOSTNAME)) == (A_NETMASK|A_HOSTNAME) + ) { + /* + is special, meaning broadcast is derived. */ + sai.sin_addr.s_addr = (~sai_netmask) | (sai_hostname & sai_netmask); + } +#endif +#if ENABLE_FEATURE_IPV6 + else if (inet_pton(AF_INET6, host, &sai6.sin6_addr) > 0) { + int sockfd6; + struct in6_ifreq ifr6; + + memcpy((char *) &ifr6.ifr6_addr, + (char *) &sai6.sin6_addr, + sizeof(struct in6_addr)); + + /* Create a channel to the NET kernel. */ + sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0); + if (ioctl(sockfd6, SIOGIFINDEX, &ifr) < 0) + bb_perror_msg_and_die("SIOGIFINDEX"); + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + ifr6.ifr6_prefixlen = prefix_len; + if (ioctl(sockfd6, a1op->selector, &ifr6) < 0) + bb_perror_msg_and_die(a1op->name); + continue; + } +#endif + else if (inet_aton(host, &sai.sin_addr) == 0) { + /* It's not a dotted quad. */ + struct hostent *hp = xgethostbyname(host); + memcpy((char *) &sai.sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + if (mask & A_HOSTNAME) + sai_hostname = sai.sin_addr.s_addr; + if (mask & A_NETMASK) + sai_netmask = sai.sin_addr.s_addr; +#endif + p = (char *) &sai; +#if ENABLE_FEATURE_IFCONFIG_HW + } else { /* A_CAST_HOST_COPY_IN_ETHER */ + /* This is the "hw" arg case. */ + if (strcmp("ether", *argv) || !*++argv) + bb_show_usage(); + /*safe_strncpy(host, *argv, sizeof(host));*/ + host = *argv; + if (in_ether(host, &sa)) + bb_error_msg_and_die("invalid hw-addr %s", host); + p = (char *) &sa; + } +#endif + memcpy( (((char *)&ifr) + a1op->ifr_offset), + p, sizeof(struct sockaddr)); + } else { + /* FIXME: error check?? */ + unsigned long i = strtoul(*argv, NULL, 0); + p = ((char *)&ifr) + a1op->ifr_offset; +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + if (mask & A_MAP_TYPE) { + if (ioctl(sockfd, SIOCGIFMAP, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFMAP"); + if ((mask & A_MAP_UCHAR) == A_MAP_UCHAR) + *((unsigned char *) p) = i; + else if (mask & A_MAP_USHORT) + *((unsigned short *) p) = i; + else + *((unsigned long *) p) = i; + } else +#endif + if (mask & A_CAST_CHAR_PTR) + *((caddr_t *) p) = (caddr_t) i; + else /* A_CAST_INT */ + *((int *) p) = i; + } + + if (ioctl(sockfd, a1op->selector, &ifr) < 0) + bb_perror_msg_and_die(a1op->name); +#ifdef QUESTIONABLE_ALIAS_CASE + if (mask & A_COLON_CHK) { + /* + * Don't do the set_flag() if the address is an alias with + * a '-' at the end, since it's deleted already! - Roman + * + * Should really use regex.h here, not sure though how well + * it'll go with the cross-platform support etc. + */ + char *ptr; + short int found_colon = 0; + for (ptr = ifr.ifr_name; *ptr; ptr++) + if (*ptr == ':') + found_colon++; + if (found_colon && ptr[-1] == '-') + continue; + } +#endif + } + if (!(mask & A_SET_AFTER)) + continue; + mask = N_SET; + } + + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFFLAGS"); + selector = op->selector; + if (mask & SET_MASK) + ifr.ifr_flags |= selector; + else + ifr.ifr_flags &= ~selector; + if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCSIFFLAGS"); + } /* while() */ + + if (ENABLE_FEATURE_CLEAN_UP) + close(sockfd); + return 0; +} + +#if ENABLE_FEATURE_IFCONFIG_HW +/* Input an Ethernet address and convert to binary. */ +static int in_ether(const char *bufp, struct sockaddr *sap) +{ + char *ptr; + int i, j; + unsigned char val; + unsigned char c; + + sap->sa_family = ARPHRD_ETHER; + ptr = sap->sa_data; + + i = 0; + do { + j = val = 0; + + /* We might get a semicolon here - not required. */ + if (i && (*bufp == ':')) { + bufp++; + } + + do { + c = *bufp; + if (((unsigned char)(c - '0')) <= 9) { + c -= '0'; + } else if (((unsigned char)((c|0x20) - 'a')) <= 5) { + c = (c|0x20) - ('a'-10); + } else if (j && (c == ':' || c == 0)) { + break; + } else { + return -1; + } + ++bufp; + val <<= 4; + val += c; + } while (++j < 2); + *ptr++ = val; + } while (++i < ETH_ALEN); + + return *bufp; /* Error if we don't end at end of string. */ +} +#endif diff --git a/networking/ifupdown.c b/networking/ifupdown.c new file mode 100644 index 0000000..76ff2a8 --- /dev/null +++ b/networking/ifupdown.c @@ -0,0 +1,1270 @@ +/* vi: set sw=4 ts=4: */ +/* + * ifupdown for busybox + * Copyright (c) 2002 Glenn McGrath <bug1@iinet.net.au> + * Copyright (c) 2003-2004 Erik Andersen <andersen@codepoet.org> + * + * Based on ifupdown v 0.6.4 by Anthony Towns + * Copyright (c) 1999 Anthony Towns <aj@azure.humbug.org.au> + * + * Changes to upstream version + * Remove checks for kernel version, assume kernel version 2.2.0 or better. + * Lines in the interfaces file cannot wrap. + * To adhere to the FHS, the default state file is /var/run/ifstate. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <sys/utsname.h> +#include <fnmatch.h> +#include <getopt.h> + +#define MAX_OPT_DEPTH 10 +#define EUNBALBRACK 10001 +#define EUNDEFVAR 10002 +#define EUNBALPER 10000 + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +#define MAX_INTERFACE_LENGTH 10 +#endif + +#define debug_noise(fmt, args...) + +/* Forward declaration */ +struct interface_defn_t; + +typedef int execfn(char *command); + +struct method_t +{ + char *name; + int (*up)(struct interface_defn_t *ifd, execfn *e); + int (*down)(struct interface_defn_t *ifd, execfn *e); +}; + +struct address_family_t +{ + char *name; + int n_methods; + const struct method_t *method; +}; + +struct mapping_defn_t +{ + struct mapping_defn_t *next; + + int max_matches; + int n_matches; + char **match; + + char *script; + + int max_mappings; + int n_mappings; + char **mapping; +}; + +struct variable_t +{ + char *name; + char *value; +}; + +struct interface_defn_t +{ + const struct address_family_t *address_family; + const struct method_t *method; + + char *iface; + int max_options; + int n_options; + struct variable_t *option; +}; + +struct interfaces_file_t +{ + llist_t *autointerfaces; + llist_t *ifaces; + struct mapping_defn_t *mappings; +}; + +#define OPTION_STR "anvf" USE_FEATURE_IFUPDOWN_MAPPING("m") "i:" +enum { + OPT_do_all = 0x1, + OPT_no_act = 0x2, + OPT_verbose = 0x4, + OPT_force = 0x8, + OPT_no_mappings = 0x10, +}; +#define DO_ALL (option_mask32 & OPT_do_all) +#define NO_ACT (option_mask32 & OPT_no_act) +#define VERBOSE (option_mask32 & OPT_verbose) +#define FORCE (option_mask32 & OPT_force) +#define NO_MAPPINGS (option_mask32 & OPT_no_mappings) + +static char **__myenviron; + +static char *startup_PATH; + +#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6 + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + +static unsigned count_bits(unsigned a) +{ + unsigned result; + result = (a & 0x55) + ((a >> 1) & 0x55); + result = (result & 0x33) + ((result >> 2) & 0x33); + return (result & 0x0F) + ((result >> 4) & 0x0F); +} + +static int count_netmask_bits(char *dotted_quad) +{ + unsigned result, a, b, c, d; + /* Found a netmask... Check if it is dotted quad */ + if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + return -1; + // FIXME: will be confused by e.g. 255.0.255.0 + result = count_bits(a); + result += count_bits(b); + result += count_bits(c); + result += count_bits(d); + return (int)result; +} +#endif + +static void addstr(char **bufp, const char *str, size_t str_length) +{ + /* xasprintf trick will be smaller, but we are often + * called with str_length == 1 - don't want to have + * THAT much of malloc/freeing! */ + char *buf = *bufp; + int len = (buf ? strlen(buf) : 0); + str_length++; + buf = xrealloc(buf, len + str_length); + /* copies at most str_length-1 chars! */ + safe_strncpy(buf + len, str, str_length); + *bufp = buf; +} + +static int strncmpz(const char *l, const char *r, size_t llen) +{ + int i = strncmp(l, r, llen); + + if (i == 0) + return -r[llen]; + return i; +} + +static char *get_var(const char *id, size_t idlen, struct interface_defn_t *ifd) +{ + int i; + + if (strncmpz(id, "iface", idlen) == 0) { + char *result; + static char label_buf[20]; + safe_strncpy(label_buf, ifd->iface, sizeof(label_buf)); + result = strchr(label_buf, ':'); + if (result) { + *result = '\0'; + } + return label_buf; + } + if (strncmpz(id, "label", idlen) == 0) { + return ifd->iface; + } + for (i = 0; i < ifd->n_options; i++) { + if (strncmpz(id, ifd->option[i].name, idlen) == 0) { + return ifd->option[i].value; + } + } + return NULL; +} + +static char *parse(const char *command, struct interface_defn_t *ifd) +{ + char *result = NULL; + size_t old_pos[MAX_OPT_DEPTH] = { 0 }; + int okay[MAX_OPT_DEPTH] = { 1 }; + int opt_depth = 1; + + while (*command) { + switch (*command) { + default: + addstr(&result, command, 1); + command++; + break; + case '\\': + if (command[1]) { + addstr(&result, command + 1, 1); + command += 2; + } else { + addstr(&result, command, 1); + command++; + } + break; + case '[': + if (command[1] == '[' && opt_depth < MAX_OPT_DEPTH) { + old_pos[opt_depth] = strlen(result); + okay[opt_depth] = 1; + opt_depth++; + command += 2; + } else { + addstr(&result, "[", 1); + command++; + } + break; + case ']': + if (command[1] == ']' && opt_depth > 1) { + opt_depth--; + if (!okay[opt_depth]) { + result[old_pos[opt_depth]] = '\0'; + } + command += 2; + } else { + addstr(&result, "]", 1); + command++; + } + break; + case '%': + { + char *nextpercent; + char *varvalue; + + command++; + nextpercent = strchr(command, '%'); + if (!nextpercent) { + errno = EUNBALPER; + free(result); + return NULL; + } + + varvalue = get_var(command, nextpercent - command, ifd); + + if (varvalue) { + addstr(&result, varvalue, strlen(varvalue)); + } else { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + /* Sigh... Add a special case for 'ip' to convert from + * dotted quad to bit count style netmasks. */ + if (strncmp(command, "bnmask", 6)==0) { + unsigned res; + varvalue = get_var("netmask", 7, ifd); + if (varvalue && (res = count_netmask_bits(varvalue)) > 0) { + const char *argument = utoa(res); + addstr(&result, argument, strlen(argument)); + command = nextpercent + 1; + break; + } + } +#endif + okay[opt_depth - 1] = 0; + } + + command = nextpercent + 1; + } + break; + } + } + + if (opt_depth > 1) { + errno = EUNBALBRACK; + free(result); + return NULL; + } + + if (!okay[0]) { + errno = EUNDEFVAR; + free(result); + return NULL; + } + + return result; +} + +/* execute() returns 1 for success and 0 for failure */ +static int execute(const char *command, struct interface_defn_t *ifd, execfn *exec) +{ + char *out; + int ret; + + out = parse(command, ifd); + if (!out) { + return 0; + } + ret = (*exec)(out); + + free(out); + if (ret != 1) { + return 0; + } + return 1; +} +#endif + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 +static int loopback_up6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add ::1 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% add ::1", ifd, exec); +#endif +} + +static int loopback_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% del ::1", ifd, exec); +#endif +} + +static int static_up6(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%netmask% dev %iface% [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); +#else + result = execute("ifconfig %iface% [[media %media%]] [[hw %hwaddress%]] [[mtu %mtu%]] up", ifd, exec); + result += execute("ifconfig %iface% add %address%/%netmask%", ifd, exec); + result += execute("[[ route -A inet6 add ::/0 gw %gateway% ]]", ifd, exec); +#endif + return ((result == 3) ? 3 : 0); +} + +static int static_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% down", ifd, exec); +#endif +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP +static int v4tunnel_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; + result = execute("ip tunnel add %iface% mode sit remote " + "%endpoint% [[local %local%]] [[ttl %ttl%]]", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + result += execute("ip addr add %address%/%netmask% dev %iface%", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); + return ((result == 4) ? 4 : 0); +} + +static int v4tunnel_down(struct interface_defn_t * ifd, execfn * exec) +{ + return execute("ip tunnel del %iface%", ifd, exec); +} +#endif + +static const struct method_t methods6[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + { "v4tunnel", v4tunnel_up, v4tunnel_down, }, +#endif + { "static", static_up6, static_down6, }, + { "loopback", loopback_up6, loopback_down6, }, +}; + +static const struct address_family_t addr_inet6 = { + "inet6", + sizeof(methods6) / sizeof(struct method_t), + methods6 +}; +#endif /* CONFIG_FEATURE_IFUPDOWN_IPV6 */ + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 +static int loopback_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add 127.0.0.1/8 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 up", ifd, exec); +#endif +} + +static int loopback_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 down", ifd, exec); +#endif +} + +static int static_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%bnmask% [[broadcast %broadcast%]] " + "dev %iface% [[peer %pointopoint%]] [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add default via %gateway% dev %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#else + /* ifconfig said to set iface up before it processes hw %hwaddress%, + * which then of course fails. Thus we run two separate ifconfig */ + result = execute("ifconfig %iface% [[hw %hwaddress%]] [[media %media%]] [[mtu %mtu%]] up", + ifd, exec); + result += execute("ifconfig %iface% %address% netmask %netmask% " + "[[broadcast %broadcast%]] [[pointopoint %pointopoint%]] ", + ifd, exec); + result += execute("[[ route add default gw %gateway% %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#endif +} + +static int static_down(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); +#else + result = execute("[[ route del default gw %gateway% %iface% ]]", ifd, exec); + result += execute("ifconfig %iface% down", ifd, exec); +#endif + return ((result == 2) ? 2 : 0); +} + +#ifndef CONFIG_APP_UDHCPC +struct dhcp_client_t +{ + const char *name; + const char *startcmd; + const char *stopcmd; +}; + +static const struct dhcp_client_t ext_dhcp_clients[] = { + { "udhcpc", + "udhcpc -R -n -p /var/run/udhcpc.%iface%.pid -i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + "kill -TERM `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", + }, + { "pump", + "pump -i %iface% [[-h %hostname%]] [[-l %leasehours%]]", + "pump -i %iface% -k", + }, + { "dhclient", + "dhclient -pf /var/run/dhclient.%iface%.pid %iface%", + "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null", + }, + { "dhcpcd", + "dhcpcd [[-h %hostname%]] [[-i %vendor%]] [[-I %clientid%]] [[-l %leasetime%]] %iface%", + "dhcpcd -k %iface%", + }, +}; +#endif + +static int dhcp_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid " + "-i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].startcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found"); + return 0; +#endif +} + +static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("kill -TERM " + "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].stopcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found, using static interface shutdown"); + return static_down(ifd, exec); +#endif +} + +static int manual_up_down(struct interface_defn_t *ifd, execfn *exec) +{ + return 1; +} + +static int bootp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("bootpc [[--bootfile %bootfile%]] --dev %iface% " + "[[--server %server%]] [[--hwaddr %hwaddr%]] " + "--returniffail --serverbcast", ifd, exec); +} + +static int ppp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("pon [[%provider%]]", ifd, exec); +} + +static int ppp_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("poff [[%provider%]]", ifd, exec); +} + +static int wvdial_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --start -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -b -m -- [[ %provider% ]]", ifd, exec); +} + +static int wvdial_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --stop -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -s 2", ifd, exec); +} + +static const struct method_t methods[] = { + { "manual", manual_up_down, manual_up_down, }, + { "wvdial", wvdial_up, wvdial_down, }, + { "ppp", ppp_up, ppp_down, }, + { "static", static_up, static_down, }, + { "bootp", bootp_up, static_down, }, + { "dhcp", dhcp_up, dhcp_down, }, + { "loopback", loopback_up, loopback_down, }, +}; + +static const struct address_family_t addr_inet = { + "inet", + sizeof(methods) / sizeof(struct method_t), + methods +}; + +#endif /* ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 */ + +static char *next_word(char **buf) +{ + unsigned short length; + char *word; + + if (!buf || !*buf || !**buf) { + return NULL; + } + + /* Skip over leading whitespace */ + word = skip_whitespace(*buf); + + /* Skip over comments */ + if (*word == '#') { + return NULL; + } + + /* Find the length of this word */ + length = strcspn(word, " \t\n"); + if (length == 0) { + return NULL; + } + *buf = word + length; + /*DBU:[dave@cray.com] if we are already at EOL dont't increment beyond it */ + if (**buf) { + **buf = '\0'; + (*buf)++; + } + + return word; +} + +static const struct address_family_t *get_address_family(const struct address_family_t *const af[], char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; af[i]; i++) { + if (strcmp(af[i]->name, name) == 0) { + return af[i]; + } + } + return NULL; +} + +static const struct method_t *get_method(const struct address_family_t *af, char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; i < af->n_methods; i++) { + if (strcmp(af->method[i].name, name) == 0) { + return &af->method[i]; + } + } + return NULL; +} + +static const llist_t *find_list_string(const llist_t *list, const char *string) +{ + if (string == NULL) + return NULL; + + while (list) { + if (strcmp(list->data, string) == 0) { + return list; + } + list = list->link; + } + return NULL; +} + +static struct interfaces_file_t *read_interfaces(const char *filename) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + struct mapping_defn_t *currmap = NULL; +#endif + struct interface_defn_t *currif = NULL; + struct interfaces_file_t *defn; + FILE *f; + char *firstword; + char *buf; + + enum { NONE, IFACE, MAPPING } currently_processing = NONE; + + defn = xzalloc(sizeof(struct interfaces_file_t)); + + f = xfopen(filename, "r"); + + while ((buf = xmalloc_getline(f)) != NULL) { + char *buf_ptr = buf; + + firstword = next_word(&buf_ptr); + if (firstword == NULL) { + free(buf); + continue; /* blank line */ + } + + if (strcmp(firstword, "mapping") == 0) { +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + currmap = xzalloc(sizeof(struct mapping_defn_t)); + + while ((firstword = next_word(&buf_ptr)) != NULL) { + if (currmap->max_matches == currmap->n_matches) { + currmap->max_matches = currmap->max_matches * 2 + 1; + currmap->match = xrealloc(currmap->match, sizeof(currmap->match) * currmap->max_matches); + } + + currmap->match[currmap->n_matches++] = xstrdup(firstword); + } + currmap->max_mappings = 0; + currmap->n_mappings = 0; + currmap->mapping = NULL; + currmap->script = NULL; + { + struct mapping_defn_t **where = &defn->mappings; + while (*where != NULL) { + where = &(*where)->next; + } + *where = currmap; + currmap->next = NULL; + } + debug_noise("Added mapping\n"); +#endif + currently_processing = MAPPING; + } else if (strcmp(firstword, "iface") == 0) { + static const struct address_family_t *const addr_fams[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 + &addr_inet, +#endif +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 + &addr_inet6, +#endif + NULL + }; + + char *iface_name; + char *address_family_name; + char *method_name; + llist_t *iface_list; + + currif = xzalloc(sizeof(struct interface_defn_t)); + iface_name = next_word(&buf_ptr); + address_family_name = next_word(&buf_ptr); + method_name = next_word(&buf_ptr); + + if (buf_ptr == NULL) { + bb_error_msg("too few parameters for line \"%s\"", buf); + return NULL; + } + + /* ship any trailing whitespace */ + buf_ptr = skip_whitespace(buf_ptr); + + if (buf_ptr[0] != '\0') { + bb_error_msg("too many parameters \"%s\"", buf); + return NULL; + } + + currif->iface = xstrdup(iface_name); + + currif->address_family = get_address_family(addr_fams, address_family_name); + if (!currif->address_family) { + bb_error_msg("unknown address type \"%s\"", address_family_name); + return NULL; + } + + currif->method = get_method(currif->address_family, method_name); + if (!currif->method) { + bb_error_msg("unknown method \"%s\"", method_name); + return NULL; + } + + for (iface_list = defn->ifaces; iface_list; iface_list = iface_list->link) { + struct interface_defn_t *tmp = (struct interface_defn_t *) iface_list->data; + if ((strcmp(tmp->iface, currif->iface) == 0) && + (tmp->address_family == currif->address_family)) { + bb_error_msg("duplicate interface \"%s\"", tmp->iface); + return NULL; + } + } + llist_add_to_end(&(defn->ifaces), (char*)currif); + + debug_noise("iface %s %s %s\n", currif->iface, address_family_name, method_name); + currently_processing = IFACE; + } else if (strcmp(firstword, "auto") == 0) { + while ((firstword = next_word(&buf_ptr)) != NULL) { + + /* Check the interface isnt already listed */ + if (find_list_string(defn->autointerfaces, firstword)) { + bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf); + } + + /* Add the interface to the list */ + llist_add_to_end(&(defn->autointerfaces), xstrdup(firstword)); + debug_noise("\nauto %s\n", firstword); + } + currently_processing = NONE; + } else { + switch (currently_processing) { + case IFACE: + { + int i; + + if (strlen(buf_ptr) == 0) { + bb_error_msg("option with empty value \"%s\"", buf); + return NULL; + } + + if (strcmp(firstword, "up") != 0 + && strcmp(firstword, "down") != 0 + && strcmp(firstword, "pre-up") != 0 + && strcmp(firstword, "post-down") != 0) { + for (i = 0; i < currif->n_options; i++) { + if (strcmp(currif->option[i].name, firstword) == 0) { + bb_error_msg("duplicate option \"%s\"", buf); + return NULL; + } + } + } + } + if (currif->n_options >= currif->max_options) { + struct variable_t *opt; + + currif->max_options = currif->max_options + 10; + opt = xrealloc(currif->option, sizeof(*opt) * currif->max_options); + currif->option = opt; + } + currif->option[currif->n_options].name = xstrdup(firstword); + currif->option[currif->n_options].value = xstrdup(buf_ptr); + if (!currif->option[currif->n_options].name) { + perror(filename); + return NULL; + } + if (!currif->option[currif->n_options].value) { + perror(filename); + return NULL; + } + debug_noise("\t%s=%s\n", currif->option[currif->n_options].name, + currif->option[currif->n_options].value); + currif->n_options++; + break; + case MAPPING: +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if (strcmp(firstword, "script") == 0) { + if (currmap->script != NULL) { + bb_error_msg("duplicate script in mapping \"%s\"", buf); + return NULL; + } else { + currmap->script = xstrdup(next_word(&buf_ptr)); + } + } else if (strcmp(firstword, "map") == 0) { + if (currmap->max_mappings == currmap->n_mappings) { + currmap->max_mappings = currmap->max_mappings * 2 + 1; + currmap->mapping = xrealloc(currmap->mapping, sizeof(char *) * currmap->max_mappings); + } + currmap->mapping[currmap->n_mappings] = xstrdup(next_word(&buf_ptr)); + currmap->n_mappings++; + } else { + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } +#endif + break; + case NONE: + default: + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } + } + free(buf); + } + if (ferror(f) != 0) { + bb_perror_msg_and_die("%s", filename); + } + fclose(f); + + return defn; +} + +static char *setlocalenv(char *format, const char *name, const char *value) +{ + char *result; + char *here; + char *there; + + result = xasprintf(format, name, value); + + for (here = there = result; *there != '=' && *there; there++) { + if (*there == '-') + *there = '_'; + if (isalpha(*there)) + *there = toupper(*there); + + if (isalnum(*there) || *there == '_') { + *here = *there; + here++; + } + } + memmove(here, there, strlen(there) + 1); + + return result; +} + +static void set_environ(struct interface_defn_t *iface, const char *mode) +{ + char **environend; + int i; + const int n_env_entries = iface->n_options + 5; + char **ppch; + + if (__myenviron != NULL) { + for (ppch = __myenviron; *ppch; ppch++) { + free(*ppch); + *ppch = NULL; + } + free(__myenviron); + } + __myenviron = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ )); + environend = __myenviron; + + for (i = 0; i < iface->n_options; i++) { + if (strcmp(iface->option[i].name, "up") == 0 + || strcmp(iface->option[i].name, "down") == 0 + || strcmp(iface->option[i].name, "pre-up") == 0 + || strcmp(iface->option[i].name, "post-down") == 0) { + continue; + } + *(environend++) = setlocalenv("IF_%s=%s", iface->option[i].name, iface->option[i].value); + } + + *(environend++) = setlocalenv("%s=%s", "IFACE", iface->iface); + *(environend++) = setlocalenv("%s=%s", "ADDRFAM", iface->address_family->name); + *(environend++) = setlocalenv("%s=%s", "METHOD", iface->method->name); + *(environend++) = setlocalenv("%s=%s", "MODE", mode); + *(environend++) = setlocalenv("%s=%s", "PATH", startup_PATH); +} + +static int doit(char *str) +{ + if (option_mask32 & (OPT_no_act|OPT_verbose)) { + puts(str); + } + if (!(option_mask32 & OPT_no_act)) { + pid_t child; + int status; + + fflush(NULL); + switch (child = fork()) { + case -1: /* failure */ + return 0; + case 0: /* child */ + execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, NULL, __myenviron); + exit(127); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return 0; + } + } + return 1; +} + +static int execute_all(struct interface_defn_t *ifd, const char *opt) +{ + int i; + char *buf; + for (i = 0; i < ifd->n_options; i++) { + if (strcmp(ifd->option[i].name, opt) == 0) { + if (!doit(ifd->option[i].value)) { + return 0; + } + } + } + + buf = xasprintf("run-parts /etc/network/if-%s.d", opt); + if (doit(buf) != 1) { + return 0; + } + return 1; +} + +static int check(char *str) { + return str != NULL; +} + +static int iface_up(struct interface_defn_t *iface) +{ + if (!iface->method->up(iface,check)) return -1; + set_environ(iface, "start"); + if (!execute_all(iface, "pre-up")) return 0; + if (!iface->method->up(iface, doit)) return 0; + if (!execute_all(iface, "up")) return 0; + return 1; +} + +static int iface_down(struct interface_defn_t *iface) +{ + if (!iface->method->down(iface,check)) return -1; + set_environ(iface, "stop"); + if (!execute_all(iface, "down")) return 0; + if (!iface->method->down(iface, doit)) return 0; + if (!execute_all(iface, "post-down")) return 0; + return 1; +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +static int popen2(FILE **in, FILE **out, char *command, ...) +{ + va_list ap; + char *argv[11] = { command }; + int argc; + int infd[2], outfd[2]; + pid_t pid; + + argc = 1; + va_start(ap, command); + while ((argc < 10) && (argv[argc] = va_arg(ap, char *))) { + argc++; + } + argv[argc] = NULL; /* make sure */ + va_end(ap); + + if (pipe(infd) != 0) { + return 0; + } + + if (pipe(outfd) != 0) { + close(infd[0]); + close(infd[1]); + return 0; + } + + fflush(NULL); + switch (pid = fork()) { + case -1: /* failure */ + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + return 0; + case 0: /* child */ + dup2(infd[0], 0); + dup2(outfd[1], 1); + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + execvp(command, argv); + exit(127); + default: /* parent */ + *in = fdopen(infd[1], "w"); + *out = fdopen(outfd[0], "r"); + close(infd[0]); + close(outfd[1]); + return pid; + } + /* unreached */ +} + +static char *run_mapping(char *physical, struct mapping_defn_t * map) +{ + FILE *in, *out; + int i, status; + pid_t pid; + + char *logical = xstrdup(physical); + + /* Run the mapping script. */ + pid = popen2(&in, &out, map->script, physical, NULL); + + /* popen2() returns 0 on failure. */ + if (pid == 0) + return logical; + + /* Write mappings to stdin of mapping script. */ + for (i = 0; i < map->n_mappings; i++) { + fprintf(in, "%s\n", map->mapping[i]); + } + fclose(in); + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + /* If the mapping script exited successfully, try to + * grab a line of output and use that as the name of the + * logical interface. */ + char *new_logical = (char *)xmalloc(MAX_INTERFACE_LENGTH); + + if (fgets(new_logical, MAX_INTERFACE_LENGTH, out)) { + /* If we are able to read a line of output from the script, + * remove any trailing whitespace and use this value + * as the name of the logical interface. */ + char *pch = new_logical + strlen(new_logical) - 1; + + while (pch >= new_logical && isspace(*pch)) + *(pch--) = '\0'; + + free(logical); + logical = new_logical; + } else { + /* If we are UNABLE to read a line of output, discard our + * freshly allocated memory. */ + free(new_logical); + } + } + + fclose(out); + + return logical; +} +#endif /* CONFIG_FEATURE_IFUPDOWN_MAPPING */ + +static llist_t *find_iface_state(llist_t *state_list, const char *iface) +{ + unsigned short iface_len = strlen(iface); + llist_t *search = state_list; + + while (search) { + if ((strncmp(search->data, iface, iface_len) == 0) && + (search->data[iface_len] == '=')) { + return search; + } + search = search->link; + } + return NULL; +} + +int ifupdown_main(int argc, char **argv) +{ + static const char statefile[] = "/var/run/ifstate"; + + int (*cmds)(struct interface_defn_t *) = NULL; + struct interfaces_file_t *defn; + llist_t *state_list = NULL; + llist_t *target_list = NULL; + const char *interfaces = "/etc/network/interfaces"; + int any_failures = 0; + + if (applet_name[2] == 'u') { + /* ifup command */ + cmds = iface_up; + } else { + /* ifdown command */ + cmds = iface_down; + } + + getopt32(argc, argv, OPTION_STR, &interfaces); + if (argc - optind > 0) { + if (DO_ALL) bb_show_usage(); + } else + if (!DO_ALL) bb_show_usage(); + + debug_noise("reading %s file:\n", interfaces); + defn = read_interfaces(interfaces); + debug_noise("\ndone reading %s\n\n", interfaces); + + if (!defn) { + exit(EXIT_FAILURE); + } + + startup_PATH = getenv("PATH"); + if (!startup_PATH) startup_PATH = ""; + + /* Create a list of interfaces to work on */ + if (DO_ALL) { + if (cmds == iface_up) { + target_list = defn->autointerfaces; + } else { + /* iface_down */ + const llist_t *list = state_list; + while (list) { + llist_add_to_end(&target_list, xstrdup(list->data)); + list = list->link; + } + target_list = defn->autointerfaces; + } + } else { + llist_add_to_end(&target_list, argv[optind]); + } + + + /* Update the interfaces */ + while (target_list) { + llist_t *iface_list; + struct interface_defn_t *currif; + char *iface; + char *liface; + char *pch; + int okay = 0; + int cmds_ret; + + iface = xstrdup(target_list->data); + target_list = target_list->link; + + pch = strchr(iface, '='); + if (pch) { + *pch = '\0'; + liface = xstrdup(pch + 1); + } else { + liface = xstrdup(iface); + } + + if (!FORCE) { + const llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + /* ifup */ + if (iface_state) { + bb_error_msg("interface %s already configured", iface); + continue; + } + } else { + /* ifdown */ + if (iface_state) { + bb_error_msg("interface %s not configured", iface); + continue; + } + } + } + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if ((cmds == iface_up) && !NO_MAPPINGS) { + struct mapping_defn_t *currmap; + + for (currmap = defn->mappings; currmap; currmap = currmap->next) { + int i; + for (i = 0; i < currmap->n_matches; i++) { + if (fnmatch(currmap->match[i], liface, 0) != 0) + continue; + if (VERBOSE) { + printf("Running mapping script %s on %s\n", currmap->script, liface); + } + liface = run_mapping(iface, currmap); + break; + } + } + } +#endif + + + iface_list = defn->ifaces; + while (iface_list) { + currif = (struct interface_defn_t *) iface_list->data; + if (strcmp(liface, currif->iface) == 0) { + char *oldiface = currif->iface; + + okay = 1; + currif->iface = iface; + + debug_noise("\nConfiguring interface %s (%s)\n", liface, currif->address_family->name); + + /* Call the cmds function pointer, does either iface_up() or iface_down() */ + cmds_ret = cmds(currif); + if (cmds_ret == -1) { + bb_error_msg("don't seem to have all the variables for %s/%s", + liface, currif->address_family->name); + any_failures += 1; + } else if (cmds_ret == 0) { + any_failures += 1; + } + + currif->iface = oldiface; + } + iface_list = iface_list->link; + } + if (VERBOSE) { + puts(""); + } + + if (!okay && !FORCE) { + bb_error_msg("ignoring unknown interface %s", liface); + any_failures += 1; + } else { + llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + char *newiface = xasprintf("%s=%s", iface, liface); + if (iface_state == NULL) { + llist_add_to_end(&state_list, newiface); + } else { + free(iface_state->data); + iface_state->data = newiface; + } + } else { + /* Remove an interface from the linked list */ + free(llist_pop(&iface_state)); + } + } + } + + /* Actually write the new state */ + if (!NO_ACT) { + FILE *state_fp = NULL; + + state_fp = xfopen(statefile, "w"); + while (state_list) { + if (state_list->data) { + fputs(state_list->data, state_fp); + fputc('\n', state_fp); + } + state_list = state_list->link; + } + fclose(state_fp); + } + + if (any_failures) + return 1; + return 0; +} diff --git a/networking/inetd.c b/networking/inetd.c new file mode 100644 index 0000000..ec7b2e8 --- /dev/null +++ b/networking/inetd.c @@ -0,0 +1,1767 @@ +/* vi: set sw=4 ts=4: */ +/* $Slackware: inetd.c 1.79s 2001/02/06 13:18:00 volkerdi Exp $ */ +/* $OpenBSD: inetd.c,v 1.79 2001/01/30 08:30:57 deraadt Exp $ */ +/* $NetBSD: inetd.c,v 1.11 1996/02/22 11:14:41 mycroft Exp $ */ +/* Busybox port by Vladimir Oleynik (C) 2001-2005 <dzo@simtreas.ru> */ +/* + * Copyright (c) 1983,1991 The Regents of the University of California. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* + * Inetd - Internet super-server + * + * This program invokes all internet services as needed. + * connection-oriented services are invoked each time a + * connection is made, by creating a process. This process + * is passed the connection as file descriptor 0 and is + * expected to do a getpeername to find out the source host + * and port. + * + * Datagram oriented services are invoked when a datagram + * arrives; a process is created and passed a pending message + * on file descriptor 0. Datagram servers may either connect + * to their peer, freeing up the original socket for inetd + * to receive further messages on, or ``take over the socket'', + * processing all arriving datagrams and, eventually, timing + * out. The first type of server is said to be ``multi-threaded''; + * the second type of server ``single-threaded''. + * + * Inetd uses a configuration file which is read at startup + * and, possibly, at some later time in response to a hangup signal. + * The configuration file is ``free format'' with fields given in the + * order shown below. Continuation lines for an entry must begin with + * a space or tab. All fields must be present in each entry. + * + * service name must be in /etc/services + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded, max # + * user[.group] or user[:group] user/group to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For RPC services + * service name/version must be in /etc/rpc + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded + * user[.group] or user[:group] user to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For non-RPC services, the "service name" can be of the form + * hostaddress:servicename, in which case the hostaddress is used + * as the host portion of the address to listen on. If hostaddress + * consists of a single `*' character, INADDR_ANY is used. + * + * A line can also consist of just + * hostaddress: + * where hostaddress is as in the preceding paragraph. Such a line must + * have no further fields; the specified hostaddress is remembered and + * used for all further lines that have no hostaddress specified, + * until the next such line (or EOF). (This is why * is provided to + * allow explicit specification of INADDR_ANY.) A line + * *: + * is implicitly in effect at the beginning of the file. + * + * The hostaddress specifier may (and often will) contain dots; + * the service name must not. + * + * For RPC services, host-address specifiers are accepted and will + * work to some extent; however, because of limitations in the + * portmapper interface, it will not work to try to give more than + * one line for any given RPC service, even if the host-address + * specifiers are different. + * + * Comment lines are indicated by a `#' in column 1. + */ + +/* + * Here's the scoop concerning the user[.:]group feature: + * + * 1) set-group-option off. + * + * a) user = root: NO setuid() or setgid() is done + * + * b) other: setgid(primary group as found in passwd) + * initgroups(name, primary group) + * setuid() + * + * 2) set-group-option on. + * + * a) user = root: setgid(specified group) + * NO initgroups() + * NO setuid() + * + * b) other: setgid(specified group) + * initgroups(name, specified group) + * setuid() + * + */ + +#include "busybox.h" +#include <syslog.h> +#include <sys/un.h> + +//#define CONFIG_FEATURE_INETD_RPC +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +//#define CONFIG_FEATURE_IPV6 + +#ifdef CONFIG_FEATURE_INETD_RPC +#include <rpc/rpc.h> +#include <rpc/pmap_clnt.h> +#endif + +#define _PATH_INETDCONF "/etc/inetd.conf" +#define _PATH_INETDPID "/var/run/inetd.pid" + + +#define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ +#define RETRYTIME (60*10) /* retry after bind or server fail */ + +#ifndef RLIMIT_NOFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif + +#ifndef OPEN_MAX +#define OPEN_MAX 64 +#endif + +/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ +#define FD_MARGIN (8) +static rlim_t rlim_ofile_cur = OPEN_MAX; +static struct rlimit rlim_ofile; + + +/* Check unsupporting builtin */ +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_FEATURE_ENABLED +#endif + +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_SETPROCTITLE +#endif + +typedef struct servtab { + char *se_hostaddr; /* host address to listen on */ + char *se_service; /* name of service */ + int se_socktype; /* type of socket to use */ + int se_family; /* address family */ + char *se_proto; /* protocol used */ +#ifdef CONFIG_FEATURE_INETD_RPC + int se_rpcprog; /* rpc program number */ + int se_rpcversl; /* rpc program lowest version */ + int se_rpcversh; /* rpc program highest version */ +#define isrpcservice(sep) ((sep)->se_rpcversl != 0) +#else +#define isrpcservice(sep) 0 +#endif + pid_t se_wait; /* single threaded server */ + short se_checked; /* looked at during merge */ + char *se_user; /* user name to run as */ + char *se_group; /* group name to run as */ +#ifdef INETD_FEATURE_ENABLED + const struct builtin *se_bi; /* if built-in, description */ +#endif + char *se_server; /* server program */ +#define MAXARGV 20 + char *se_argv[MAXARGV + 1]; /* program arguments */ + int se_fd; /* open descriptor */ + union { + struct sockaddr se_un_ctrladdr; + struct sockaddr_in se_un_ctrladdr_in; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 se_un_ctrladdr_in6; +#endif + struct sockaddr_un se_un_ctrladdr_un; + } se_un; /* bound address */ +#define se_ctrladdr se_un.se_un_ctrladdr +#define se_ctrladdr_in se_un.se_un_ctrladdr_in +#define se_ctrladdr_in6 se_un.se_un_ctrladdr_in6 +#define se_ctrladdr_un se_un.se_un_ctrladdr_un + int se_ctrladdr_size; + int se_max; /* max # of instances of this service */ + int se_count; /* number started since se_time */ + struct timeval se_time; /* start of se_count */ + struct servtab *se_next; +} servtab_t; + +static servtab_t *servtab; + +#ifdef INETD_FEATURE_ENABLED +struct builtin { + const char *bi_service; /* internally provided service name */ + int bi_socktype; /* type of socket supported */ + short bi_fork; /* 1 if should fork before call */ + short bi_wait; /* 1 if should wait for child */ + void (*bi_fn) (int, servtab_t *); +}; + + /* Echo received data */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +static void echo_stream(int, servtab_t *); +static void echo_dg(int, servtab_t *); +#endif + /* Internet /dev/null */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +static void discard_stream(int, servtab_t *); +static void discard_dg(int, servtab_t *); +#endif + /* Return 32 bit time since 1900 */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +static void machtime_stream(int, servtab_t *); +static void machtime_dg(int, servtab_t *); +#endif + /* Return human-readable time */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +static void daytime_stream(int, servtab_t *); +static void daytime_dg(int, servtab_t *); +#endif + /* Familiar character generator */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +static void chargen_stream(int, servtab_t *); +static void chargen_dg(int, servtab_t *); +#endif + +static const struct builtin builtins[] = { +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO + /* Echo received data */ + {"echo", SOCK_STREAM, 1, 0, echo_stream,}, + {"echo", SOCK_DGRAM, 0, 0, echo_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + /* Internet /dev/null */ + {"discard", SOCK_STREAM, 1, 0, discard_stream,}, + {"discard", SOCK_DGRAM, 0, 0, discard_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME + /* Return 32 bit time since 1900 */ + {"time", SOCK_STREAM, 0, 0, machtime_stream,}, + {"time", SOCK_DGRAM, 0, 0, machtime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + /* Return human-readable time */ + {"daytime", SOCK_STREAM, 0, 0, daytime_stream,}, + {"daytime", SOCK_DGRAM, 0, 0, daytime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + /* Familiar character generator */ + {"chargen", SOCK_STREAM, 1, 0, chargen_stream,}, + {"chargen", SOCK_DGRAM, 0, 0, chargen_dg,}, +#endif + {NULL, 0, 0, 0, NULL} +}; +#endif /* INETD_FEATURE_ENABLED */ + +static int global_queuelen = 128; +static int nsock, maxsock; +static fd_set allsock; +static int toomany; +static int timingout; +static struct servent *sp; +static uid_t uid; + +static char *CONFIG = _PATH_INETDCONF; + +static FILE *fconfig; +static char line[1024]; +static char *defhost; + +/* xstrdup(NULL) returns NULL, but this one + * will return newly-allocated "" if called with NULL arg + * TODO: audit whether this makes any real difference + */ +static char *xxstrdup(char *cp) +{ + return xstrdup(cp ? cp : ""); +} + +static int setconfig(void) +{ + free(defhost); + defhost = xstrdup("*"); + if (fconfig != NULL) { + fseek(fconfig, 0L, SEEK_SET); + return 1; + } + fconfig = fopen(CONFIG, "r"); + return (fconfig != NULL); +} + +static void endconfig(void) +{ + if (fconfig) { + (void) fclose(fconfig); + fconfig = NULL; + } + free(defhost); + defhost = 0; +} + +#ifdef CONFIG_FEATURE_INETD_RPC +static void register_rpc(servtab_t *sep) +{ + int n; + struct sockaddr_in ir_sin; + struct protoent *pp; + socklen_t size; + + if ((pp = getprotobyname(sep->se_proto + 4)) == NULL) { + bb_perror_msg("%s: getproto", sep->se_proto); + return; + } + size = sizeof ir_sin; + if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) { + bb_perror_msg("%s/%s: getsockname", + sep->se_service, sep->se_proto); + return; + } + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + (void) pmap_unset(sep->se_rpcprog, n); + if (!pmap_set(sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port))) + bb_perror_msg("%s %s: pmap_set: %u %u %u %u", + sep->se_service, sep->se_proto, + sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port)); + } +} + +static void unregister_rpc(servtab_t *sep) +{ + int n; + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + if (!pmap_unset(sep->se_rpcprog, n)) + bb_error_msg("pmap_unset(%u, %u)", sep->se_rpcprog, n); + } +} +#endif /* CONFIG_FEATURE_INETD_RPC */ + +static void freeconfig(servtab_t *cp) +{ + int i; + + free(cp->se_hostaddr); + free(cp->se_service); + free(cp->se_proto); + free(cp->se_user); + free(cp->se_group); + free(cp->se_server); + for (i = 0; i < MAXARGV; i++) + free(cp->se_argv[i]); +} + +static int bump_nofile (void) +{ +#define FD_CHUNK 32 + + struct rlimit rl; + + if (getrlimit (RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("getrlimit"); + return -1; + } + rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK); + rl.rlim_cur = MIN(FD_SETSIZE, rl.rlim_cur + FD_CHUNK); + if (rl.rlim_cur <= rlim_ofile_cur) { + bb_error_msg("bump_nofile: cannot extend file limit, max = %d", + (int) rl.rlim_cur); + return -1; + } + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("setrlimit"); + return -1; + } + + rlim_ofile_cur = rl.rlim_cur; + return 0; +} + +static void setup(servtab_t *sep) +{ + int r; + + sep->se_fd = socket(sep->se_family, sep->se_socktype, 0); + if (sep->se_fd < 0) { + bb_perror_msg("%s/%s: socket", sep->se_service, sep->se_proto); + return; + } + if (setsockopt_reuseaddr(sep->se_fd) < 0) + bb_perror_msg("setsockopt(SO_REUSEADDR)"); + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct passwd *pwd; + + /* + * for RPC services, attempt to use a reserved port + * if they are going to be running as root. + * + * Also, zero out the port for all RPC services; let bind() + * find one. + */ + sep->se_ctrladdr_in.sin_port = 0; + if (sep->se_user && (pwd = getpwnam(sep->se_user)) && + pwd->pw_uid == 0 && uid == 0) + r = bindresvport(sep->se_fd, &sep->se_ctrladdr_in); + else { + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r == 0) { + socklen_t len = sep->se_ctrladdr_size; + int saveerrno = errno; + + /* update se_ctrladdr_in.sin_port */ + r = getsockname(sep->se_fd, &sep->se_ctrladdr, &len); + if (r <= 0) + errno = saveerrno; + } + } + } else +#endif + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r < 0) { + bb_perror_msg("%s/%s (%d): bind", + sep->se_service, sep->se_proto, sep->se_ctrladdr.sa_family); + close(sep->se_fd); + sep->se_fd = -1; + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + return; + } + if (sep->se_socktype == SOCK_STREAM) + listen(sep->se_fd, global_queuelen); + + FD_SET(sep->se_fd, &allsock); + nsock++; + if (sep->se_fd > maxsock) { + maxsock = sep->se_fd; + if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); + } +} + +static char *nextline(void) +{ + char *cp; + FILE *fd = fconfig; + + if (fgets(line, sizeof(line), fd) == NULL) + return NULL; + cp = strchr(line, '\n'); + if (cp) + *cp = '\0'; + return line; +} + +static char *skip(char **cpp) /* int report; */ +{ + char *cp = *cpp; + char *start; + +/* erp: */ + if (*cpp == NULL) { + /* if (report) */ + /* bb_error_msg("syntax error in inetd config file"); */ + return NULL; + } + +again: + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\0') { + int c; + + c = getc(fconfig); + (void) ungetc(c, fconfig); + if (c == ' ' || c == '\t') + if ((cp = nextline())) + goto again; + *cpp = NULL; + /* goto erp; */ + return NULL; + } + start = cp; + while (*cp && *cp != ' ' && *cp != '\t') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + /* if ((*cpp = cp) == NULL) */ + /* goto erp; */ + + *cpp = cp; + return start; +} + +static servtab_t *new_servtab(void) +{ + return xmalloc(sizeof(servtab_t)); +} + +static servtab_t *dupconfig(servtab_t *sep) +{ + servtab_t *newtab; + int argc; + + newtab = new_servtab(); + memset(newtab, 0, sizeof(servtab_t)); + newtab->se_service = xstrdup(sep->se_service); + newtab->se_socktype = sep->se_socktype; + newtab->se_family = sep->se_family; + newtab->se_proto = xstrdup(sep->se_proto); +#ifdef CONFIG_FEATURE_INETD_RPC + newtab->se_rpcprog = sep->se_rpcprog; + newtab->se_rpcversl = sep->se_rpcversl; + newtab->se_rpcversh = sep->se_rpcversh; +#endif + newtab->se_wait = sep->se_wait; + newtab->se_user = xstrdup(sep->se_user); + newtab->se_group = xstrdup(sep->se_group); +#ifdef INETD_FEATURE_ENABLED + newtab->se_bi = sep->se_bi; +#endif + newtab->se_server = xstrdup(sep->se_server); + + for (argc = 0; argc <= MAXARGV; argc++) + newtab->se_argv[argc] = xstrdup(sep->se_argv[argc]); + newtab->se_max = sep->se_max; + + return newtab; +} + +static servtab_t *getconfigent(void) +{ + servtab_t *sep; + int argc; + char *cp, *arg; + char *hostdelim; + servtab_t *nsep; + servtab_t *psep; + + sep = new_servtab(); + + /* memset(sep, 0, sizeof *sep); */ + more: + /* freeconfig(sep); */ + + while ((cp = nextline()) && *cp == '#') /* skip comment line */; + if (cp == NULL) { + /* free(sep); */ + return NULL; + } + + memset((char *) sep, 0, sizeof *sep); + arg = skip(&cp); + if (arg == NULL) { + /* A blank line. */ + goto more; + } + + /* Check for a host name. */ + hostdelim = strrchr(arg, ':'); + if (hostdelim) { + *hostdelim = '\0'; + sep->se_hostaddr = xstrdup(arg); + arg = hostdelim + 1; + /* + * If the line is of the form `host:', then just change the + * default host for the following lines. + */ + if (*arg == '\0') { + arg = skip(&cp); + if (cp == NULL) { + free(defhost); + defhost = sep->se_hostaddr; + goto more; + } + } + } else + sep->se_hostaddr = xxstrdup(defhost); + + sep->se_service = xxstrdup(arg); + arg = skip(&cp); + + if (strcmp(arg, "stream") == 0) + sep->se_socktype = SOCK_STREAM; + else if (strcmp(arg, "dgram") == 0) + sep->se_socktype = SOCK_DGRAM; + else if (strcmp(arg, "rdm") == 0) + sep->se_socktype = SOCK_RDM; + else if (strcmp(arg, "seqpacket") == 0) + sep->se_socktype = SOCK_SEQPACKET; + else if (strcmp(arg, "raw") == 0) + sep->se_socktype = SOCK_RAW; + else + sep->se_socktype = -1; + + sep->se_proto = xxstrdup(skip(&cp)); + + if (strcmp(sep->se_proto, "unix") == 0) { + sep->se_family = AF_UNIX; + } else { + sep->se_family = AF_INET; + if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') +#ifdef CONFIG_FEATURE_IPV6 + sep->se_family = AF_INET6; +#else + bb_error_msg("%s: IPV6 not supported", sep->se_proto); +#endif + if (strncmp(sep->se_proto, "rpc/", 4) == 0) { +#ifdef CONFIG_FEATURE_INETD_RPC + char *p, *ccp; + long l; + + p = strchr(sep->se_service, '/'); + if (p == 0) { + bb_error_msg("%s: no rpc version", sep->se_service); + goto more; + } + *p++ = '\0'; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX) { + badafterall: + bb_error_msg("%s/%s: bad rpc version", sep->se_service, p); + goto more; + } + sep->se_rpcversl = sep->se_rpcversh = l; + if (*ccp == '-') { + p = ccp + 1; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX || l < sep->se_rpcversl || *ccp) + goto badafterall; + sep->se_rpcversh = l; + } else if (*ccp != '\0') + goto badafterall; +#else + bb_error_msg("%s: rpc services not supported", sep->se_service); +#endif + } + } + arg = skip(&cp); + if (arg == NULL) + goto more; + + { + char *s = strchr(arg, '.'); + if (s) { + *s++ = '\0'; + sep->se_max = xatoi(s); + } else + sep->se_max = toomany; + } + sep->se_wait = strcmp(arg, "wait") == 0; + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + sep->se_user = xxstrdup(skip(&cp)); + arg = strchr(sep->se_user, '.'); + if (arg == NULL) + arg = strchr(sep->se_user, ':'); + if (arg) { + *arg++ = '\0'; + sep->se_group = xstrdup(arg); + } + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + + sep->se_server = xxstrdup(skip(&cp)); + if (strcmp(sep->se_server, "internal") == 0) { +#ifdef INETD_FEATURE_ENABLED + const struct builtin *bi; + + for (bi = builtins; bi->bi_service; bi++) + if (bi->bi_socktype == sep->se_socktype && + strcmp(bi->bi_service, sep->se_service) == 0) + break; + if (bi->bi_service == 0) { + bb_error_msg("internal service %s unknown", sep->se_service); + goto more; + } + sep->se_bi = bi; + sep->se_wait = bi->bi_wait; +#else + bb_perror_msg("internal service %s unknown", sep->se_service); + goto more; +#endif + } +#ifdef INETD_FEATURE_ENABLED + else + sep->se_bi = NULL; +#endif + argc = 0; + for (arg = skip(&cp); cp; arg = skip(&cp)) { + if (argc < MAXARGV) + sep->se_argv[argc++] = xxstrdup(arg); + } + while (argc <= MAXARGV) + sep->se_argv[argc++] = NULL; + + /* + * Now that we've processed the entire line, check if the hostname + * specifier was a comma separated list of hostnames. If so + * we'll make new entries for each address. + */ + while ((hostdelim = strrchr(sep->se_hostaddr, ',')) != NULL) { + nsep = dupconfig(sep); + + /* + * NULL terminate the hostname field of the existing entry, + * and make a dup for the new entry. + */ + *hostdelim++ = '\0'; + nsep->se_hostaddr = xstrdup(hostdelim); + + nsep->se_next = sep->se_next; + sep->se_next = nsep; + } + + nsep = sep; + while (nsep != NULL) { + nsep->se_checked = 1; + if (nsep->se_family == AF_INET) { + if (!strcmp(nsep->se_hostaddr, "*")) + nsep->se_ctrladdr_in.sin_addr.s_addr = INADDR_ANY; + else if (!inet_aton(nsep->se_hostaddr, &nsep->se_ctrladdr_in.sin_addr)) { + struct hostent *hp; + + hp = gethostbyname(nsep->se_hostaddr); + if (hp == 0) { + bb_error_msg("%s: unknown host", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else if (hp->h_addrtype != AF_INET) { + bb_error_msg("%s: address isn't an Internet " + "address", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else { + int i = 1; + + memmove(&nsep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[0], sizeof(struct in_addr)); + while (hp->h_addr_list[i] != NULL) { + psep = dupconfig(nsep); + psep->se_hostaddr = xxstrdup(nsep->se_hostaddr); + psep->se_checked = 1; + memmove(&psep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[i], sizeof(struct in_addr)); + psep->se_ctrladdr_size = sizeof(psep->se_ctrladdr_in); + i++; + /* Prepend to list, don't want to look up */ + /* its hostname again. */ + psep->se_next = sep; + sep = psep; + } + } + } + } +/* XXX BUG?: is this skip: label supposed to remain? */ + skip: + nsep = nsep->se_next; + } + + /* + * Finally, free any entries which failed the gethostbyname + * check. + */ + psep = NULL; + nsep = sep; + while (nsep != NULL) { + servtab_t *tsep; + + if (nsep->se_checked == 0) { + tsep = nsep; + if (psep == NULL) { + sep = nsep->se_next; + nsep = sep; + } else { + nsep = nsep->se_next; + psep->se_next = nsep; + } + freeconfig(tsep); + } else { + nsep->se_checked = 0; + psep = nsep; + nsep = nsep->se_next; + } + } + + return sep; +} + +#define Block_Using_Signals(m) do { \ + sigemptyset(&m); \ + sigaddset(&m, SIGCHLD); \ + sigaddset(&m, SIGHUP); \ + sigaddset(&m, SIGALRM); \ + sigprocmask(SIG_BLOCK, &m, NULL); \ +} while(0) + +static servtab_t *enter(servtab_t *cp) +{ + servtab_t *sep; + sigset_t omask; + + sep = new_servtab(); + *sep = *cp; + sep->se_fd = -1; +#ifdef CONFIG_FEATURE_INETD_RPC + sep->se_rpcprog = -1; +#endif + Block_Using_Signals(omask); + sep->se_next = servtab; + servtab = sep; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + return sep; +} + +static int matchconf(servtab_t *old, servtab_t *new) +{ + if (strcmp(old->se_service, new->se_service) != 0) + return 0; + + if (strcmp(old->se_hostaddr, new->se_hostaddr) != 0) + return 0; + + if (strcmp(old->se_proto, new->se_proto) != 0) + return 0; + + /* + * If the new servtab is bound to a specific address, check that the + * old servtab is bound to the same entry. If the new service is not + * bound to a specific address then the check of se_hostaddr above + * is sufficient. + */ + + if (old->se_family == AF_INET && new->se_family == AF_INET && + memcmp(&old->se_ctrladdr_in.sin_addr, + &new->se_ctrladdr_in.sin_addr, + sizeof(new->se_ctrladdr_in.sin_addr)) != 0) + return 0; + +#ifdef CONFIG_FEATURE_IPV6 + if (old->se_family == AF_INET6 && new->se_family == AF_INET6 && + memcmp(&old->se_ctrladdr_in6.sin6_addr, + &new->se_ctrladdr_in6.sin6_addr, + sizeof(new->se_ctrladdr_in6.sin6_addr)) != 0) + return 0; +#endif + return 1; +} + +static void config(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep, *cp, **sepp; + sigset_t omask; + size_t n; + char protoname[10]; + + if (!setconfig()) { + bb_perror_msg("%s", CONFIG); + return; + } + for (sep = servtab; sep; sep = sep->se_next) + sep->se_checked = 0; + cp = getconfigent(); + while (cp != NULL) { + for (sep = servtab; sep; sep = sep->se_next) + if (matchconf(sep, cp)) + break; + + if (sep != 0) { + int i; + +#define SWAP(type, a, b) do {type c=(type)a; a=(type)b; b=(type)c;} while (0) + + Block_Using_Signals(omask); + /* + * sep->se_wait may be holding the pid of a daemon + * that we're waiting for. If so, don't overwrite + * it unless the config file explicitly says don't + * wait. + */ + if ( +#ifdef INETD_FEATURE_ENABLED + cp->se_bi == 0 && +#endif + (sep->se_wait == 1 || cp->se_wait == 0)) + sep->se_wait = cp->se_wait; + SWAP(int, cp->se_max, sep->se_max); + SWAP(char *, sep->se_user, cp->se_user); + SWAP(char *, sep->se_group, cp->se_group); + SWAP(char *, sep->se_server, cp->se_server); + for (i = 0; i < MAXARGV; i++) + SWAP(char *, sep->se_argv[i], cp->se_argv[i]); +#undef SWAP + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); + sep->se_rpcversl = cp->se_rpcversl; + sep->se_rpcversh = cp->se_rpcversh; +#endif + sigprocmask(SIG_UNBLOCK, &omask, NULL); + freeconfig(cp); + } else { + sep = enter(cp); + } + sep->se_checked = 1; + + switch (sep->se_family) { + case AF_UNIX: + if (sep->se_fd != -1) + break; + (void) unlink(sep->se_service); + n = strlen(sep->se_service); + if (n > sizeof sep->se_ctrladdr_un.sun_path - 1) + n = sizeof sep->se_ctrladdr_un.sun_path - 1; + safe_strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n + 1); + sep->se_ctrladdr_un.sun_family = AF_UNIX; + sep->se_ctrladdr_size = n + sizeof sep->se_ctrladdr_un.sun_family; + setup(sep); + break; + case AF_INET: + sep->se_ctrladdr_in.sin_family = AF_INET; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + // FIXME: atoi_or_else(str, 0) would be handy here + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + // FIXME: atoi_or_else(str, 0) would be handy here + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in.sin_port) { + sep->se_ctrladdr_in.sin_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: + sep->se_ctrladdr_in6.sin6_family = AF_INET6; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in6; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in6.sin6_port) { + sep->se_ctrladdr_in6.sin6_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#endif /* CONFIG_FEATURE_IPV6 */ + } + serv_unknown: + if (cp->se_next != NULL) { + servtab_t *tmp = cp; + + cp = cp->se_next; + free(tmp); + } else { + free(cp); + cp = getconfigent(); + } + } + endconfig(); + /* + * Purge anything not looked at above. + */ + Block_Using_Signals(omask); + sepp = &servtab; + while ((sep = *sepp)) { + if (sep->se_checked) { + sepp = &sep->se_next; + continue; + } + *sepp = sep->se_next; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); +#endif + if (sep->se_family == AF_UNIX) + (void) unlink(sep->se_service); + freeconfig(sep); + free(sep); + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); +} + + +static void reapchild(int sig ATTRIBUTE_UNUSED) +{ + pid_t pid; + int save_errno = errno, status; + servtab_t *sep; + + for (;;) { + pid = wait3(&status, WNOHANG, NULL); + if (pid <= 0) + break; + for (sep = servtab; sep; sep = sep->se_next) + if (sep->se_wait == pid) { + if (WIFEXITED(status) && WEXITSTATUS(status)) + bb_error_msg("%s: exit status 0x%x", + sep->se_server, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + bb_error_msg("%s: exit signal 0x%x", + sep->se_server, WTERMSIG(status)); + sep->se_wait = 1; + FD_SET(sep->se_fd, &allsock); + nsock++; + } + } + errno = save_errno; +} + +static void retry(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + timingout = 0; + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) { + switch (sep->se_family) { + case AF_UNIX: + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif + setup(sep); +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_fd != -1 && isrpcservice(sep)) + register_rpc(sep); +#endif + break; + } + } + } +} + +static void goaway(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + /* XXX signal race walking sep list */ + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) + continue; + + switch (sep->se_family) { + case AF_UNIX: + (void) unlink(sep->se_service); + break; + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_wait == 1 && isrpcservice(sep)) + unregister_rpc(sep); /* XXX signal race */ +#endif + break; + } + (void) close(sep->se_fd); + } + (void) unlink(_PATH_INETDPID); + exit(0); +} + + +#ifdef INETD_SETPROCTITLE +static char **Argv; +static char *LastArg; + +static void +inetd_setproctitle(char *a, int s) +{ + socklen_t size; + char *cp; + struct sockaddr_in prt_sin; + char buf[80]; + + cp = Argv[0]; + size = sizeof(prt_sin); + (void) snprintf(buf, sizeof buf, "-%s", a); + if (getpeername(s, (struct sockaddr *) &prt_sin, &size) == 0) { + char *sa = inet_ntoa(prt_sin.sin_addr); + + buf[sizeof(buf) - 1 - strlen(sa) - 3] = '\0'; + strcat(buf, " ["); + strcat(buf, sa); + strcat(buf, "]"); + } + strncpy(cp, buf, LastArg - cp); + cp += strlen(cp); + while (cp < LastArg) + *cp++ = ' '; +} +#endif + + +int +inetd_main(int argc, char *argv[]) +{ + servtab_t *sep; + struct passwd *pwd; + struct group *grp = NULL; + int tmpint; + struct sigaction sa, sapipe; + int opt; + pid_t pid; + char buf[50]; + char *stoomany; + sigset_t omask, wait_mask; + +#ifdef INETD_SETPROCTITLE + extern char **environ; + char **envp = environ; + + Argv = argv; + if (envp == 0 || *envp == 0) + envp = argv; + while (*envp) + envp++; + LastArg = envp[-1] + strlen(envp[-1]); +#endif + + openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + + opt = getopt32(argc, argv, "R:f", &stoomany); + if(opt & 1) { + toomany = xatoi_u(stoomany); + } + argc -= optind; + argv += optind; + + uid = getuid(); + if (uid != 0) + CONFIG = NULL; + if (argc > 0) + CONFIG = argv[0]; + if (CONFIG == NULL) + bb_error_msg_and_die("non-root must specify a config file"); + + if (!(opt & 2)) { +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(0, 0, argc, argv, "-f"); +#else + xdaemon(0, 0); +#endif + } else { + setsid(); + } + logmode = LOGMODE_SYSLOG; + + if (uid == 0) { + gid_t gid = getgid(); + + /* If run by hand, ensure groups vector gets trashed */ + setgroups(1, &gid); + } + + { + FILE *fp = fopen(_PATH_INETDPID, "w"); + + if (fp != NULL) { + fprintf(fp, "%u\n", getpid()); + (void) fclose(fp); + } + } + + if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { + bb_perror_msg("getrlimit"); + } else { + rlim_ofile_cur = rlim_ofile.rlim_cur; + if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ + rlim_ofile_cur = OPEN_MAX; + } + + memset((char *) &sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGALRM); + sigaddset(&sa.sa_mask, SIGCHLD); + sigaddset(&sa.sa_mask, SIGHUP); + sa.sa_handler = retry; + sigaction(SIGALRM, &sa, NULL); + config(SIGHUP); + sa.sa_handler = config; + sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = reapchild; + sigaction(SIGCHLD, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, &sapipe); + memset(&wait_mask, 0, sizeof(wait_mask)); + { + /* space for daemons to overwrite environment for ps */ +#define DUMMYSIZE 100 + char dummy[DUMMYSIZE]; + + (void) memset(dummy, 'x', DUMMYSIZE - 1); + dummy[DUMMYSIZE - 1] = '\0'; + + (void) setenv("inetd_dummy", dummy, 1); + } + + for (;;) { + int n, ctrl = -1; + fd_set readable; + + if (nsock == 0) { + Block_Using_Signals(omask); + while (nsock == 0) + sigsuspend(&wait_mask); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + } + + readable = allsock; + n = select(maxsock + 1, &readable, NULL, NULL, NULL); + if (n <= 0) { + if (n < 0 && errno != EINTR) { + bb_perror_msg("select"); + sleep(1); + } + continue; + } + + for (sep = servtab; n && sep; sep = sep->se_next) { + if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) + continue; + + n--; + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { + ctrl = accept(sep->se_fd, NULL, NULL); + if (ctrl < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("accept (for %s)", sep->se_service); + continue; + } + if (sep->se_family == AF_INET && sep->se_socktype == SOCK_STREAM) { + struct sockaddr_in peer; + socklen_t plen = sizeof(peer); + + if (getpeername(ctrl, (struct sockaddr *) &peer, &plen) < 0) { + bb_error_msg("cannot getpeername"); + close(ctrl); + continue; + } + if (ntohs(peer.sin_port) == 20) { + /* XXX ftp bounce */ + close(ctrl); + continue; + } + } + } else + ctrl = sep->se_fd; + + Block_Using_Signals(omask); + pid = 0; +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi == 0 || sep->se_bi->bi_fork) +#endif + { + if (sep->se_count++ == 0) + (void) gettimeofday(&sep->se_time, NULL); + else if (toomany > 0 && sep->se_count >= sep->se_max) { + struct timeval now; + + (void) gettimeofday(&now, NULL); + if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { + sep->se_time = now; + sep->se_count = 1; + } else { + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + if (sep->se_family == AF_INET && + ntohs(sep->se_ctrladdr_in.sin_port) >= IPPORT_RESERVED) { + /* + * Cannot close it -- there are + * thieves on the system. + * Simply ignore the connection. + */ + --sep->se_count; + continue; + } + bb_error_msg("%s/%s server failing (looping), service terminated", + sep->se_service, sep->se_proto); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + FD_CLR(sep->se_fd, &allsock); + (void) close(sep->se_fd); + sep->se_fd = -1; + sep->se_count = 0; + nsock--; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + continue; + } + } + pid = fork(); + } + if (pid < 0) { + bb_perror_msg("fork"); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + sleep(1); + continue; + } + if (pid && sep->se_wait) { + sep->se_wait = pid; + FD_CLR(sep->se_fd, &allsock); + nsock--; + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (pid == 0) { +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi) { + (*sep->se_bi->bi_fn)(ctrl, sep); + } else +#endif + { + pwd = getpwnam(sep->se_user); + if (pwd == NULL) { + bb_error_msg("getpwnam: %s: no such user", sep->se_user); + goto do_exit1; + } + if (setsid() < 0) + bb_perror_msg("%s: setsid", sep->se_service); + if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { + bb_error_msg("getgrnam: %s: no such group", sep->se_group); + goto do_exit1; + } + if (uid != 0) { + /* a user running private inetd */ + if (uid != pwd->pw_uid) + _exit(1); + } else if (pwd->pw_uid) { + if (sep->se_group) + pwd->pw_gid = grp->gr_gid; + xsetgid((gid_t) pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + xsetuid((uid_t) pwd->pw_uid); + } else if (sep->se_group) { + xsetgid(grp->gr_gid); + setgroups(1, &grp->gr_gid); + } + dup2(ctrl, 0); + if (ctrl) close(ctrl); + dup2(0, 1); + dup2(0, 2); + if (rlim_ofile.rlim_cur != rlim_ofile_cur) + if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) + bb_perror_msg("setrlimit"); + closelog(); + for (tmpint = rlim_ofile_cur - 1; --tmpint > 2;) + (void) close(tmpint); + sigaction(SIGPIPE, &sapipe, NULL); + execv(sep->se_server, sep->se_argv); + bb_perror_msg("execv %s", sep->se_server); +do_exit1: + if (sep->se_socktype != SOCK_STREAM) + recv(0, buf, sizeof(buf), 0); + _exit(1); + } + } + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + } /* for (sep = servtab...) */ + } /* for(;;) */ +} + +/* + * Internet services provided internally by inetd: + */ +#define BUFSIZE 4096 + +#if defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME) +static int dg_badinput(struct sockaddr_in *dg_sin) +{ + if (ntohs(dg_sin->sin_port) < IPPORT_RESERVED) + return 1; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST)) + return 1; + /* XXX compare against broadcast addresses in SIOCGIFCONF list? */ + return 0; +} +#endif + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + int i; + + inetd_setproctitle(sep->se_service, s); + while (1) { + i = read(s, buffer, sizeof(buffer)); + if (i <= 0) break; + /* FIXME: this isnt correct - safe_write()? */ + if (write(s, buffer, i) <= 0) break; + } + exit(0); +} + +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + int i; + socklen_t size; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + + size = sizeof(sa); + i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size); + if (i < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO */ + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + + inetd_setproctitle(sep->se_service, s); + while (1) { + errno = 0; + if (read(s, buffer, sizeof(buffer)) <= 0 && errno != EINTR) + exit(0); + } +} + +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + + (void) read(s, buffer, sizeof(buffer)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +#define LINESIZ 72 +static char ring[128]; +static char *endring; + +static void +initring(void) +{ + int i; + + endring = ring; + + for (i = 0; i <= 128; ++i) + if (isprint(i)) + *endring++ = i; +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_stream(int s, servtab_t *sep) +{ + char *rs; + int len; + char text[LINESIZ + 2]; + + inetd_setproctitle(sep->se_service, s); + + if (!endring) { + initring(); + rs = ring; + } + + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + rs = ring; + for (;;) { + len = endring - rs; + if (len >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + if (write(s, text, sizeof(text)) != sizeof(text)) + break; + } + exit(0); +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + static char *rs; + int len; + char text[LINESIZ + 2]; + socklen_t size; + + if (endring == 0) { + initring(); + rs = ring; + } + + size = sizeof(sa); + if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + + if ((len = endring - rs) >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +/* + * Return a machine readable date and time, in the form of the + * number of seconds since midnight, Jan 1, 1900. Since gettimeofday + * returns the number of seconds since midnight, Jan 1, 1970, + * we must add 2208988800 seconds to this figure to make up for + * some seventy years Bell Labs was asleep. + */ + +static u_int machtime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + fprintf(stderr, "Unable to get time of day\n"); + return 0L; + } + return htonl((u_int) tv.tv_sec + 2208988800UL); +} + +/* ARGSUSED */ +static void +machtime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + + result = machtime(); + (void) write(s, (char *) &result, sizeof(result)); +} + +/* ARGSUSED */ +static void +machtime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + struct sockaddr_in *dg_sin; + socklen_t size; + + size = sizeof(sa); + if (recvfrom(s, (char *) &result, sizeof(result), 0, &sa, &size) < 0) + return; + /* if (dg_badinput((struct sockaddr *)&ss)) */ + dg_sin = (struct sockaddr_in *) &sa; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST) || + ntohs(dg_sin->sin_port) < IPPORT_RESERVED / 2) + return; + result = machtime(); + (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +/* Return human-readable time of day */ +/* ARGSUSED */ +static void daytime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + + t = time(NULL); + + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) write(s, buffer, strlen(buffer)); +} + +/* Return human-readable time of day */ +/* ARGSUSED */ +void +daytime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + socklen_t size; + + t = time(NULL); + + size = sizeof(sa); + if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) sendto(s, buffer, strlen(buffer), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */ diff --git a/networking/interface.c b/networking/interface.c new file mode 100644 index 0000000..dd45582 --- /dev/null +++ b/networking/interface.c @@ -0,0 +1,1152 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen <andersen@codepoet.org> + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Added print_bytes_scaled function to reduce code size. + * Added some (potentially) missing defines. + * Improved display support for -a and for a named interface. + * + * ----------------------------------------------------------- + * + * ifconfig This file contains an implementation of the command + * that either displays or sets the characteristics of + * one or more of the system's networking interfaces. + * + * Version: $Id: interface.c,v 1.25 2004/08/26 21:45:21 andersen Exp $ + * + * Author: Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * and others. Copyright 1993 MicroWalt Corporation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Patched to support 'add' and 'del' keywords for INET(4) addresses + * by Mrs. Brisby <mrs.brisby@nimh.org> + * + * {1.34} - 19980630 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - gettext instead of catgets for i18n + * 10/1998 - Andi Kleen. Use interface list primitives. + * 20001008 - Bernd Eckenfels, Patch from RH for setting mtu + * (default AF was wrong) + */ + +#include "inet_common.h" +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <net/if.h> +#include <net/if_arp.h> +#include "busybox.h" + +#ifdef CONFIG_FEATURE_IPV6 +# define HAVE_AFINET6 1 +#else +# undef HAVE_AFINET6 +#endif + +#define _PATH_PROCNET_DEV "/proc/net/dev" +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" + +#ifdef HAVE_AFINET6 + +#ifndef _LINUX_IN6_H +/* + * This is in linux/include/net/ipv6.h. + */ + +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + unsigned int ifr6_ifindex; +}; + +#endif + +#endif /* HAVE_AFINET6 */ + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +#define SIOCSIFTXQLEN 0x8943 +#define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +#define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef HAVE_TXQUEUELEN +#define HAVE_TXQUEUELEN 1 +#endif + +#ifndef IFF_DYNAMIC +#define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +/* This structure defines protocol families and their handlers. */ +struct aftype { + const char *name; + const char *title; + int af; + int alen; + char *(*print) (unsigned char *); + char *(*sprint) (struct sockaddr *, int numeric); + int (*input) (int type, char *bufp, struct sockaddr *); + void (*herror) (char *text); + int (*rprint) (int options); + int (*rinput) (int typ, int ext, char **argv); + + /* may modify src */ + int (*getmask) (char *src, struct sockaddr * mask, char *name); + + int fd; + char *flag_file; +}; + +/* Display an Internet socket address. */ +static char *INET_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + + if (INET_rresolve(buff, sizeof(buff), (struct sockaddr_in *) sap, + numeric, 0xffffff00) != 0) + return NULL; + + return buff; +} + +static struct aftype inet_aftype = { + .name = "inet", + .title = "DARPA Internet", + .af = AF_INET, + .alen = 4, + .sprint = INET_sprint, + .fd = -1 +}; + +#ifdef HAVE_AFINET6 + +/* Display an Internet socket address. */ +/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */ +static char *INET6_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + if (INET6_rresolve + (buff, sizeof(buff), (struct sockaddr_in6 *) sap, numeric) != 0) + return safe_strncpy(buff, "[UNKNOWN]", sizeof(buff)); + return buff; +} + +static struct aftype inet6_aftype = { + .name = "inet6", + .title = "IPv6", + .af = AF_INET6, + .alen = sizeof(struct in6_addr), + .sprint = INET6_sprint, + .fd = -1 +}; + +#endif /* HAVE_AFINET6 */ + +/* Display an UNSPEC address. */ +static char *UNSPEC_print(unsigned char *ptr) +{ + static char buff[sizeof(struct sockaddr) * 3 + 1]; + char *pos; + unsigned int i; + + pos = buff; + for (i = 0; i < sizeof(struct sockaddr); i++) { + /* careful -- not every libc's sprintf returns # bytes written */ + sprintf(pos, "%02X-", (*ptr++ & 0377)); + pos += 3; + } + /* Erase trailing "-". Works as long as sizeof(struct sockaddr) != 0 */ + *--pos = '\0'; + return buff; +} + +/* Display an UNSPEC socket address. */ +static char *UNSPEC_sprint(struct sockaddr *sap, int numeric) +{ + static char buf[64]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buf, "[NONE SET]", sizeof(buf)); + return UNSPEC_print((unsigned char *)sap->sa_data); +} + +static struct aftype unspec_aftype = { + "unspec", "UNSPEC", AF_UNSPEC, 0, + UNSPEC_print, UNSPEC_sprint, NULL, NULL, + NULL, +}; + +static struct aftype * const aftypes[] = { + &inet_aftype, +#ifdef HAVE_AFINET6 + &inet6_aftype, +#endif + &unspec_aftype, + NULL +}; + +/* Check our protocol family table for this family. */ +static struct aftype *get_afntype(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return *afp; + afp++; + } + return NULL; +} + +/* Check our protocol family table for this family and return its socket */ +static int get_socket_for_af(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return (*afp)->fd; + afp++; + } + return -1; +} + +struct user_net_device_stats { + unsigned long long rx_packets; /* total packets received */ + unsigned long long tx_packets; /* total packets transmitted */ + unsigned long long rx_bytes; /* total bytes received */ + unsigned long long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* no space in linux buffers */ + unsigned long tx_dropped; /* no space available in linux */ + unsigned long rx_multicast; /* multicast packets received */ + unsigned long rx_compressed; + unsigned long tx_compressed; + unsigned long collisions; + + /* detailed rx_errors: */ + unsigned long rx_length_errors; + unsigned long rx_over_errors; /* receiver ring buff overflow */ + unsigned long rx_crc_errors; /* recved pkt with crc error */ + unsigned long rx_frame_errors; /* recv'd frame alignment error */ + unsigned long rx_fifo_errors; /* recv'r fifo overrun */ + unsigned long rx_missed_errors; /* receiver missed packet */ + /* detailed tx_errors */ + unsigned long tx_aborted_errors; + unsigned long tx_carrier_errors; + unsigned long tx_fifo_errors; + unsigned long tx_heartbeat_errors; + unsigned long tx_window_errors; +}; + +struct interface { + struct interface *next, *prev; + char name[IFNAMSIZ]; /* interface name */ + short type; /* if type */ + short flags; /* various flags */ + int metric; /* routing metric */ + int mtu; /* MTU value */ + int tx_queue_len; /* transmit queue length */ + struct ifmap map; /* hardware setup */ + struct sockaddr addr; /* IP address */ + struct sockaddr dstaddr; /* P-P IP address */ + struct sockaddr broadaddr; /* IP broadcast address */ + struct sockaddr netmask; /* IP network mask */ + int has_ip; + char hwaddr[32]; /* HW address */ + int statistics_valid; + struct user_net_device_stats stats; /* statistics */ + int keepalive; /* keepalive value for SLIP */ + int outfill; /* outfill value for SLIP */ +}; + + +int interface_opt_a; /* show all interfaces */ + +static struct interface *int_list, *int_last; +static int skfd = -1; /* generic raw socket desc. */ + + +static int sockets_open(int family) +{ + struct aftype * const *aft; + int sfd = -1; + static int force = -1; + + if (force < 0) { + force = 0; + if (get_linux_version_code() < KERNEL_VERSION(2,1,0)) + force = 1; + if (access("/proc/net", R_OK)) + force = 1; + } + for (aft = aftypes; *aft; aft++) { + struct aftype *af = *aft; + int type = SOCK_DGRAM; + + if (af->af == AF_UNSPEC) + continue; + if (family && family != af->af) + continue; + if (af->fd != -1) { + sfd = af->fd; + continue; + } + /* Check some /proc file first to not stress kmod */ + if (!family && !force && af->flag_file) { + if (access(af->flag_file, R_OK)) + continue; + } + af->fd = socket(af->af, type, 0); + if (af->fd >= 0) + sfd = af->fd; + } + if (sfd < 0) { + bb_error_msg("no usable address families found"); + } + return sfd; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +static void sockets_close(void) +{ + struct aftype * const *aft; + for (aft = aftypes; *aft != NULL; aft++) { + struct aftype *af = *aft; + if( af->fd != -1 ) { + close(af->fd); + af->fd = -1; + } + } +} +#endif +#if 0 +/* like strcmp(), but knows about numbers */ +except that the freshly added calls to xatoul() brf on ethernet aliases with +uClibc with e.g.: ife->name='lo' name='eth0:1' +static int nstrcmp(const char *a, const char *b) +{ + const char *a_ptr = a; + const char *b_ptr = b; + + while (*a == *b) { + if (*a == '\0') { + return 0; + } + if (!isdigit(*a) && isdigit(*(a+1))) { + a_ptr = a+1; + b_ptr = b+1; + } + a++; + b++; + } + + if (isdigit(*a) && isdigit(*b)) { + return xatoul(a_ptr) > xatoul(b_ptr) ? 1 : -1; + } + return *a - *b; +} +#endif + +static struct interface *add_interface(char *name) +{ + struct interface *ife, **nextp, *new; + + for (ife = int_last; ife; ife = ife->prev) { + int n = /*n*/strcmp(ife->name, name); + + if (n == 0) + return ife; + if (n < 0) + break; + } + + new = xzalloc(sizeof(*new)); + safe_strncpy(new->name, name, IFNAMSIZ); + nextp = ife ? &ife->next : &int_list; + new->prev = ife; + new->next = *nextp; + if (new->next) + new->next->prev = new; + else + int_last = new; + *nextp = new; + return new; +} + + +static int if_readconf(void) +{ + int numreqs = 30; + struct ifconf ifc; + struct ifreq *ifr; + int n, err = -1; + int skfd2; + + /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets + (as of 2.1.128) */ + skfd2 = get_socket_for_af(AF_INET); + if (skfd2 < 0) { + bb_perror_msg(("warning: no inet socket available")); + /* Try to soldier on with whatever socket we can get hold of. */ + skfd2 = sockets_open(0); + if (skfd2 < 0) + return -1; + } + + ifc.ifc_buf = NULL; + for (;;) { + ifc.ifc_len = sizeof(struct ifreq) * numreqs; + ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len); + + if (ioctl(skfd2, SIOCGIFCONF, &ifc) < 0) { + perror("SIOCGIFCONF"); + goto out; + } + if (ifc.ifc_len == sizeof(struct ifreq) * numreqs) { + /* assume it overflowed and try again */ + numreqs += 10; + continue; + } + break; + } + + ifr = ifc.ifc_req; + for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq)) { + add_interface(ifr->ifr_name); + ifr++; + } + err = 0; + + out: + free(ifc.ifc_buf); + return err; +} + +static char *get_name(char *name, char *p) +{ + /* Extract <name> from nul-terminated p where p matches + <name>: after leading whitespace. + If match is not made, set name empty and return unchanged p */ + int namestart=0, nameend=0; + while (isspace(p[namestart])) + namestart++; + nameend=namestart; + while (p[nameend] && p[nameend]!=':' && !isspace(p[nameend])) + nameend++; + if (p[nameend]==':') { + if ((nameend-namestart)<IFNAMSIZ) { + memcpy(name,&p[namestart],nameend-namestart); + name[nameend-namestart]='\0'; + p=&p[nameend]; + } else { + /* Interface name too large */ + name[0]='\0'; + } + } else { + /* trailing ':' not found - return empty */ + name[0]='\0'; + } + return p + 1; +} + +/* If scanf supports size qualifiers for %n conversions, then we can + * use a modified fmt that simply stores the position in the fields + * having no associated fields in the proc string. Of course, we need + * to zero them again when we're done. But that is smaller than the + * old approach of multiple scanf occurrences with large numbers of + * args. */ + +/* static const char * const ss_fmt[] = { */ +/* "%lln%llu%lu%lu%lu%lu%ln%ln%lln%llu%lu%lu%lu%lu%lu", */ +/* "%llu%llu%lu%lu%lu%lu%ln%ln%llu%llu%lu%lu%lu%lu%lu", */ +/* "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu" */ +/* }; */ + + /* Lie about the size of the int pointed to for %n. */ +#if INT_MAX == LONG_MAX +static const char * const ss_fmt[] = { + "%n%llu%u%u%u%u%n%n%n%llu%u%u%u%u%u", + "%llu%llu%u%u%u%u%n%n%llu%llu%u%u%u%u%u", + "%llu%llu%u%u%u%u%u%u%llu%llu%u%u%u%u%u%u" +}; +#else +static const char * const ss_fmt[] = { + "%n%llu%lu%lu%lu%lu%n%n%n%llu%lu%lu%lu%lu%lu", + "%llu%llu%lu%lu%lu%lu%n%n%llu%llu%lu%lu%lu%lu%lu", + "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu" +}; + +#endif + +static void get_dev_fields(char *bp, struct interface *ife, int procnetdev_vsn) +{ + memset(&ife->stats, 0, sizeof(struct user_net_device_stats)); + + sscanf(bp, ss_fmt[procnetdev_vsn], + &ife->stats.rx_bytes, /* missing for 0 */ + &ife->stats.rx_packets, + &ife->stats.rx_errors, + &ife->stats.rx_dropped, + &ife->stats.rx_fifo_errors, + &ife->stats.rx_frame_errors, + &ife->stats.rx_compressed, /* missing for <= 1 */ + &ife->stats.rx_multicast, /* missing for <= 1 */ + &ife->stats.tx_bytes, /* missing for 0 */ + &ife->stats.tx_packets, + &ife->stats.tx_errors, + &ife->stats.tx_dropped, + &ife->stats.tx_fifo_errors, + &ife->stats.collisions, + &ife->stats.tx_carrier_errors, + &ife->stats.tx_compressed /* missing for <= 1 */ + ); + + if (procnetdev_vsn <= 1) { + if (procnetdev_vsn == 0) { + ife->stats.rx_bytes = 0; + ife->stats.tx_bytes = 0; + } + ife->stats.rx_multicast = 0; + ife->stats.rx_compressed = 0; + ife->stats.tx_compressed = 0; + } +} + +static inline int procnetdev_version(char *buf) +{ + if (strstr(buf, "compressed")) + return 2; + if (strstr(buf, "bytes")) + return 1; + return 0; +} + +static int if_readlist_proc(char *target) +{ + static int proc_read; + FILE *fh; + char buf[512]; + struct interface *ife; + int err, procnetdev_vsn; + + if (proc_read) + return 0; + if (!target) + proc_read = 1; + + fh = fopen(_PATH_PROCNET_DEV, "r"); + if (!fh) { + bb_perror_msg("warning: cannot open %s, limiting output", _PATH_PROCNET_DEV); + return if_readconf(); + } + fgets(buf, sizeof buf, fh); /* eat line */ + fgets(buf, sizeof buf, fh); + + procnetdev_vsn = procnetdev_version(buf); + + err = 0; + while (fgets(buf, sizeof buf, fh)) { + char *s, name[128]; + + s = get_name(name, buf); + ife = add_interface(name); + get_dev_fields(s, ife, procnetdev_vsn); + ife->statistics_valid = 1; + if (target && !strcmp(target, name)) + break; + } + if (ferror(fh)) { + perror(_PATH_PROCNET_DEV); + err = -1; + proc_read = 0; + } + fclose(fh); + return err; +} + +static int if_readlist(void) +{ + int err = if_readlist_proc(NULL); + + if (!err) + err = if_readconf(); + return err; +} + +static int for_all_interfaces(int (*doit) (struct interface *, void *), + void *cookie) +{ + struct interface *ife; + + if (!int_list && (if_readlist() < 0)) + return -1; + for (ife = int_list; ife; ife = ife->next) { + int err = doit(ife, cookie); + + if (err) + return err; + } + return 0; +} + +/* Fetch the interface configuration from the kernel. */ +static int if_fetch(struct interface *ife) +{ + struct ifreq ifr; + int fd; + char *ifname = ife->name; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) + return -1; + ife->flags = ifr.ifr_flags; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) + memset(ife->hwaddr, 0, 32); + else + memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8); + + ife->type = ifr.ifr_hwaddr.sa_family; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMETRIC, &ifr) < 0) + ife->metric = 0; + else + ife->metric = ifr.ifr_metric; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMTU, &ifr) < 0) + ife->mtu = 0; + else + ife->mtu = ifr.ifr_mtu; + +#ifdef SIOCGIFMAP + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0) + ife->map = ifr.ifr_map; + else +#endif + memset(&ife->map, 0, sizeof(struct ifmap)); + +#ifdef HAVE_TXQUEUELEN + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) < 0) + ife->tx_queue_len = -1; /* unknown value */ + else + ife->tx_queue_len = ifr.ifr_qlen; +#else + ife->tx_queue_len = -1; /* unknown value */ +#endif + + /* IPv4 address? */ + fd = get_socket_for_af(AF_INET); + if (fd >= 0) { + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { + ife->has_ip = 1; + ife->addr = ifr.ifr_addr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFDSTADDR, &ifr) < 0) + memset(&ife->dstaddr, 0, sizeof(struct sockaddr)); + else + ife->dstaddr = ifr.ifr_dstaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFBRDADDR, &ifr) < 0) + memset(&ife->broadaddr, 0, sizeof(struct sockaddr)); + else + ife->broadaddr = ifr.ifr_broadaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) + memset(&ife->netmask, 0, sizeof(struct sockaddr)); + else + ife->netmask = ifr.ifr_netmask; + } else + memset(&ife->addr, 0, sizeof(struct sockaddr)); + } + + return 0; +} + + +static int do_if_fetch(struct interface *ife) +{ + if (if_fetch(ife) < 0) { + char *errmsg; + + if (errno == ENODEV) { + /* Give better error message for this case. */ + errmsg = "Device not found"; + } else { + errmsg = strerror(errno); + } + bb_error_msg("%s: error fetching interface information: %s", + ife->name, errmsg); + return -1; + } + return 0; +} + +/* This structure defines hardware protocols and their handlers. */ +struct hwtype { + const char * const name; + const char *title; + int type; + int alen; + char *(*print) (unsigned char *); + int (*input) (char *, struct sockaddr *); + int (*activate) (int fd); + int suppress_null_addr; +}; + +static const struct hwtype unspec_hwtype = { + .name = "unspec", + .title = "UNSPEC", + .type = -1, + .print = UNSPEC_print +}; + +static const struct hwtype loop_hwtype = { + .name = "loop", + .title = "Local Loopback", + .type = ARPHRD_LOOPBACK +}; + +#include <net/if_arp.h> + +#if (__GLIBC__ >=2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include <net/ethernet.h> +#else +#include <linux/if_ether.h> +#endif + +/* Display an Ethernet address in readable format. */ +static char *pr_ether(unsigned char *ptr) +{ + static char buff[64]; + + snprintf(buff, sizeof(buff), "%02X:%02X:%02X:%02X:%02X:%02X", + (ptr[0] & 0377), (ptr[1] & 0377), (ptr[2] & 0377), + (ptr[3] & 0377), (ptr[4] & 0377), (ptr[5] & 0377) + ); + return buff; +} + +static const struct hwtype ether_hwtype = { + .name = "ether", + .title = "Ethernet", + .type = ARPHRD_ETHER, + .alen = ETH_ALEN, + .print = pr_ether +}; + +#include <net/if_arp.h> + +static const struct hwtype ppp_hwtype = { + .name = "ppp", + .title = "Point-to-Point Protocol", + .type = ARPHRD_PPP +}; + +#ifdef CONFIG_FEATURE_IPV6 +static const struct hwtype sit_hwtype = { + .name = "sit", + .title = "IPv6-in-IPv4", + .type = ARPHRD_SIT, + .print = UNSPEC_print, + .suppress_null_addr = 1 +} ; +#endif + +static const struct hwtype * const hwtypes[] = { + &loop_hwtype, + ðer_hwtype, + &ppp_hwtype, + &unspec_hwtype, +#ifdef CONFIG_FEATURE_IPV6 + &sit_hwtype, +#endif + NULL +}; + +#ifdef IFF_PORTSEL +static const char * const if_port_text[] = { + /* Keep in step with <linux/netdevice.h> */ + "unknown", + "10base2", + "10baseT", + "AUI", + "100baseT", + "100baseTX", + "100baseFX", + NULL +}; +#endif + +/* Check our hardware type table for this type. */ +static const struct hwtype *get_hwntype(int type) +{ + const struct hwtype * const *hwp; + + hwp = hwtypes; + while (*hwp != NULL) { + if ((*hwp)->type == type) + return *hwp; + hwp++; + } + return NULL; +} + +/* return 1 if address is all zeros */ +static int hw_null_address(const struct hwtype *hw, void *ap) +{ + unsigned int i; + unsigned char *address = (unsigned char *) ap; + + for (i = 0; i < hw->alen; i++) + if (address[i]) + return 0; + return 1; +} + +static const char TRext[] = "\0\0\0Ki\0Mi\0Gi\0Ti"; + +static void print_bytes_scaled(unsigned long long ull, const char *end) +{ + unsigned long long int_part; + const char *ext; + unsigned int frac_part; + int i; + + frac_part = 0; + ext = TRext; + int_part = ull; + i = 4; + do { + if (int_part >= 1024) { + frac_part = ((((unsigned int) int_part) & (1024-1)) * 10) / 1024; + int_part /= 1024; + ext += 3; /* KiB, MiB, GiB, TiB */ + } + --i; + } while (i); + + printf("X bytes:%llu (%llu.%u %sB)%s", ull, int_part, frac_part, ext, end); +} + +static const char * const ife_print_flags_strs[] = { + "UP ", + "BROADCAST ", + "DEBUG ", + "LOOPBACK ", + "POINTOPOINT ", + "NOTRAILERS ", + "RUNNING ", + "NOARP ", + "PROMISC ", + "ALLMULTI ", + "SLAVE ", + "MASTER ", + "MULTICAST ", +#ifdef HAVE_DYNAMIC + "DYNAMIC " +#endif +}; + +static const unsigned short ife_print_flags_mask[] = { + IFF_UP, + IFF_BROADCAST, + IFF_DEBUG, + IFF_LOOPBACK, + IFF_POINTOPOINT, + IFF_NOTRAILERS, + IFF_RUNNING, + IFF_NOARP, + IFF_PROMISC, + IFF_ALLMULTI, + IFF_SLAVE, + IFF_MASTER, + IFF_MULTICAST, +#ifdef HAVE_DYNAMIC + IFF_DYNAMIC +#endif + 0 +}; + +static void ife_print(struct interface *ptr) +{ + struct aftype *ap; + const struct hwtype *hw; + int hf; + int can_compress = 0; + +#ifdef HAVE_AFINET6 + FILE *f; + char addr6[40], devname[20]; + struct sockaddr_in6 sap; + int plen, scope, dad_status, if_idx; + char addr6p[8][5]; +#endif + + ap = get_afntype(ptr->addr.sa_family); + if (ap == NULL) + ap = get_afntype(0); + + hf = ptr->type; + + if (hf == ARPHRD_CSLIP || hf == ARPHRD_CSLIP6) + can_compress = 1; + + hw = get_hwntype(hf); + if (hw == NULL) + hw = get_hwntype(-1); + + printf("%-9.9s Link encap:%s ", ptr->name, hw->title); + /* For some hardware types (eg Ash, ATM) we don't print the + hardware address if it's null. */ + if (hw->print != NULL && (!(hw_null_address(hw, ptr->hwaddr) && + hw->suppress_null_addr))) + printf("HWaddr %s ", hw->print((unsigned char *)ptr->hwaddr)); +#ifdef IFF_PORTSEL + if (ptr->flags & IFF_PORTSEL) { + printf("Media:%s", if_port_text[ptr->map.port] /* [0] */); + if (ptr->flags & IFF_AUTOMEDIA) + printf("(auto)"); + } +#endif + puts(""); + + if (ptr->has_ip) { + printf(" %s addr:%s ", ap->name, + ap->sprint(&ptr->addr, 1)); + if (ptr->flags & IFF_POINTOPOINT) { + printf(" P-t-P:%s ", ap->sprint(&ptr->dstaddr, 1)); + } + if (ptr->flags & IFF_BROADCAST) { + printf(" Bcast:%s ", ap->sprint(&ptr->broadaddr, 1)); + } + printf(" Mask:%s\n", ap->sprint(&ptr->netmask, 1)); + } + +#ifdef HAVE_AFINET6 + +#define IPV6_ADDR_ANY 0x0000U + +#define IPV6_ADDR_UNICAST 0x0001U +#define IPV6_ADDR_MULTICAST 0x0002U +#define IPV6_ADDR_ANYCAST 0x0004U + +#define IPV6_ADDR_LOOPBACK 0x0010U +#define IPV6_ADDR_LINKLOCAL 0x0020U +#define IPV6_ADDR_SITELOCAL 0x0040U + +#define IPV6_ADDR_COMPATv4 0x0080U + +#define IPV6_ADDR_SCOPE_MASK 0x00f0U + +#define IPV6_ADDR_MAPPED 0x1000U +#define IPV6_ADDR_RESERVED 0x2000U /* reserved address space */ + + if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) { + while (fscanf + (f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], + addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope, + &dad_status, devname) != EOF) { + if (!strcmp(devname, ptr->name)) { + sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7]); + inet_pton(AF_INET6, addr6, + (struct sockaddr *) &sap.sin6_addr); + sap.sin6_family = AF_INET6; + printf(" inet6 addr: %s/%d", + inet6_aftype.sprint((struct sockaddr *) &sap, 1), + plen); + printf(" Scope:"); + switch (scope & IPV6_ADDR_SCOPE_MASK) { + case 0: + printf("Global"); + break; + case IPV6_ADDR_LINKLOCAL: + printf("Link"); + break; + case IPV6_ADDR_SITELOCAL: + printf("Site"); + break; + case IPV6_ADDR_COMPATv4: + printf("Compat"); + break; + case IPV6_ADDR_LOOPBACK: + printf("Host"); + break; + default: + printf("Unknown"); + } + puts(""); + } + } + fclose(f); + } +#endif + + printf(" "); + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */ + + if (ptr->flags == 0) { + printf("[NO FLAGS] "); + } else { + int i = 0; + do { + if (ptr->flags & ife_print_flags_mask[i]) { + printf(ife_print_flags_strs[i]); + } + } while (ife_print_flags_mask[++i]); + } + + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short */ + printf(" MTU:%d Metric:%d", ptr->mtu, ptr->metric ? ptr->metric : 1); +#ifdef SIOCSKEEPALIVE + if (ptr->outfill || ptr->keepalive) + printf(" Outfill:%d Keepalive:%d", ptr->outfill, ptr->keepalive); +#endif + puts(""); + + /* If needed, display the interface statistics. */ + + if (ptr->statistics_valid) { + /* XXX: statistics are currently only printed for the primary address, + * not for the aliases, although strictly speaking they're shared + * by all addresses. + */ + printf(" "); + + printf("RX packets:%llu errors:%lu dropped:%lu overruns:%lu frame:%lu\n", + ptr->stats.rx_packets, ptr->stats.rx_errors, + ptr->stats.rx_dropped, ptr->stats.rx_fifo_errors, + ptr->stats.rx_frame_errors); + if (can_compress) + printf(" compressed:%lu\n", + ptr->stats.rx_compressed); + printf(" "); + printf("TX packets:%llu errors:%lu dropped:%lu overruns:%lu carrier:%lu\n", + ptr->stats.tx_packets, ptr->stats.tx_errors, + ptr->stats.tx_dropped, ptr->stats.tx_fifo_errors, + ptr->stats.tx_carrier_errors); + printf(" collisions:%lu ", ptr->stats.collisions); + if (can_compress) + printf("compressed:%lu ", ptr->stats.tx_compressed); + if (ptr->tx_queue_len != -1) + printf("txqueuelen:%d ", ptr->tx_queue_len); + printf("\n R"); + print_bytes_scaled(ptr->stats.rx_bytes, " T"); + print_bytes_scaled(ptr->stats.tx_bytes, "\n"); + + } + + if ((ptr->map.irq || ptr->map.mem_start || ptr->map.dma || + ptr->map.base_addr)) { + printf(" "); + if (ptr->map.irq) + printf("Interrupt:%d ", ptr->map.irq); + if (ptr->map.base_addr >= 0x100) /* Only print devices using it for + I/O maps */ + printf("Base address:0x%lx ", + (unsigned long) ptr->map.base_addr); + if (ptr->map.mem_start) { + printf("Memory:%lx-%lx ", ptr->map.mem_start, + ptr->map.mem_end); + } + if (ptr->map.dma) + printf("DMA chan:%x ", ptr->map.dma); + puts(""); + } + puts(""); +} + + +static int do_if_print(struct interface *ife, void *cookie) +{ + int *opt_a = (int *) cookie; + int res; + + res = do_if_fetch(ife); + if (res >= 0) { + if ((ife->flags & IFF_UP) || *opt_a) + ife_print(ife); + } + return res; +} + +static struct interface *lookup_interface(char *name) +{ + struct interface *ife = NULL; + + if (if_readlist_proc(name) < 0) + return NULL; + ife = add_interface(name); + return ife; +} + +/* for ipv4 add/del modes */ +static int if_print(char *ifname) +{ + int res; + + if (!ifname) { + res = for_all_interfaces(do_if_print, &interface_opt_a); + } else { + struct interface *ife; + + ife = lookup_interface(ifname); + res = do_if_fetch(ife); + if (res >= 0) + ife_print(ife); + } + return res; +} + +int display_interfaces(char *ifname); +int display_interfaces(char *ifname) +{ + int status; + + /* Create a channel to the NET kernel. */ + if ((skfd = sockets_open(0)) < 0) { + bb_perror_msg_and_die("socket"); + } + + /* Do we have to show the current setup? */ + status = if_print(ifname); +#ifdef CONFIG_FEATURE_CLEAN_UP + sockets_close(); +#endif + exit(status < 0); +} diff --git a/networking/ip.c b/networking/ip.c new file mode 100644 index 0000000..4c8b89e --- /dev/null +++ b/networking/ip.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "busybox.h" + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +int ip_main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + + ip_parse_common_args(&argc, &argv); + + if (argc > 1) { + if (ENABLE_FEATURE_IP_ADDRESS && matches(argv[1], "address") == 0) { + ret = do_ipaddr(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_ROUTE && matches(argv[1], "route") == 0) { + ret = do_iproute(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_LINK && matches(argv[1], "link") == 0) { + ret = do_iplink(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_TUNNEL && + (matches(argv[1], "tunnel") == 0 || strcmp(argv[1], "tunl") == 0)) { + ret = do_iptunnel(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_RULE && matches(argv[1], "rule") == 0) { + ret = do_iprule(argc-2, argv+2); + } + } + if (ret) { + bb_show_usage(); + } + return EXIT_SUCCESS; +} diff --git a/networking/ipaddr.c b/networking/ipaddr.c new file mode 100644 index 0000000..c907808 --- /dev/null +++ b/networking/ipaddr.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int ipaddr_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_ipaddr(argc-1, argv+1); +} diff --git a/networking/ipcalc.c b/networking/ipcalc.c new file mode 100644 index 0000000..0ee9646 --- /dev/null +++ b/networking/ipcalc.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ipcalc implementation for busybox + * + * By Jordan Crouse <jordan@cosmicpenguin.net> + * Stephan Linz <linz@li-pro.net> + * + * This is a complete reimplementation of the ipcalc program + * from Red Hat. I didn't look at their source code, but there + * is no denying that this is a loving reimplementation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <ctype.h> +#include <getopt.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#define CLASS_A_NETMASK ntohl(0xFF000000) +#define CLASS_B_NETMASK ntohl(0xFFFF0000) +#define CLASS_C_NETMASK ntohl(0xFFFFFF00) + +static unsigned long get_netmask(unsigned long ipaddr) +{ + ipaddr = htonl(ipaddr); + + if ((ipaddr & 0xC0000000) == 0xC0000000) + return CLASS_C_NETMASK; + else if ((ipaddr & 0x80000000) == 0x80000000) + return CLASS_B_NETMASK; + else if ((ipaddr & 0x80000000) == 0) + return CLASS_A_NETMASK; + else + return 0; +} + +#ifdef CONFIG_FEATURE_IPCALC_FANCY +static int get_prefix(unsigned long netmask) +{ + unsigned long msk = 0x80000000; + int ret = 0; + + netmask = htonl(netmask); + while(msk) { + if (netmask & msk) + ret++; + msk >>= 1; + } + return ret; +} +#else +int get_prefix(unsigned long netmask); +#endif + + +#define NETMASK 0x01 +#define BROADCAST 0x02 +#define NETWORK 0x04 +#define NETPREFIX 0x08 +#define HOSTNAME 0x10 +#define SILENT 0x20 + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + static const struct option long_options[] = { + { "netmask", no_argument, NULL, 'm' }, + { "broadcast", no_argument, NULL, 'b' }, + { "network", no_argument, NULL, 'n' }, +# if ENABLE_FEATURE_IPCALC_FANCY + { "prefix", no_argument, NULL, 'p' }, + { "hostname", no_argument, NULL, 'h' }, + { "silent", no_argument, NULL, 's' }, +# endif + { NULL, 0, NULL, 0 } + }; +#endif + +int ipcalc_main(int argc, char **argv) +{ + unsigned opt; + int have_netmask = 0; + in_addr_t netmask, broadcast, network, ipaddr; + struct in_addr a; + char *ipstr; + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + applet_long_options = long_options; +#endif + opt = getopt32(argc, argv, "mbn" USE_FEATURE_IPCALC_FANCY("phs")); + argc -= optind; + argv += optind; + if (opt & (BROADCAST | NETWORK | NETPREFIX)) { + if (argc > 2 || argc <= 0) + bb_show_usage(); + } else { + if (argc != 1) + bb_show_usage(); + } + if (opt & SILENT) + logmode = LOGMODE_NONE; /* Suppress error_msg() output */ + + ipstr = argv[0]; + if (ENABLE_FEATURE_IPCALC_FANCY) { + unsigned long netprefix = 0; + char *prefixstr; + + prefixstr = ipstr; + + while (*prefixstr) { + if (*prefixstr == '/') { + *prefixstr = (char)0; + prefixstr++; + if (*prefixstr) { + unsigned msk; + netprefix = xatoul_range(prefixstr, 0, 32); + netmask = 0; + msk = 0x80000000; + while (netprefix > 0) { + netmask |= msk; + msk >>= 1; + netprefix--; + } + netmask = htonl(netmask); + /* Even if it was 0, we will signify that we have a netmask. This allows */ + /* for specification of default routes, etc which have a 0 netmask/prefix */ + have_netmask = 1; + } + break; + } + prefixstr++; + } + } + ipaddr = inet_aton(ipstr, &a); + + if (ipaddr == 0) { + bb_error_msg_and_die("bad IP address: %s", argv[0]); + } + ipaddr = a.s_addr; + + if (argc == 2) { + if (ENABLE_FEATURE_IPCALC_FANCY && have_netmask) { + bb_error_msg_and_die("use prefix or netmask, not both"); + } + + netmask = inet_aton(argv[1], &a); + if (netmask == 0) { + bb_error_msg_and_die("bad netmask: %s", argv[1]); + } + netmask = a.s_addr; + } else { + + /* JHC - If the netmask wasn't provided then calculate it */ + if (!ENABLE_FEATURE_IPCALC_FANCY || !have_netmask) + netmask = get_netmask(ipaddr); + } + + if (opt & NETMASK) { + printf("NETMASK=%s\n", inet_ntoa((*(struct in_addr *) &netmask))); + } + + if (opt & BROADCAST) { + broadcast = (ipaddr & netmask) | ~netmask; + printf("BROADCAST=%s\n", inet_ntoa((*(struct in_addr *) &broadcast))); + } + + if (opt & NETWORK) { + network = ipaddr & netmask; + printf("NETWORK=%s\n", inet_ntoa((*(struct in_addr *) &network))); + } + + if (ENABLE_FEATURE_IPCALC_FANCY) { + if (opt & NETPREFIX) { + printf("PREFIX=%i\n", get_prefix(netmask)); + } + + if (opt & HOSTNAME) { + struct hostent *hostinfo; + int x; + + hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET); + if (!hostinfo) { + bb_herror_msg_and_die("cannot find hostname for %s", argv[0]); + } + for (x = 0; hostinfo->h_name[x]; x++) { + hostinfo->h_name[x] = tolower(hostinfo->h_name[x]); + } + + printf("HOSTNAME=%s\n", hostinfo->h_name); + } + } + + return EXIT_SUCCESS; +} diff --git a/networking/iplink.c b/networking/iplink.c new file mode 100644 index 0000000..4a28c74 --- /dev/null +++ b/networking/iplink.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iplink_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iplink(argc-1, argv+1); +} diff --git a/networking/iproute.c b/networking/iproute.c new file mode 100644 index 0000000..62335a8 --- /dev/null +++ b/networking/iproute.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iproute_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iproute(argc-1, argv+1); +} diff --git a/networking/iprule.c b/networking/iprule.c new file mode 100644 index 0000000..43eaea6 --- /dev/null +++ b/networking/iprule.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iprule_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iprule(argc-1, argv+1); +} diff --git a/networking/iptunnel.c b/networking/iptunnel.c new file mode 100644 index 0000000..9ae734a --- /dev/null +++ b/networking/iptunnel.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iptunnel_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iptunnel(argc-1, argv+1); +} diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild new file mode 100644 index 0000000..8383630 --- /dev/null +++ b/networking/libiproute/Kbuild @@ -0,0 +1,66 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_IP) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + iproute.o \ + iptunnel.o \ + iprule.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_proto.o \ + ll_types.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPADDR) += \ + ip_parse_common_args.o \ + ipaddress.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPLINK) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPROUTE) += \ + ip_parse_common_args.o \ + iproute.o \ + libnetlink.o \ + ll_map.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPTUNNEL) += \ + ip_parse_common_args.o \ + iptunnel.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPRULE) += \ + ip_parse_common_args.o \ + iprule.o \ + rt_names.o \ + utils.o + diff --git a/networking/libiproute/ip_common.h b/networking/libiproute/ip_common.h new file mode 100644 index 0000000..1529178 --- /dev/null +++ b/networking/libiproute/ip_common.h @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _IP_COMMON_H +#define _IP_COMMON_H 1 + +#include "busybox.h" +#include <asm/types.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#if !defined IFA_RTA +#include <linux/if_addr.h> +#endif +#if !defined IFLA_RTA +#include <linux/if_link.h> +#endif + +extern int preferred_family; +extern char * _SL_; + +extern void ip_parse_common_args(int *argcp, char ***argvp); +extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ipaddr_list_or_flush(int argc, char **argv, int flush); +extern int iproute_monitor(int argc, char **argv); +extern void iplink_usage(void) ATTRIBUTE_NORETURN; +extern void ipneigh_reset_filter(void); +extern int do_ipaddr(int argc, char **argv); +extern int do_iproute(int argc, char **argv); +extern int do_iprule(int argc, char **argv); +extern int do_ipneigh(int argc, char **argv); +extern int do_iptunnel(int argc, char **argv); +extern int do_iplink(int argc, char **argv); +extern int do_ipmonitor(int argc, char **argv); +extern int do_multiaddr(int argc, char **argv); +extern int do_multiroute(int argc, char **argv); +#endif /* ip_common.h */ diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c new file mode 100644 index 0000000..fee6e5e --- /dev/null +++ b/networking/libiproute/ip_parse_common_args.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include <string.h> + +#include "libbb.h" +#include "utils.h" +#include "ip_common.h" + + +int preferred_family = AF_UNSPEC; +int oneline = 0; +char * _SL_ = NULL; + +void ip_parse_common_args(int *argcp, char ***argvp) +{ + int argc = *argcp; + char **argv = *argvp; + + while (argc > 1) { + char *opt = argv[1]; + + if (strcmp(opt,"--") == 0) { + argc--; argv++; + break; + } + + if (opt[0] != '-') + break; + + if (opt[1] == '-') + opt++; + + if (matches(opt, "-family") == 0) { + argc--; + argv++; + if (!argv[1]) + bb_show_usage(); + if (strcmp(argv[1], "inet") == 0) + preferred_family = AF_INET; + else if (strcmp(argv[1], "inet6") == 0) + preferred_family = AF_INET6; + else if (strcmp(argv[1], "link") == 0) + preferred_family = AF_PACKET; + else + invarg(argv[1], "protocol family"); + } else if (strcmp(opt, "-4") == 0) { + preferred_family = AF_INET; + } else if (strcmp(opt, "-6") == 0) { + preferred_family = AF_INET6; + } else if (strcmp(opt, "-0") == 0) { + preferred_family = AF_PACKET; + } else if (matches(opt, "-oneline") == 0) { + ++oneline; + } else { + bb_show_usage(); + } + argc--; argv++; + } + _SL_ = oneline ? "\\" : "\n" ; + *argcp = argc; + *argvp = argv; +} diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c new file mode 100644 index 0000000..2a267fe --- /dev/null +++ b/networking/libiproute/ipaddress.c @@ -0,0 +1,824 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipaddress.c "ip address". + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * Changes: + * Laszlo Valko <valko@linux.karinthy.hu> 990223: address label must be zero terminated + */ + +#include "libbb.h" +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <fnmatch.h> +#include <string.h> +#include <unistd.h> + +#include <net/if.h> +#include <net/if_arp.h> + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static struct +{ + int ifindex; + int family; + int oneline; + int showqueue; + inet_prefix pfx; + int scope, scopemask; + int flags, flagmask; + int up; + char *label; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; +} filter; + +static void print_link_flags(FILE *fp, unsigned flags, unsigned mdown) +{ + fprintf(fp, "<"); + flags &= ~IFF_RUNNING; +#define _PF(f) if (flags&IFF_##f) { \ + flags &= ~IFF_##f ; \ + fprintf(fp, #f "%s", flags ? "," : ""); } + _PF(LOOPBACK); + _PF(BROADCAST); + _PF(POINTOPOINT); + _PF(MULTICAST); + _PF(NOARP); +#if 0 + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); +#endif + _PF(UP); +#undef _PF + if (flags) + fprintf(fp, "%x", flags); + if (mdown) + fprintf(fp, ",M-DOWN"); + fprintf(fp, "> "); +} + +static void print_queuelen(char *name) +{ + struct ifreq ifr; + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + return; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) { + perror("SIOCGIFXQLEN"); + close(s); + return; + } + close(s); + + if (ifr.ifr_qlen) + printf("qlen %d", ifr.ifr_qlen); +} + +static int print_linkinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + const struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX+1]; + int len = n->nlmsg_len; + unsigned m_flag = 0; + + if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) + return -1; + + if (filter.ifindex && ifi->ifi_index != filter.ifindex) + return 0; + if (filter.up && !(ifi->ifi_flags&IFF_UP)) + return 0; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + if (tb[IFLA_IFNAME] == NULL) { + bb_error_msg("nil ifname"); + return -1; + } + if (filter.label && + (!filter.family || filter.family == AF_PACKET) && + fnmatch(filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)) + return 0; + + if (n->nlmsg_type == RTM_DELLINK) + fprintf(fp, "Deleted "); + + fprintf(fp, "%d: %s", ifi->ifi_index, + tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : "<nil>"); + + if (tb[IFLA_LINK]) { + SPRINT_BUF(b1); + int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]); + if (iflink == 0) + fprintf(fp, "@NONE: "); + else { + fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1)); + m_flag = ll_index_to_flags(iflink); + m_flag = !(m_flag & IFF_UP); + } + } else { + fprintf(fp, ": "); + } + print_link_flags(fp, ifi->ifi_flags, m_flag); + + if (tb[IFLA_MTU]) + fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + if (tb[IFLA_QDISC]) + fprintf(fp, "qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC])); +#ifdef IFLA_MASTER + if (tb[IFLA_MASTER]) { + SPRINT_BUF(b1); + fprintf(fp, "master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); + } +#endif + if (filter.showqueue) + print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME])); + + if (!filter.family || filter.family == AF_PACKET) { + SPRINT_BUF(b1); + fprintf(fp, "%s", _SL_); + fprintf(fp, " link/%s ", ll_type_n2a(ifi->ifi_type, b1, sizeof(b1))); + + if (tb[IFLA_ADDRESS]) { + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]), + RTA_PAYLOAD(tb[IFLA_ADDRESS]), + ifi->ifi_type, + b1, sizeof(b1))); + } + if (tb[IFLA_BROADCAST]) { + if (ifi->ifi_flags&IFF_POINTOPOINT) + fprintf(fp, " peer "); + else + fprintf(fp, " brd "); + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]), + RTA_PAYLOAD(tb[IFLA_BROADCAST]), + ifi->ifi_type, + b1, sizeof(b1))); + } + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_addrinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * rta_tb[IFA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR) + return 0; + len -= NLMSG_LENGTH(sizeof(*ifa)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (filter.flushb && n->nlmsg_type != RTM_NEWADDR) + return 0; + + memset(rta_tb, 0, sizeof(rta_tb)); + parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); + + if (!rta_tb[IFA_LOCAL]) + rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; + if (!rta_tb[IFA_ADDRESS]) + rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL]; + + if (filter.ifindex && filter.ifindex != ifa->ifa_index) + return 0; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + return 0; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + return 0; + if (filter.label) { + const char *label; + if (rta_tb[IFA_LABEL]) + label = RTA_DATA(rta_tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + return 0; + } + if (filter.pfx.family) { + if (rta_tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + return 0; + } + } + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELADDR; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELADDR) + fprintf(fp, "Deleted "); + + if (filter.oneline) + fprintf(fp, "%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index)); + if (ifa->ifa_family == AF_INET) + fprintf(fp, " inet "); + else if (ifa->ifa_family == AF_INET6) + fprintf(fp, " inet6 "); + else + fprintf(fp, " family %d ", ifa->ifa_family); + + if (rta_tb[IFA_LOCAL]) { + fprintf(fp, "%s", rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_LOCAL]), + RTA_DATA(rta_tb[IFA_LOCAL]), + abuf, sizeof(abuf))); + + if (rta_tb[IFA_ADDRESS] == NULL || + memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) { + fprintf(fp, "/%d ", ifa->ifa_prefixlen); + } else { + fprintf(fp, " peer %s/%d ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ADDRESS]), + RTA_DATA(rta_tb[IFA_ADDRESS]), + abuf, sizeof(abuf)), + ifa->ifa_prefixlen); + } + } + + if (rta_tb[IFA_BROADCAST]) { + fprintf(fp, "brd %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_BROADCAST]), + RTA_DATA(rta_tb[IFA_BROADCAST]), + abuf, sizeof(abuf))); + } + if (rta_tb[IFA_ANYCAST]) { + fprintf(fp, "any %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ANYCAST]), + RTA_DATA(rta_tb[IFA_ANYCAST]), + abuf, sizeof(abuf))); + } + fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1))); + if (ifa->ifa_flags&IFA_F_SECONDARY) { + ifa->ifa_flags &= ~IFA_F_SECONDARY; + fprintf(fp, "secondary "); + } + if (ifa->ifa_flags&IFA_F_TENTATIVE) { + ifa->ifa_flags &= ~IFA_F_TENTATIVE; + fprintf(fp, "tentative "); + } + if (ifa->ifa_flags&IFA_F_DEPRECATED) { + ifa->ifa_flags &= ~IFA_F_DEPRECATED; + fprintf(fp, "deprecated "); + } + if (!(ifa->ifa_flags&IFA_F_PERMANENT)) { + fprintf(fp, "dynamic "); + } else + ifa->ifa_flags &= ~IFA_F_PERMANENT; + if (ifa->ifa_flags) + fprintf(fp, "flags %02x ", ifa->ifa_flags); + if (rta_tb[IFA_LABEL]) + fprintf(fp, "%s", (char*)RTA_DATA(rta_tb[IFA_LABEL])); + if (rta_tb[IFA_CACHEINFO]) { + struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]); + char buf[128]; + fprintf(fp, "%s", _SL_); + if (ci->ifa_valid == 0xFFFFFFFFU) + sprintf(buf, "valid_lft forever"); + else + sprintf(buf, "valid_lft %dsec", ci->ifa_valid); + if (ci->ifa_prefered == 0xFFFFFFFFU) + sprintf(buf+strlen(buf), " preferred_lft forever"); + else + sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered); + fprintf(fp, " %s", buf); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + + +struct nlmsg_list +{ + struct nlmsg_list *next; + struct nlmsghdr h; +}; + +static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo, FILE *fp) +{ + for ( ;ainfo ; ainfo = ainfo->next) { + struct nlmsghdr *n = &ainfo->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (n->nlmsg_type != RTM_NEWADDR) + continue; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa))) + return -1; + + if (ifa->ifa_index != ifindex || + (filter.family && filter.family != ifa->ifa_family)) + continue; + + print_addrinfo(NULL, n, fp); + } + return 0; +} + + +static int store_nlmsg(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + struct nlmsg_list **linfo = (struct nlmsg_list**)arg; + struct nlmsg_list *h; + struct nlmsg_list **lp; + + h = malloc(n->nlmsg_len+sizeof(void*)); + if (h == NULL) + return -1; + + memcpy(&h->h, n, n->nlmsg_len); + h->next = NULL; + + for (lp = linfo; *lp; lp = &(*lp)->next) /* NOTHING */; + *lp = h; + + ll_remember_index(who, n, NULL); + return 0; +} + +static void ipaddr_reset_filter(int _oneline) +{ + memset(&filter, 0, sizeof(filter)); + filter.oneline = _oneline; +} + +int ipaddr_list_or_flush(int argc, char **argv, int flush) +{ + static const char *const option[] = { "to", "scope", "up", "label", "dev", 0 }; + + struct nlmsg_list *linfo = NULL; + struct nlmsg_list *ainfo = NULL; + struct nlmsg_list *l; + struct rtnl_handle rth; + char *filter_dev = NULL; + int no_link = 0; + + ipaddr_reset_filter(oneline); + filter.showqueue = 1; + + if (filter.family == AF_UNSPEC) + filter.family = preferred_family; + + if (flush) { + if (argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "flush"); + return -1; + } + if (filter.family == AF_PACKET) { + bb_error_msg("cannot flush link addresses"); + return -1; + } + } + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* to */ + NEXT_ARG(); + get_prefix(&filter.pfx, *argv, filter.family); + if (filter.family == AF_UNSPEC) { + filter.family = filter.pfx.family; + } + break; + case 1: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + filter.scopemask = -1; + if (rtnl_rtscope_a2n(&scope, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "scope"); + } + scope = RT_SCOPE_NOWHERE; + filter.scopemask = 0; + } + filter.scope = scope; + break; + } + case 2: /* up */ + filter.up = 1; + break; + case 3: /* label */ + NEXT_ARG(); + filter.label = *argv; + break; + case 4: /* dev */ + NEXT_ARG(); + default: + if (filter_dev) { + duparg2("dev", *argv); + } + filter_dev = *argv; + } + argv++; + argc--; + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &linfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + if (filter_dev) { + filter.ifindex = ll_name_to_index(filter_dev); + if (filter.ifindex <= 0) { + bb_error_msg("device \"%s\" does not exist", filter_dev); + return -1; + } + } + + if (flush) { + char flushb[4096-512]; + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + perror("Cannot send dump request"); + exit(1); + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_addrinfo, stdout, NULL, NULL) < 0) { + fprintf(stderr, "Flush terminated\n"); + exit(1); + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.family != AF_PACKET) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &ainfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + } + + + if (filter.family && filter.family != AF_PACKET) { + struct nlmsg_list **lp; + lp=&linfo; + + if (filter.oneline) + no_link = 1; + + while ((l=*lp)!=NULL) { + int ok = 0; + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + struct nlmsg_list *a; + + for (a=ainfo; a; a=a->next) { + struct nlmsghdr *n = &a->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (ifa->ifa_index != ifi->ifi_index || + (filter.family && filter.family != ifa->ifa_family)) + continue; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + continue; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + continue; + if (filter.pfx.family || filter.label) { + struct rtattr *tb[IFA_MAX+1]; + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n)); + if (!tb[IFA_LOCAL]) + tb[IFA_LOCAL] = tb[IFA_ADDRESS]; + + if (filter.pfx.family && tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + continue; + } + if (filter.label) { + SPRINT_BUF(b1); + const char *label; + if (tb[IFA_LABEL]) + label = RTA_DATA(tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + continue; + } + } + + ok = 1; + break; + } + if (!ok) + *lp = l->next; + else + lp = &l->next; + } + } + + for (l=linfo; l; l = l->next) { + if (no_link || print_linkinfo(NULL, &l->h, stdout) == 0) { + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + if (filter.family != AF_PACKET) + print_selected_addrinfo(ifi->ifi_index, ainfo, stdout); + } + fflush(stdout); + } + + exit(0); +} + +static int default_scope(inet_prefix *lcl) +{ + if (lcl->family == AF_INET) { + if (lcl->bytelen >= 1 && *(__u8*)&lcl->data == 127) + return RT_SCOPE_HOST; + } + return 0; +} + +static int ipaddr_modify(int cmd, int argc, char **argv) +{ + static const char *const option[] = { + "peer", "remote", "broadcast", "brd", + "anycast", "scope", "dev", "label", "local", 0 + }; + + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct ifaddrmsg ifa; + char buf[256]; + } req; + char *d = NULL; + char *l = NULL; + inet_prefix lcl; + inet_prefix peer; + int local_len = 0; + int peer_len = 0; + int brd_len = 0; + int any_len = 0; + int scoped = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = cmd; + req.ifa.ifa_family = preferred_family; + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* peer */ + case 1: /* remote */ + NEXT_ARG(); + + if (peer_len) { + duparg("peer", *argv); + } + get_prefix(&peer, *argv, req.ifa.ifa_family); + peer_len = peer.bytelen; + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = peer.family; + } + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen); + req.ifa.ifa_prefixlen = peer.bitlen; + break; + case 2: /* broadcast */ + case 3: /* brd */ + { + inet_prefix addr; + NEXT_ARG(); + if (brd_len) { + duparg("broadcast", *argv); + } + if (strcmp(*argv, "+") == 0) { + brd_len = -1; + } + else if (strcmp(*argv, "-") == 0) { + brd_len = -2; + } else { + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) + req.ifa.ifa_family = addr.family; + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen); + brd_len = addr.bytelen; + } + break; + } + case 4: /* anycast */ + { + inet_prefix addr; + NEXT_ARG(); + if (any_len) { + duparg("anycast", *argv); + } + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = addr.family; + } + addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen); + any_len = addr.bytelen; + break; + } + case 5: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + if (rtnl_rtscope_a2n(&scope, *argv)) { + invarg(*argv, "scope"); + } + req.ifa.ifa_scope = scope; + scoped = 1; + break; + } + case 6: /* dev */ + NEXT_ARG(); + d = *argv; + break; + case 7: /* label */ + NEXT_ARG(); + l = *argv; + addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1); + break; + case 8: /* local */ + NEXT_ARG(); + default: + if (local_len) { + duparg2("local", *argv); + } + get_prefix(&lcl, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = lcl.family; + } + addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen); + local_len = lcl.bytelen; + } + argc--; + argv++; + } + + if (d == NULL) { + bb_error_msg(bb_msg_requires_arg,"\"dev\""); + return -1; + } + if (l && matches(d, l) != 0) { + bb_error_msg_and_die("\"dev\" (%s) must match \"label\" (%s)", d, l); + } + + if (peer_len == 0 && local_len && cmd != RTM_DELADDR) { + peer = lcl; + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen); + } + if (req.ifa.ifa_prefixlen == 0) + req.ifa.ifa_prefixlen = lcl.bitlen; + + if (brd_len < 0 && cmd != RTM_DELADDR) { + inet_prefix brd; + int i; + if (req.ifa.ifa_family != AF_INET) { + bb_error_msg("broadcast can be set only for IPv4 addresses"); + return -1; + } + brd = peer; + if (brd.bitlen <= 30) { + for (i=31; i>=brd.bitlen; i--) { + if (brd_len == -1) + brd.data[0] |= htonl(1<<(31-i)); + else + brd.data[0] &= ~htonl(1<<(31-i)); + } + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen); + brd_len = brd.bytelen; + } + } + if (!scoped && cmd != RTM_DELADDR) + req.ifa.ifa_scope = default_scope(&lcl); + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if ((req.ifa.ifa_index = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + exit(2); + + exit(0); +} + +int do_ipaddr(int argc, char **argv) +{ + static const char *const commands[] = { + "add", "delete", "list", "show", "lst", "flush", 0 + }; + + int command_num = 2; + + if (*argv) { + command_num = index_in_substr_array(commands, *argv); + } + switch (command_num) { + case 0: /* add */ + return ipaddr_modify(RTM_NEWADDR, argc-1, argv+1); + case 1: /* delete */ + return ipaddr_modify(RTM_DELADDR, argc-1, argv+1); + case 2: /* list */ + case 3: /* show */ + case 4: /* lst */ + return ipaddr_list_or_flush(argc-1, argv+1, 0); + case 5: /* flush */ + return ipaddr_list_or_flush(argc-1, argv+1, 1); + } + bb_error_msg_and_die("unknown command %s", *argv); +} diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c new file mode 100644 index 0000000..1ea11f6 --- /dev/null +++ b/networking/libiproute/iplink.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * iplink.c "ip link". + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <net/if.h> +#include <net/if_packet.h> +#include <netpacket/packet.h> + +#include <net/ethernet.h> + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +static int on_off(char *msg) +{ + bb_error_msg("error: argument of \"%s\" must be \"on\" or \"off\"", msg); + return -1; +} + +static int get_ctl_fd(void) +{ + int s_errno; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + s_errno = errno; + fd = socket(PF_PACKET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + fd = socket(PF_INET6, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + errno = s_errno; + perror("Cannot create control socket"); + return -1; +} + +static int do_chflags(char *dev, __u32 flags, __u32 mask) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (err) { + perror("SIOCGIFFLAGS"); + close(fd); + return -1; + } + if ((ifr.ifr_flags^flags)&mask) { + ifr.ifr_flags &= ~mask; + ifr.ifr_flags |= mask&flags; + err = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (err) + perror("SIOCSIFFLAGS"); + } + close(fd); + return err; +} + +static int do_changename(char *dev, char *newdev) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCSIFNAME, &ifr); + if (err) { + perror("SIOCSIFNAME"); + close(fd); + return -1; + } + close(fd); + return err; +} + +static int set_qlen(char *dev, int qlen) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_qlen = qlen; + if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) { + perror("SIOCSIFXQLEN"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int set_mtu(char *dev, int mtu) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + if (ioctl(s, SIOCSIFMTU, &ifr) < 0) { + perror("SIOCSIFMTU"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int get_address(char *dev, int *htype) +{ + struct ifreq ifr; + struct sockaddr_ll me; + socklen_t alen; + int s; + + s = socket(PF_PACKET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket(PF_PACKET)"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + perror("SIOCGIFINDEX"); + close(s); + return -1; + } + + memset(&me, 0, sizeof(me)); + me.sll_family = AF_PACKET; + me.sll_ifindex = ifr.ifr_ifindex; + me.sll_protocol = htons(ETH_P_LOOP); + if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { + perror("bind"); + close(s); + return -1; + } + + alen = sizeof(me); + if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { + perror("getsockname"); + close(s); + return -1; + } + close(s); + *htype = me.sll_hatype; + return me.sll_halen; +} + +static int parse_address(char *dev, int hatype, int halen, char *lla, struct ifreq *ifr) +{ + int alen; + + memset(ifr, 0, sizeof(*ifr)); + strncpy(ifr->ifr_name, dev, sizeof(ifr->ifr_name)); + ifr->ifr_hwaddr.sa_family = hatype; + alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), 14, lla); + if (alen < 0) + return -1; + if (alen != halen) { + bb_error_msg("wrong address (%s) length: expected %d bytes", lla, halen); + return -1; + } + return 0; +} + +static int set_address(struct ifreq *ifr, int brd) +{ + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) { + perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR"); + close(s); + return -1; + } + close(s); + return 0; +} + + +static int do_set(int argc, char **argv) +{ + char *dev = NULL; + __u32 mask = 0; + __u32 flags = 0; + int qlen = -1; + int mtu = -1; + char *newaddr = NULL; + char *newbrd = NULL; + struct ifreq ifr0, ifr1; + char *newname = NULL; + int htype, halen; + + while (argc > 0) { + if (strcmp(*argv, "up") == 0) { + mask |= IFF_UP; + flags |= IFF_UP; + } else if (strcmp(*argv, "down") == 0) { + mask |= IFF_UP; + flags &= ~IFF_UP; + } else if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + newname = *argv; + } else if (strcmp(*argv, "mtu") == 0) { + NEXT_ARG(); + if (mtu != -1) + duparg("mtu", *argv); + if (get_integer(&mtu, *argv, 0)) + invarg(*argv, "mtu"); + } else if (strcmp(*argv, "multicast") == 0) { + NEXT_ARG(); + mask |= IFF_MULTICAST; + if (strcmp(*argv, "on") == 0) { + flags |= IFF_MULTICAST; + } else if (strcmp(*argv, "off") == 0) { + flags &= ~IFF_MULTICAST; + } else + return on_off("multicast"); + } else if (strcmp(*argv, "arp") == 0) { + NEXT_ARG(); + mask |= IFF_NOARP; + if (strcmp(*argv, "on") == 0) { + flags &= ~IFF_NOARP; + } else if (strcmp(*argv, "off") == 0) { + flags |= IFF_NOARP; + } else + return on_off("noarp"); + } else if (strcmp(*argv, "addr") == 0) { + NEXT_ARG(); + newaddr = *argv; + } else { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + } + if (dev) + duparg2("dev", *argv); + dev = *argv; + } + argc--; argv++; + } + + if (!dev) { + bb_error_msg(bb_msg_requires_arg, "\"dev\""); + exit(-1); + } + + if (newaddr || newbrd) { + halen = get_address(dev, &htype); + if (halen < 0) + return -1; + if (newaddr) { + if (parse_address(dev, htype, halen, newaddr, &ifr0) < 0) + return -1; + } + if (newbrd) { + if (parse_address(dev, htype, halen, newbrd, &ifr1) < 0) + return -1; + } + } + + if (newname && strcmp(dev, newname)) { + if (do_changename(dev, newname) < 0) + return -1; + dev = newname; + } + if (qlen != -1) { + if (set_qlen(dev, qlen) < 0) + return -1; + } + if (mtu != -1) { + if (set_mtu(dev, mtu) < 0) + return -1; + } + if (newaddr || newbrd) { + if (newbrd) { + if (set_address(&ifr1, 1) < 0) + return -1; + } + if (newaddr) { + if (set_address(&ifr0, 0) < 0) + return -1; + } + } + if (mask) + return do_chflags(dev, flags, mask); + return 0; +} + +static int ipaddr_list_link(int argc, char **argv) +{ + preferred_family = AF_PACKET; + return ipaddr_list_or_flush(argc, argv, 0); +} + +int do_iplink(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "set") == 0) + return do_set(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return ipaddr_list_link(argc-1, argv+1); + } else + return ipaddr_list_link(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c new file mode 100644 index 0000000..9c3b870 --- /dev/null +++ b/networking/libiproute/iproute.c @@ -0,0 +1,858 @@ +/* vi: set sw=4 ts=4: */ +/* + * iproute.c "ip route". + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + * Kunihiro Ishiguro <kunihiro@zebra.org> 001102: rtnh_ifindex was not initialized + */ + +#include "libbb.h" + +#include <sys/socket.h> + +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +#ifndef RTAX_RTTVAR +#define RTAX_RTTVAR RTAX_HOPS +#endif + + +static struct +{ + int tb; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; + int protocol, protocolmask; + int scope, scopemask; + int type, typemask; + int tos, tosmask; + int iif, iifmask; + int oif, oifmask; + int realm, realmmask; + inet_prefix rprefsrc; + inet_prefix rvia; + inet_prefix rdst; + inet_prefix mdst; + inet_prefix rsrc; + inet_prefix msrc; +} filter; + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_route(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + inet_prefix dst; + inet_prefix src; + int host_len = -1; + SPRINT_BUF(b1); + + + if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) { + fprintf(stderr, "Not a route: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE) + return 0; + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (r->rtm_family == AF_INET6) + host_len = 128; + else if (r->rtm_family == AF_INET) + host_len = 32; + + if (r->rtm_family == AF_INET6) { + if (filter.tb) { + if (filter.tb < 0) { + if (!(r->rtm_flags&RTM_F_CLONED)) { + return 0; + } + } else { + if (r->rtm_flags&RTM_F_CLONED) { + return 0; + } + if (filter.tb == RT_TABLE_LOCAL) { + if (r->rtm_type != RTN_LOCAL) { + return 0; + } + } else if (filter.tb == RT_TABLE_MAIN) { + if (r->rtm_type == RTN_LOCAL) { + return 0; + } + } else { + return 0; + } + } + } + } else { + if (filter.tb > 0 && filter.tb != r->rtm_table) { + return 0; + } + } + if (filter.rdst.family && + (r->rtm_family != filter.rdst.family || filter.rdst.bitlen > r->rtm_dst_len)) { + return 0; + } + if (filter.mdst.family && + (r->rtm_family != filter.mdst.family || + (filter.mdst.bitlen >= 0 && filter.mdst.bitlen < r->rtm_dst_len))) { + return 0; + } + if (filter.rsrc.family && + (r->rtm_family != filter.rsrc.family || filter.rsrc.bitlen > r->rtm_src_len)) { + return 0; + } + if (filter.msrc.family && + (r->rtm_family != filter.msrc.family || + (filter.msrc.bitlen >= 0 && filter.msrc.bitlen < r->rtm_src_len))) { + return 0; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (filter.rdst.family && inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen)) + return 0; + if (filter.mdst.family && filter.mdst.bitlen >= 0 && + inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len)) + return 0; + + if (filter.rsrc.family && inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen)) + return 0; + if (filter.msrc.family && filter.msrc.bitlen >= 0 && + inet_addr_match(&src, &filter.msrc, r->rtm_src_len)) + return 0; + + if (filter.flushb && + r->rtm_family == AF_INET6 && + r->rtm_dst_len == 0 && + r->rtm_type == RTN_UNREACHABLE && + tb[RTA_PRIORITY] && + *(int*)RTA_DATA(tb[RTA_PRIORITY]) == -1) + return 0; + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELROUTE; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELROUTE) { + fprintf(fp, "Deleted "); + } + if (r->rtm_type != RTN_UNICAST && !filter.type) { + fprintf(fp, "%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + } + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "%s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "%s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "0/%d ", r->rtm_dst_len); + } else { + fprintf(fp, "default "); + } + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "from %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "from 0/%u ", r->rtm_src_len); + } + if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) { + fprintf(fp, "via %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } + if (tb[RTA_OIF] && filter.oifmask != -1) { + fprintf(fp, "dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF]))); + } + + if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) { + /* Do not use format_host(). It is our local addr + and symbolic name will not be useful. + */ + fprintf(fp, " src %s ", + rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_PREFSRC]), + RTA_DATA(tb[RTA_PREFSRC]), + abuf, sizeof(abuf))); + } + if (tb[RTA_PRIORITY]) { + fprintf(fp, " metric %d ", *(__u32*)RTA_DATA(tb[RTA_PRIORITY])); + } + if (r->rtm_family == AF_INET6) { + struct rta_cacheinfo *ci = NULL; + if (tb[RTA_CACHEINFO]) { + ci = RTA_DATA(tb[RTA_CACHEINFO]); + } + if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) { + static int hz; + if (!hz) { + hz = get_hz(); + } + if (r->rtm_flags & RTM_F_CLONED) { + fprintf(fp, "%s cache ", _SL_); + } + if (ci->rta_expires) { + fprintf(fp, " expires %dsec", ci->rta_expires/hz); + } + if (ci->rta_error != 0) { + fprintf(fp, " error %d", ci->rta_error); + } + } else if (ci) { + if (ci->rta_error != 0) + fprintf(fp, " error %d", ci->rta_error); + } + } + if (tb[RTA_IIF] && filter.iifmask != -1) { + fprintf(fp, " iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF]))); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int iproute_modify(int cmd, unsigned flags, int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char mxbuf[256]; + struct rtattr * mxrta = (void*)mxbuf; + unsigned mxlock = 0; + char *d = NULL; + int gw_ok = 0; + int dst_ok = 0; + int proto_ok = 0; + int type_ok = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST|flags; + req.n.nlmsg_type = cmd; + req.r.rtm_family = preferred_family; + req.r.rtm_table = RT_TABLE_MAIN; + req.r.rtm_scope = RT_SCOPE_NOWHERE; + + if (cmd != RTM_DELROUTE) { + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_type = RTN_UNICAST; + } + + mxrta->rta_type = RTA_METRICS; + mxrta->rta_len = RTA_LENGTH(0); + + while (argc > 0) { + if (strcmp(*argv, "src") == 0) { + inet_prefix addr; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "via") == 0) { + inet_prefix addr; + gw_ok = 1; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "mtu") == 0) { + unsigned mtu; + NEXT_ARG(); + if (strcmp(*argv, "lock") == 0) { + mxlock |= (1<<RTAX_MTU); + NEXT_ARG(); + } + if (get_unsigned(&mtu, *argv, 0)) { + invarg(*argv, "mtu"); + } + rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu); + } else if (matches(*argv, "protocol") == 0) { + uint32_t prot; + NEXT_ARG(); + if (rtnl_rtprot_a2n(&prot, *argv)) + invarg(*argv, "protocol"); + req.r.rtm_protocol = prot; + proto_ok =1; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "oif") == 0) { + NEXT_ARG(); + d = *argv; + } else { + int type; + inet_prefix dst; + + if (strcmp(*argv, "to") == 0) { + NEXT_ARG(); + } + if ((**argv < '0' || **argv > '9') && + rtnl_rtntype_a2n(&type, *argv) == 0) { + NEXT_ARG(); + req.r.rtm_type = type; + type_ok = 1; + } + + if (dst_ok) { + duparg2("to", *argv); + } + get_prefix(&dst, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = dst.family; + } + req.r.rtm_dst_len = dst.bitlen; + dst_ok = 1; + if (dst.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } + } + argc--; argv++; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + if (d) { + int idx; + + ll_init_map(&rth); + + if (d) { + if ((idx = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (mxrta->rta_len > RTA_LENGTH(0)) { + if (mxlock) { + rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock); + } + addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta)); + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) { + exit(2); + } + + return 0; +} + +static int rtnl_rtcache_request(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct rtmsg rtm; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + memset(&req, 0, sizeof(req)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = RTM_GETROUTE; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.rtm.rtm_family = family; + req.rtm.rtm_flags |= RTM_F_CLONED; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +static int iproute_flush_cache(void) +{ +#define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush" + + int len; + int flush_fd = open (ROUTE_FLUSH_PATH, O_WRONLY); + char *buffer = "-1"; + + if (flush_fd < 0) { + fprintf(stderr, "Cannot open \"%s\"\n", ROUTE_FLUSH_PATH); + return -1; + } + + len = strlen (buffer); + + if ((write (flush_fd, (void *)buffer, len)) < len) { + fprintf(stderr, "Cannot flush routing cache\n"); + return -1; + } + close(flush_fd); + return 0; +} + +static void iproute_reset_filter(void) +{ + memset(&filter, 0, sizeof(filter)); + filter.mdst.bitlen = -1; + filter.msrc.bitlen = -1; +} + +static int iproute_list_or_flush(int argc, char **argv, int flush) +{ + int do_ipv6 = preferred_family; + struct rtnl_handle rth; + char *id = NULL; + char *od = NULL; + + iproute_reset_filter(); + filter.tb = RT_TABLE_MAIN; + + if (flush && argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "\"ip route flush\""); + return -1; + } + + while (argc > 0) { + if (matches(*argv, "protocol") == 0) { + uint32_t prot = 0; + NEXT_ARG(); + filter.protocolmask = -1; + if (rtnl_rtprot_a2n(&prot, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "protocol"); + } + prot = 0; + filter.protocolmask = 0; + } + filter.protocol = prot; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "oif") == 0) { + NEXT_ARG(); + od = *argv; + } else if (strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + id = *argv; + } else if (matches(*argv, "from") == 0) { + NEXT_ARG(); + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rsrc, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.msrc, *argv, do_ipv6); + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.msrc, *argv, do_ipv6); + filter.rsrc = filter.msrc; + } + } else { + if (matches(*argv, "to") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rdst, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.mdst, *argv, do_ipv6); + } else if (matches(*argv, "table") == 0) { + NEXT_ARG(); + if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else if (matches(*argv, "main") != 0) { + invarg(*argv, "table"); + } + } else if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.mdst, *argv, do_ipv6); + filter.rdst = filter.mdst; + } + } + argc--; argv++; + } + + if (do_ipv6 == AF_UNSPEC && filter.tb) { + do_ipv6 = AF_INET; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + ll_init_map(&rth); + + if (id || od) { + int idx; + + if (id) { + if ((idx = ll_name_to_index(id)) == 0) { + bb_error_msg("cannot find device \"%s\"", id); + return -1; + } + filter.iif = idx; + filter.iifmask = -1; + } + if (od) { + if ((idx = ll_name_to_index(od)) == 0) { + bb_error_msg("cannot find device \"%s\"", od); + } + filter.oif = idx; + filter.oifmask = -1; + } + } + + if (flush) { + char flushb[4096-512]; + + if (filter.tb == -1) { + if (do_ipv6 != AF_INET6) + iproute_flush_cache(); + if (do_ipv6 == AF_INET) + return 0; + } + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + perror("Cannot send dump request"); + return -1; + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg("flush terminated"); + return -1; + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.tb != -1) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } else { + if (rtnl_rtcache_request(&rth, do_ipv6) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } + + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + exit(0); +} + + +static int iproute_get(int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char *idev = NULL; + char *odev = NULL; + int connected = 0; + int from_ok = 0; + static const char * const options[] = + { "from", "iif", "oif", "dev", "notify", "connected", "to", 0 }; + + memset(&req, 0, sizeof(req)); + + iproute_reset_filter(); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + req.r.rtm_family = preferred_family; + req.r.rtm_table = 0; + req.r.rtm_protocol = 0; + req.r.rtm_scope = 0; + req.r.rtm_type = 0; + req.r.rtm_src_len = 0; + req.r.rtm_dst_len = 0; + req.r.rtm_tos = 0; + + while (argc > 0) { + switch (index_in_str_array(options, *argv)) { + case 0: /* from */ + { + inet_prefix addr; + NEXT_ARG(); + from_ok = 1; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen); + } + req.r.rtm_src_len = addr.bitlen; + break; + } + case 1: /* iif */ + NEXT_ARG(); + idev = *argv; + break; + case 2: /* oif */ + case 3: /* dev */ + NEXT_ARG(); + odev = *argv; + break; + case 4: /* notify */ + req.r.rtm_flags |= RTM_F_NOTIFY; + break; + case 5: /* connected */ + connected = 1; + break; + case 6: /* to */ + NEXT_ARG(); + default: + { + inet_prefix addr; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &addr.data, addr.bytelen); + } + req.r.rtm_dst_len = addr.bitlen; + } + argc--; argv++; + } + } + + if (req.r.rtm_dst_len == 0) { + bb_error_msg_and_die("need at least destination address"); + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if (idev || odev) { + int idx; + + if (idev) { + if ((idx = ll_name_to_index(idev)) == 0) { + bb_error_msg("cannot find device \"%s\"", idev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_IIF, idx); + } + if (odev) { + if ((idx = ll_name_to_index(odev)) == 0) { + bb_error_msg("cannot find device \"%s\"", odev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + + if (connected && !from_ok) { + struct rtmsg *r = NLMSG_DATA(&req.n); + int len = req.n.nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + if (req.n.nlmsg_type != RTM_NEWROUTE) { + bb_error_msg("not a route?"); + return -1; + } + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong len %d", len); + return -1; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (tb[RTA_PREFSRC]) { + tb[RTA_PREFSRC]->rta_type = RTA_SRC; + r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]); + } else if (!tb[RTA_SRC]) { + bb_error_msg("failed to connect the route"); + return -1; + } + if (!odev && tb[RTA_OIF]) { + tb[RTA_OIF]->rta_type = 0; + } + if (tb[RTA_GATEWAY]) { + tb[RTA_GATEWAY]->rta_type = 0; + } + if (!idev && tb[RTA_IIF]) { + tb[RTA_IIF]->rta_type = 0; + } + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + } + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + exit(0); +} + +int do_iproute(int argc, char **argv) +{ + static const char * const ip_route_commands[] = + { "add", "append", "change", "chg", "delete", "get", + "list", "show", "prepend", "replace", "test", "flush", 0 }; + int command_num = 6; + unsigned int flags = 0; + int cmd = RTM_NEWROUTE; + + /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */ + /* It probably means that it is using "first match" rule */ + if (*argv) { + command_num = index_in_substr_array(ip_route_commands, *argv); + } + switch (command_num) { + case 0: /* add*/ + flags = NLM_F_CREATE|NLM_F_EXCL; + break; + case 1: /* append */ + flags = NLM_F_CREATE|NLM_F_APPEND; + break; + case 2: /* change */ + case 3: /* chg */ + flags = NLM_F_REPLACE; + break; + case 4: /* delete */ + case 5: /* del */ + cmd = RTM_DELROUTE; + break; + case 6: /* get */ + return iproute_get(argc-1, argv+1); + case 7: /* list */ + case 8: /* show */ + return iproute_list_or_flush(argc-1, argv+1, 0); + case 9: /* prepend */ + flags = NLM_F_CREATE; + case 10: /* replace */ + flags = NLM_F_CREATE|NLM_F_REPLACE; + case 11: /* test */ + flags = NLM_F_EXCL; + case 12: /* flush */ + return iproute_list_or_flush(argc-1, argv+1, 1); + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + + return iproute_modify(cmd, flags, argc-1, argv+1); +} diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c new file mode 100644 index 0000000..1933877 --- /dev/null +++ b/networking/libiproute/iprule.c @@ -0,0 +1,331 @@ +/* vi: set sw=4 ts=4: */ +/* + * iprule.c "ip rule". + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libbb.h" +#include <syslog.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> + +#include "rt_names.h" +#include "utils.h" +/* +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip rule [ list | add | del ] SELECTOR ACTION\n"); + fprintf(stderr, "SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n"); + fprintf(stderr, " [ dev STRING ] [ pref NUMBER ]\n"); + fprintf(stderr, "ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n"); + fprintf(stderr, " [ prohibit | reject | unreachable ]\n"); + fprintf(stderr, " [ realms [SRCREALM/]DSTREALM ]\n"); + fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n"); + exit(-1); +} +*/ +static int print_rule(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + int host_len = -1; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWRULE) + return 0; + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) + return -1; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (r->rtm_family == AF_INET) + host_len = 32; + else if (r->rtm_family == AF_INET6) + host_len = 128; +/* else if (r->rtm_family == AF_DECnet) + host_len = 16; + else if (r->rtm_family == AF_IPX) + host_len = 80; +*/ + if (tb[RTA_PRIORITY]) + fprintf(fp, "%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY])); + else + fprintf(fp, "0:\t"); + + fprintf(fp, "from "); + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "%s/%u", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "%s", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "0/%d", r->rtm_src_len); + } else { + fprintf(fp, "all"); + } + fprintf(fp, " "); + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "to %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "to %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf))); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "to 0/%d ", r->rtm_dst_len); + } + + if (r->rtm_tos) { + fprintf(fp, "tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1))); + } + if (tb[RTA_PROTOINFO]) { + fprintf(fp, "fwmark %#x ", *(__u32*)RTA_DATA(tb[RTA_PROTOINFO])); + } + + if (tb[RTA_IIF]) { + fprintf(fp, "iif %s ", (char*)RTA_DATA(tb[RTA_IIF])); + } + + if (r->rtm_table) + fprintf(fp, "lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1))); + + if (tb[RTA_FLOW]) { + __u32 to = *(__u32*)RTA_DATA(tb[RTA_FLOW]); + __u32 from = to>>16; + to &= 0xFFFF; + if (from) { + fprintf(fp, "realms %s/", + rtnl_rtrealm_n2a(from, b1, sizeof(b1))); + } + fprintf(fp, "%s ", + rtnl_rtrealm_n2a(to, b1, sizeof(b1))); + } + + if (r->rtm_type == RTN_NAT) { + if (tb[RTA_GATEWAY]) { + fprintf(fp, "map-to %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } else + fprintf(fp, "masquerade"); + } else if (r->rtm_type != RTN_UNICAST) + fprintf(fp, "%s", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +int iprule_list(int argc, char **argv) +{ + struct rtnl_handle rth; + int af = preferred_family; + + if (af == AF_UNSPEC) + af = AF_INET; + + if (argc > 0) { + bb_error_msg("\"rule show\" needs no arguments"); + return -1; + } + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_wilddump_request(&rth, af, RTM_GETRULE) < 0) { + bb_perror_msg("Cannot send dump request"); + return 1; + } + + if (rtnl_dump_filter(&rth, print_rule, stdout, NULL, NULL) < 0) { + bb_error_msg("Dump terminated"); + return 1; + } + + return 0; +} + + +int iprule_modify(int cmd, int argc, char **argv) +{ + int table_ok = 0; + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_type = cmd; + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.r.rtm_family = preferred_family; + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_table = 0; + req.r.rtm_type = RTN_UNSPEC; + + if (cmd == RTM_NEWRULE) { + req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL; + req.r.rtm_type = RTN_UNICAST; + } + + while (argc > 0) { + if (strcmp(*argv, "from") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_src_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_SRC, &dst.data, dst.bytelen); + } else if (strcmp(*argv, "to") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_dst_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "order") == 0 || + matches(*argv, "priority") == 0) { + __u32 pref; + NEXT_ARG(); + if (get_u32(&pref, *argv, 0)) + invarg("preference value", *argv); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref); + } else if (strcmp(*argv, "tos") == 0) { + __u32 tos; + NEXT_ARG(); + if (rtnl_dsfield_a2n(&tos, *argv)) + invarg("TOS value", *argv); + req.r.rtm_tos = tos; + } else if (strcmp(*argv, "fwmark") == 0) { + __u32 fwmark; + NEXT_ARG(); + if (get_u32(&fwmark, *argv, 0)) + invarg("fwmark value", *argv); + addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark); + } else if (matches(*argv, "realms") == 0) { + __u32 realm; + NEXT_ARG(); + if (get_rt_realms(&realm, *argv)) + invarg("realms", *argv); + addattr32(&req.n, sizeof(req), RTA_FLOW, realm); + } else if (matches(*argv, "table") == 0 || + strcmp(*argv, "lookup") == 0) { + int tid; + NEXT_ARG(); + if (rtnl_rttable_a2n(&tid, *argv)) + invarg("table ID", *argv); + req.r.rtm_table = tid; + table_ok = 1; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1); + } else if (strcmp(*argv, "nat") == 0 || + matches(*argv, "map-to") == 0) { + NEXT_ARG(); + addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv)); + req.r.rtm_type = RTN_NAT; + } else { + int type; + + if (strcmp(*argv, "type") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "help") == 0) + bb_show_usage(); + if (rtnl_rtntype_a2n(&type, *argv)) + invarg("Failed to parse rule type", *argv); + req.r.rtm_type = type; + } + argc--; + argv++; + } + + if (req.r.rtm_family == AF_UNSPEC) + req.r.rtm_family = AF_INET; + + if (!table_ok && cmd == RTM_NEWRULE) + req.r.rtm_table = RT_TABLE_MAIN; + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + return 2; + + return 0; +} + +int do_iprule(int argc, char **argv) +{ + static const char * const ip_rule_commands[] = + {"add", "delete", "list", "show", 0}; + int command_num = 2; + int cmd; + + if (argc < 1) + return iprule_list(0, NULL); + if (*argv) + command_num = index_in_substr_array(ip_rule_commands, *argv); + switch (command_num) { + case 0: /* add */ + cmd = RTM_NEWRULE; + break; + case 1: /* delete */ + cmd = RTM_DELRULE; + break; + case 2: /* list */ + case 3: /* show */ + return iprule_list(argc-1, argv+1); + break; + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + return iprule_modify(cmd, argc-1, argv+1); +} + diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c new file mode 100644 index 0000000..2080324 --- /dev/null +++ b/networking/libiproute/iptunnel.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * iptunnel.c "ip tunnel" + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + * Rani Assaf <rani@magic.metawire.com> 980930: do not allow key for ipip/sit + * Phil Karn <karn@ka9q.ampr.org> 990408: "pmtudisc" flag + */ + +#include "libbb.h" +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <string.h> +#include <unistd.h> + +#include <netinet/ip.h> + +#include <net/if.h> +#include <net/if_arp.h> + +#include <asm/types.h> +#ifndef __constant_htons +#define __constant_htons htons +#endif +#include <linux/if_tunnel.h> + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static int do_ioctl_get_ifindex(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + bb_perror_msg("ioctl"); + return 0; + } + close(fd); + return ifr.ifr_ifindex; +} + +static int do_ioctl_get_iftype(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFHWADDR, &ifr)) { + bb_perror_msg("ioctl"); + return -1; + } + close(fd); + return ifr.ifr_addr.sa_family; +} + + +static char *do_ioctl_get_ifname(int idx) +{ + static struct ifreq ifr; + int fd; + + ifr.ifr_ifindex = idx; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFNAME, &ifr)) { + bb_perror_msg("ioctl"); + return NULL; + } + close(fd); + return ifr.ifr_name; +} + + + +static int do_get_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCGETTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_add_ioctl(int cmd, char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (cmd == SIOCCHGTUNNEL && p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, cmd, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_del_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCDELTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p) +{ + int count = 0; + char medium[IFNAMSIZ]; + memset(p, 0, sizeof(*p)); + memset(&medium, 0, sizeof(medium)); + + p->iph.version = 4; + p->iph.ihl = 5; +#ifndef IP_DF +#define IP_DF 0x4000 /* Flag: "Don't Fragment" */ +#endif + p->iph.frag_off = htons(IP_DF); + + while (argc > 0) { + if (strcmp(*argv, "mode") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "ipip") == 0 || + strcmp(*argv, "ip/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPIP; + } else if (strcmp(*argv, "gre") == 0 || + strcmp(*argv, "gre/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_GRE; + } else if (strcmp(*argv, "sit") == 0 || + strcmp(*argv, "ipv6/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPV6; + } else { + bb_error_msg("cannot guess tunnel mode"); + exit(-1); + } + } else if (strcmp(*argv, "key") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->i_key = p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"key\""); + exit(-1); + } + p->i_key = p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "ikey") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"ikey\""); + exit(-1); + } + p->i_key = htonl(uval); + } + } else if (strcmp(*argv, "okey") == 0) { + unsigned uval; + NEXT_ARG(); + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"okey\""); + exit(-1); + } + p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "seq") == 0) { + p->i_flags |= GRE_SEQ; + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "iseq") == 0) { + p->i_flags |= GRE_SEQ; + } else if (strcmp(*argv, "oseq") == 0) { + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "csum") == 0) { + p->i_flags |= GRE_CSUM; + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "icsum") == 0) { + p->i_flags |= GRE_CSUM; + } else if (strcmp(*argv, "ocsum") == 0) { + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "nopmtudisc") == 0) { + p->iph.frag_off = 0; + } else if (strcmp(*argv, "pmtudisc") == 0) { + p->iph.frag_off = htons(IP_DF); + } else if (strcmp(*argv, "remote") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.daddr = get_addr32(*argv); + } else if (strcmp(*argv, "local") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.saddr = get_addr32(*argv); + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + strncpy(medium, *argv, IFNAMSIZ-1); + } else if (strcmp(*argv, "ttl") == 0) { + unsigned uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (get_unsigned(&uval, *argv, 0)) + invarg(*argv, "TTL"); + if (uval > 255) + invarg(*argv, "TTL must be <=255"); + p->iph.ttl = uval; + } + } else if (strcmp(*argv, "tos") == 0 || + matches(*argv, "dsfield") == 0) { + __u32 uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (rtnl_dsfield_a2n(&uval, *argv)) + invarg(*argv, "TOS"); + p->iph.tos = uval; + } else + p->iph.tos = 1; + } else { + if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + } + if (p->name[0]) + duparg2("name", *argv); + strncpy(p->name, *argv, IFNAMSIZ); + if (cmd == SIOCCHGTUNNEL && count == 0) { + struct ip_tunnel_parm old_p; + memset(&old_p, 0, sizeof(old_p)); + if (do_get_ioctl(*argv, &old_p)) + return -1; + *p = old_p; + } + } + count++; + argc--; argv++; + } + + + if (p->iph.protocol == 0) { + if (memcmp(p->name, "gre", 3) == 0) + p->iph.protocol = IPPROTO_GRE; + else if (memcmp(p->name, "ipip", 4) == 0) + p->iph.protocol = IPPROTO_IPIP; + else if (memcmp(p->name, "sit", 3) == 0) + p->iph.protocol = IPPROTO_IPV6; + } + + if (p->iph.protocol == IPPROTO_IPIP || p->iph.protocol == IPPROTO_IPV6) { + if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) { + bb_error_msg("keys are not allowed with ipip and sit"); + return -1; + } + } + + if (medium[0]) { + p->link = do_ioctl_get_ifindex(medium); + if (p->link == 0) + return -1; + } + + if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->i_key = p->iph.daddr; + p->i_flags |= GRE_KEY; + } + if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->o_key = p->iph.daddr; + p->o_flags |= GRE_KEY; + } + if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) { + bb_error_msg("broadcast tunnel requires a source address"); + return -1; + } + return 0; +} + + +static int do_add(int cmd, int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, cmd, &p) < 0) + return -1; + + if (p.iph.ttl && p.iph.frag_off == 0) { + bb_error_msg("ttl != 0 and noptmudisc are incompatible"); + return -1; + } + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_add_ioctl(cmd, "tunl0", &p); + case IPPROTO_GRE: + return do_add_ioctl(cmd, "gre0", &p); + case IPPROTO_IPV6: + return do_add_ioctl(cmd, "sit0", &p); + default: + bb_error_msg("cannot determine tunnel mode (ipip, gre or sit)"); + return -1; + } + return -1; +} + +static int do_del(int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_del_ioctl("tunl0", &p); + case IPPROTO_GRE: + return do_del_ioctl("gre0", &p); + case IPPROTO_IPV6: + return do_del_ioctl("sit0", &p); + default: + return do_del_ioctl(p.name, &p); + } + return -1; +} + +static void print_tunnel(struct ip_tunnel_parm *p) +{ + char s1[256]; + char s2[256]; + char s3[64]; + char s4[64]; + + format_host(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1)); + format_host(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2)); + inet_ntop(AF_INET, &p->i_key, s3, sizeof(s3)); + inet_ntop(AF_INET, &p->o_key, s4, sizeof(s4)); + + printf("%s: %s/ip remote %s local %s ", + p->name, + p->iph.protocol == IPPROTO_IPIP ? "ip" : + (p->iph.protocol == IPPROTO_GRE ? "gre" : + (p->iph.protocol == IPPROTO_IPV6 ? "ipv6" : "unknown")), + p->iph.daddr ? s1 : "any", p->iph.saddr ? s2 : "any"); + if (p->link) { + char *n = do_ioctl_get_ifname(p->link); + if (n) + printf(" dev %s ", n); + } + if (p->iph.ttl) + printf(" ttl %d ", p->iph.ttl); + else + printf(" ttl inherit "); + if (p->iph.tos) { + SPRINT_BUF(b1); + printf(" tos"); + if (p->iph.tos&1) + printf(" inherit"); + if (p->iph.tos&~1) + printf("%c%s ", p->iph.tos&1 ? '/' : ' ', + rtnl_dsfield_n2a(p->iph.tos&~1, b1, sizeof(b1))); + } + if (!(p->iph.frag_off&htons(IP_DF))) + printf(" nopmtudisc"); + + if ((p->i_flags&GRE_KEY) && (p->o_flags&GRE_KEY) && p->o_key == p->i_key) + printf(" key %s", s3); + else if ((p->i_flags|p->o_flags)&GRE_KEY) { + if (p->i_flags&GRE_KEY) + printf(" ikey %s ", s3); + if (p->o_flags&GRE_KEY) + printf(" okey %s ", s4); + } + + if (p->i_flags&GRE_SEQ) + printf("%s Drop packets out of sequence.\n", _SL_); + if (p->i_flags&GRE_CSUM) + printf("%s Checksum in received packet is required.", _SL_); + if (p->o_flags&GRE_SEQ) + printf("%s Sequence packets on output.", _SL_); + if (p->o_flags&GRE_CSUM) + printf("%s Checksum output packets.", _SL_); +} + +static int do_tunnels_list(struct ip_tunnel_parm *p) +{ + char name[IFNAMSIZ]; + unsigned long rx_bytes, rx_packets, rx_errs, rx_drops, + rx_fifo, rx_frame, + tx_bytes, tx_packets, tx_errs, tx_drops, + tx_fifo, tx_colls, tx_carrier, rx_multi; + int type; + struct ip_tunnel_parm p1; + + char buf[512]; + FILE *fp = fopen("/proc/net/dev", "r"); + if (fp == NULL) { + perror("fopen"); + return -1; + } + + fgets(buf, sizeof(buf), fp); + fgets(buf, sizeof(buf), fp); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *ptr; + buf[sizeof(buf) - 1] = 0; + if ((ptr = strchr(buf, ':')) == NULL || + (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) { + bb_error_msg("wrong format of /proc/net/dev. Sorry"); + return -1; + } + if (sscanf(ptr, "%lu%lu%lu%lu%lu%lu%lu%*d%lu%lu%lu%lu%lu%lu%lu", + &rx_bytes, &rx_packets, &rx_errs, &rx_drops, + &rx_fifo, &rx_frame, &rx_multi, + &tx_bytes, &tx_packets, &tx_errs, &tx_drops, + &tx_fifo, &tx_colls, &tx_carrier) != 14) + continue; + if (p->name[0] && strcmp(p->name, name)) + continue; + type = do_ioctl_get_iftype(name); + if (type == -1) { + bb_error_msg("failed to get type of [%s]", name); + continue; + } + if (type != ARPHRD_TUNNEL && type != ARPHRD_IPGRE && type != ARPHRD_SIT) + continue; + memset(&p1, 0, sizeof(p1)); + if (do_get_ioctl(name, &p1)) + continue; + if ((p->link && p1.link != p->link) || + (p->name[0] && strcmp(p1.name, p->name)) || + (p->iph.daddr && p1.iph.daddr != p->iph.daddr) || + (p->iph.saddr && p1.iph.saddr != p->iph.saddr) || + (p->i_key && p1.i_key != p->i_key)) + continue; + print_tunnel(&p1); + puts(""); + } + return 0; +} + +static int do_show(int argc, char **argv) +{ + int err; + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + err = do_get_ioctl(p.name[0] ? p.name : "tunl0", &p); + break; + case IPPROTO_GRE: + err = do_get_ioctl(p.name[0] ? p.name : "gre0", &p); + break; + case IPPROTO_IPV6: + err = do_get_ioctl(p.name[0] ? p.name : "sit0", &p); + break; + default: + do_tunnels_list(&p); + return 0; + } + if (err) + return -1; + + print_tunnel(&p); + puts(""); + return 0; +} + +int do_iptunnel(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "add") == 0) + return do_add(SIOCADDTUNNEL, argc-1, argv+1); + if (matches(*argv, "change") == 0) + return do_add(SIOCCHGTUNNEL, argc-1, argv+1); + if (matches(*argv, "del") == 0) + return do_del(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return do_show(argc-1, argv+1); + } else + return do_show(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c new file mode 100644 index 0000000..c7e17e1 --- /dev/null +++ b/networking/libiproute/libnetlink.c @@ -0,0 +1,396 @@ +/* vi: set sw=4 ts=4: */ +/* + * libnetlink.c RTnetlink service routines. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + */ + +#include "libbb.h" +#include <sys/socket.h> + +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <sys/uio.h> + +#include "libnetlink.h" + +void rtnl_close(struct rtnl_handle *rth) +{ + close(rth->fd); +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + socklen_t addr_len; + + memset(rth, 0, sizeof(rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (rth->fd < 0) { + bb_perror_msg("cannot open netlink socket"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + bb_perror_msg("cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + bb_perror_msg("cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + bb_error_msg("wrong address length %d", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + bb_error_msg("wrong address family %d", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { { &nlh, sizeof(nlh) }, { req, len } }; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + iov, 2, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2) +{ + char buf[8192]; + struct sockaddr_nl nladdr; + struct iovec iov = { buf, sizeof(buf) }; + + while (1) { + int status; + struct nlmsghdr *h; + + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) { + return err; + } + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + return 0; + } + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *l_err = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + bb_error_msg("ERROR truncated"); + } else { + errno = -l_err->error; + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) { + return err; + } + +skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { (void*)n, n->nlmsg_len }; + char buf[8192]; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + if (answer == NULL) { + n->nlmsg_flags |= NLM_F_ACK; + } + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + bb_perror_msg("cannot talk to rtnetlink"); + return -1; + } + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) { + continue; + } + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + for (h = (struct nlmsghdr*)buf; status >= sizeof(*h); ) { + int l_err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("truncated message"); + return -1; + } + bb_error_msg_and_die("malformed message: len=%d!", len); + } + + if (nladdr.nl_pid != peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + l_err = junk(&nladdr, h, jarg); + if (l_err < 0) { + return l_err; + } + } + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (l < sizeof(struct nlmsgerr)) { + bb_error_msg("ERROR truncated"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) { + memcpy(answer, h, h->nlmsg_len); + } + return 0; + } + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + bb_error_msg("unexpected reply!"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) { + tb[rta->rta_type] = rta; + } + rta = RTA_NEXT(rta,len); + } + if (len) { + bb_error_msg("deficit %d, rta_len=%d!", len, rta->rta_len); + } + return 0; +} diff --git a/networking/libiproute/libnetlink.h b/networking/libiproute/libnetlink.h new file mode 100644 index 0000000..7cfcd20 --- /dev/null +++ b/networking/libiproute/libnetlink.h @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include <asm/types.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); +extern int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl*, struct nlmsghdr *n, void*), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2); +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, char *buf, int); + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); + + +#endif /* __LIBNETLINK_H__ */ + diff --git a/networking/libiproute/linux/pkt_sched.h b/networking/libiproute/linux/pkt_sched.h new file mode 100644 index 0000000..d6cf1fc --- /dev/null +++ b/networking/libiproute/linux/pkt_sched.h @@ -0,0 +1,414 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LINUX_PKT_SCHED_H +#define __LINUX_PKT_SCHED_H + +/* Logical priority bands not depending on specific packet scheduler. + Every scheduler will map them to real traffic classes, if it has + no more precise mechanism to classify packets. + + These numbers have no special meaning, though their coincidence + with obsolete IPv6 values is not occasional :-). New IPv6 drafts + preferred full anarchy inspired by diffserv group. + + Note: TC_PRIO_BESTEFFORT does not mean that it is the most unhappy + class, actually, as rule it will be handled with more care than + filler or even bulk. + */ + +#include <asm/types.h> + +#define TC_PRIO_BESTEFFORT 0 +#define TC_PRIO_FILLER 1 +#define TC_PRIO_BULK 2 +#define TC_PRIO_INTERACTIVE_BULK 4 +#define TC_PRIO_INTERACTIVE 6 +#define TC_PRIO_CONTROL 7 + +#define TC_PRIO_MAX 15 + +/* Generic queue statistics, available for all the elements. + Particular schedulers may have also their private records. + */ + +struct tc_stats +{ + __u64 bytes; /* NUmber of enqueues bytes */ + __u32 packets; /* Number of enqueued packets */ + __u32 drops; /* Packets dropped because of lack of resources */ + __u32 overlimits; /* Number of throttle events when this + * flow goes out of allocated bandwidth */ + __u32 bps; /* Current flow byte rate */ + __u32 pps; /* Current flow packet rate */ + __u32 qlen; + __u32 backlog; +#ifdef __KERNEL__ + spinlock_t *lock; +#endif +}; + +struct tc_estimator +{ + char interval; + unsigned char ewma_log; +}; + +/* "Handles" + --------- + + All the traffic control objects have 32bit identifiers, or "handles". + + They can be considered as opaque numbers from user API viewpoint, + but actually they always consist of two fields: major and + minor numbers, which are interpreted by kernel specially, + that may be used by applications, though not recommended. + + F.e. qdisc handles always have minor number equal to zero, + classes (or flows) have major equal to parent qdisc major, and + minor uniquely identifying class inside qdisc. + + Macros to manipulate handles: + */ + +#define TC_H_MAJ_MASK (0xFFFF0000U) +#define TC_H_MIN_MASK (0x0000FFFFU) +#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK) +#define TC_H_MIN(h) ((h)&TC_H_MIN_MASK) +#define TC_H_MAKE(maj,min) (((maj)&TC_H_MAJ_MASK)|((min)&TC_H_MIN_MASK)) + +#define TC_H_UNSPEC (0U) +#define TC_H_ROOT (0xFFFFFFFFU) +#define TC_H_INGRESS (0xFFFFFFF1U) + +struct tc_ratespec +{ + unsigned char cell_log; + unsigned char __reserved; + unsigned short feature; + short addend; + unsigned short mpu; + __u32 rate; +}; + +/* FIFO section */ + +struct tc_fifo_qopt +{ + __u32 limit; /* Queue length: bytes for bfifo, packets for pfifo */ +}; + +/* PRIO section */ + +#define TCQ_PRIO_BANDS 16 + +struct tc_prio_qopt +{ + int bands; /* Number of bands */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */ +}; + +/* CSZ section */ + +struct tc_csz_qopt +{ + int flows; /* Maximal number of guaranteed flows */ + unsigned char R_log; /* Fixed point position for round number */ + unsigned char delta_log; /* Log of maximal managed time interval */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> CSZ band */ +}; + +struct tc_csz_copt +{ + struct tc_ratespec slice; + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_CSZ_UNSPEC, + TCA_CSZ_PARMS, + TCA_CSZ_RTAB, + TCA_CSZ_PTAB, +}; + +/* TBF section */ + +struct tc_tbf_qopt +{ + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_TBF_UNSPEC, + TCA_TBF_PARMS, + TCA_TBF_RTAB, + TCA_TBF_PTAB, +}; + + +/* TEQL section */ + +/* TEQL does not require any parameters */ + +/* SFQ section */ + +struct tc_sfq_qopt +{ + unsigned quantum; /* Bytes per round allocated to flow */ + int perturb_period; /* Period of hash perturbation */ + __u32 limit; /* Maximal packets in queue */ + unsigned divisor; /* Hash divisor */ + unsigned flows; /* Maximal number of flows */ +}; + +/* + * NOTE: limit, divisor and flows are hardwired to code at the moment. + * + * limit=flows=128, divisor=1024; + * + * The only reason for this is efficiency, it is possible + * to change these parameters in compile time. + */ + +/* RED section */ + +enum +{ + TCA_RED_UNSPEC, + TCA_RED_PARMS, + TCA_RED_STAB, +}; + +struct tc_red_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) */ + __u32 qth_min; /* Min average length threshold (bytes) */ + __u32 qth_max; /* Max average length threshold (bytes) */ + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + unsigned char flags; +#define TC_RED_ECN 1 +}; + +struct tc_red_xstats +{ + __u32 early; /* Early drops */ + __u32 pdrop; /* Drops due to queue limits */ + __u32 other; /* Drops due to drop() calls */ + __u32 marked; /* Marked packets */ +}; + +/* GRED section */ + +#define MAX_DPs 16 + +enum +{ + TCA_GRED_UNSPEC, + TCA_GRED_PARMS, + TCA_GRED_STAB, + TCA_GRED_DPS, +}; + +#define TCA_SET_OFF TCA_GRED_PARMS +struct tc_gred_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) +*/ + __u32 qth_min; /* Min average length threshold (bytes) +*/ + __u32 qth_max; /* Max average length threshold (bytes) +*/ + __u32 DP; /* upto 2^32 DPs */ + __u32 backlog; + __u32 qave; + __u32 forced; + __u32 early; + __u32 other; + __u32 pdrop; + + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + __u8 prio; /* prio of this VQ */ + __u32 packets; + __u32 bytesin; +}; +/* gred setup */ +struct tc_gred_sopt +{ + __u32 DPs; + __u32 def_DP; + __u8 grio; +}; + +/* HTB section */ +#define TC_HTB_NUMPRIO 4 +#define TC_HTB_MAXDEPTH 4 + +struct tc_htb_opt +{ + struct tc_ratespec rate; + struct tc_ratespec ceil; + __u32 buffer; + __u32 cbuffer; + __u32 quantum; /* out only */ + __u32 level; /* out only */ + __u8 prio; + __u8 injectd; /* inject class distance */ + __u8 pad[2]; +}; +struct tc_htb_glob +{ + __u32 rate2quantum; /* bps->quantum divisor */ + __u32 defcls; /* default class number */ + __u32 use_dcache; /* use dequeue cache ? */ + __u32 debug; /* debug flags */ + + + /* stats */ + __u32 deq_rate; /* dequeue rate */ + __u32 utilz; /* dequeue utilization */ + __u32 trials; /* deq_prio trials per dequeue */ + __u32 dcache_hits; + __u32 direct_pkts; /* count of non shapped packets */ +}; +enum +{ + TCA_HTB_UNSPEC, + TCA_HTB_PARMS, + TCA_HTB_INIT, + TCA_HTB_CTAB, + TCA_HTB_RTAB, +}; +struct tc_htb_xstats +{ + __u32 lends; + __u32 borrows; + __u32 giants; /* too big packets (rate will not be accurate) */ + __u32 injects; /* how many times leaf used injected bw */ + __u32 tokens; + __u32 ctokens; +}; + +/* CBQ section */ + +#define TC_CBQ_MAXPRIO 8 +#define TC_CBQ_MAXLEVEL 8 +#define TC_CBQ_DEF_EWMA 5 + +struct tc_cbq_lssopt +{ + unsigned char change; + unsigned char flags; +#define TCF_CBQ_LSS_BOUNDED 1 +#define TCF_CBQ_LSS_ISOLATED 2 + unsigned char ewma_log; + unsigned char level; +#define TCF_CBQ_LSS_FLAGS 1 +#define TCF_CBQ_LSS_EWMA 2 +#define TCF_CBQ_LSS_MAXIDLE 4 +#define TCF_CBQ_LSS_MINIDLE 8 +#define TCF_CBQ_LSS_OFFTIME 0x10 +#define TCF_CBQ_LSS_AVPKT 0x20 + __u32 maxidle; + __u32 minidle; + __u32 offtime; + __u32 avpkt; +}; + +struct tc_cbq_wrropt +{ + unsigned char flags; + unsigned char priority; + unsigned char cpriority; + unsigned char __reserved; + __u32 allot; + __u32 weight; +}; + +struct tc_cbq_ovl +{ + unsigned char strategy; +#define TC_CBQ_OVL_CLASSIC 0 +#define TC_CBQ_OVL_DELAY 1 +#define TC_CBQ_OVL_LOWPRIO 2 +#define TC_CBQ_OVL_DROP 3 +#define TC_CBQ_OVL_RCLASSIC 4 + unsigned char priority2; + __u32 penalty; +}; + +struct tc_cbq_police +{ + unsigned char police; + unsigned char __res1; + unsigned short __res2; +}; + +struct tc_cbq_fopt +{ + __u32 split; + __u32 defmap; + __u32 defchange; +}; + +struct tc_cbq_xstats +{ + __u32 borrows; + __u32 overactions; + __s32 avgidle; + __s32 undertime; +}; + +enum +{ + TCA_CBQ_UNSPEC, + TCA_CBQ_LSSOPT, + TCA_CBQ_WRROPT, + TCA_CBQ_FOPT, + TCA_CBQ_OVL_STRATEGY, + TCA_CBQ_RATE, + TCA_CBQ_RTAB, + TCA_CBQ_POLICE, +}; + +#define TCA_CBQ_MAX TCA_CBQ_POLICE + +/* dsmark section */ + +enum { + TCA_DSMARK_UNSPEC, + TCA_DSMARK_INDICES, + TCA_DSMARK_DEFAULT_INDEX, + TCA_DSMARK_SET_TC_INDEX, + TCA_DSMARK_MASK, + TCA_DSMARK_VALUE +}; + +#define TCA_DSMARK_MAX TCA_DSMARK_VALUE + +/* ATM section */ + +enum { + TCA_ATM_UNSPEC, + TCA_ATM_FD, /* file/socket descriptor */ + TCA_ATM_PTR, /* pointer to descriptor - later */ + TCA_ATM_HDR, /* LL header */ + TCA_ATM_EXCESS, /* excess traffic class (0 for CLP) */ + TCA_ATM_ADDR, /* PVC address (for output only) */ + TCA_ATM_STATE /* VC state (ATM_VS_*; for output only) */ +}; + +#define TCA_ATM_MAX TCA_ATM_STATE + +#endif diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c new file mode 100644 index 0000000..ba0a65a --- /dev/null +++ b/networking/libiproute/ll_addr.c @@ -0,0 +1,85 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_addr.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include "libbb.h" + +#include <string.h> +#include <net/if_arp.h> + +#include "rt_names.h" +#include "utils.h" + + +const char *ll_addr_n2a(unsigned char *addr, int alen, int type, char *buf, int blen) +{ + int i; + int l; + + if (alen == 4 && + (type == ARPHRD_TUNNEL || type == ARPHRD_SIT || type == ARPHRD_IPGRE)) { + return inet_ntop(AF_INET, addr, buf, blen); + } + l = 0; + for (i=0; i<alen; i++) { + if (i==0) { + snprintf(buf+l, blen, "%02x", addr[i]); + blen -= 2; + l += 2; + } else { + snprintf(buf+l, blen, ":%02x", addr[i]); + blen -= 3; + l += 3; + } + } + return buf; +} + +int ll_addr_a2n(unsigned char *lladdr, int len, char *arg) +{ + if (strchr(arg, '.')) { + inet_prefix pfx; + if (get_addr_1(&pfx, arg, AF_INET)) { + bb_error_msg("\"%s\" is invalid lladdr", arg); + return -1; + } + if (len < 4) { + return -1; + } + memcpy(lladdr, pfx.data, 4); + return 4; + } else { + int i; + + for (i=0; i<len; i++) { + int temp; + char *cp = strchr(arg, ':'); + if (cp) { + *cp = 0; + cp++; + } + if (sscanf(arg, "%x", &temp) != 1) { + bb_error_msg("\"%s\" is invalid lladdr", arg); + return -1; + } + if (temp < 0 || temp > 255) { + bb_error_msg("\"%s\" is invalid lladdr", arg); + return -1; + } + lladdr[i] = temp; + if (!cp) { + break; + } + arg = cp; + } + return i+1; + } +} diff --git a/networking/libiproute/ll_map.c b/networking/libiproute/ll_map.c new file mode 100644 index 0000000..a14fa4e --- /dev/null +++ b/networking/libiproute/ll_map.c @@ -0,0 +1,186 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_map.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + */ + +#include "libbb.h" +#include <string.h> + +#include "libnetlink.h" +#include "ll_map.h" + +#include <sys/socket.h> /* socket() */ +#include <net/if.h> /* struct ifreq and co. */ +#include <sys/ioctl.h> /* ioctl() & SIOCGIFINDEX */ + +struct idxmap { + struct idxmap * next; + int index; + int type; + int alen; + unsigned flags; + unsigned char addr[8]; + char name[16]; +}; + +static struct idxmap *idxmap[16]; + +int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + int h; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct idxmap *im, **imp; + struct rtattr *tb[IFLA_MAX+1]; + + if (n->nlmsg_type != RTM_NEWLINK) + return 0; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi))) + return -1; + + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n)); + if (tb[IFLA_IFNAME] == NULL) + return 0; + + h = ifi->ifi_index&0xF; + + for (imp=&idxmap[h]; (im=*imp)!=NULL; imp = &im->next) + if (im->index == ifi->ifi_index) + break; + + if (im == NULL) { + im = xmalloc(sizeof(*im)); + im->next = *imp; + im->index = ifi->ifi_index; + *imp = im; + } + + im->type = ifi->ifi_type; + im->flags = ifi->ifi_flags; + if (tb[IFLA_ADDRESS]) { + int alen; + im->alen = alen = RTA_PAYLOAD(tb[IFLA_ADDRESS]); + if (alen > sizeof(im->addr)) + alen = sizeof(im->addr); + memcpy(im->addr, RTA_DATA(tb[IFLA_ADDRESS]), alen); + } else { + im->alen = 0; + memset(im->addr, 0, sizeof(im->addr)); + } + strcpy(im->name, RTA_DATA(tb[IFLA_IFNAME])); + return 0; +} + +const char *ll_idx_n2a(int idx, char *buf) +{ + struct idxmap *im; + + if (idx == 0) + return "*"; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->name; + snprintf(buf, 16, "if%d", idx); + return buf; +} + + +const char *ll_index_to_name(int idx) +{ + static char nbuf[16]; + + return ll_idx_n2a(idx, nbuf); +} + +int ll_index_to_type(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return -1; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->type; + return -1; +} + +unsigned ll_index_to_flags(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return 0; + + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->flags; + return 0; +} + +int ll_name_to_index(char *name) +{ + static char ncache[16]; + static int icache; + struct idxmap *im; + int sock_fd; + int i; + + if (name == NULL) + return 0; + if (icache && strcmp(name, ncache) == 0) + return icache; + for (i=0; i<16; i++) { + for (im = idxmap[i]; im; im = im->next) { + if (strcmp(im->name, name) == 0) { + icache = im->index; + strcpy(ncache, name); + return im->index; + } + } + } + /* We have not found the interface in our cache, but the kernel + * may still know about it. One reason is that we may be using + * module on-demand loading, which means that the kernel will + * load the module and make the interface exist only when + * we explicitely request it (check for dev_load() in net/core/dev.c). + * I can think of other similar scenario, but they are less common... + * Jean II */ + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd) { + struct ifreq ifr; + int ret; + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifindex = -1; + ret = ioctl(sock_fd, SIOCGIFINDEX, &ifr); + close(sock_fd); + if (ret >= 0) + /* In theory, we should redump the interface list + * to update our cache, this is left as an exercise + * to the reader... Jean II */ + return ifr.ifr_ifindex; + } + + return 0; +} + +int ll_init_map(struct rtnl_handle *rth) +{ + if (rtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(rth, ll_remember_index, &idxmap, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + return 0; +} diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h new file mode 100644 index 0000000..226d48f --- /dev/null +++ b/networking/libiproute/ll_map.h @@ -0,0 +1,13 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LL_MAP_H__ +#define __LL_MAP_H__ 1 + +extern int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ll_init_map(struct rtnl_handle *rth); +extern int ll_name_to_index(char *name); +extern const char *ll_index_to_name(int idx); +extern const char *ll_idx_n2a(int idx, char *buf); +extern int ll_index_to_type(int idx); +extern unsigned ll_index_to_flags(int idx); + +#endif /* __LL_MAP_H__ */ diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c new file mode 100644 index 0000000..a3fe9d3 --- /dev/null +++ b/networking/libiproute/ll_proto.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_proto.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include "libbb.h" +#include <string.h> + +#include "rt_names.h" +#include "utils.h" + +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include <net/ethernet.h> +#else +#include <linux/if_ether.h> +#endif + +#define __PF(f,n) { ETH_P_##f, #n }, +static struct { + int id; + char *name; +} llproto_names[] = { +__PF(LOOP,loop) +__PF(PUP,pup) +#ifdef ETH_P_PUPAT +__PF(PUPAT,pupat) +#endif +__PF(IP,ip) +__PF(X25,x25) +__PF(ARP,arp) +__PF(BPQ,bpq) +#ifdef ETH_P_IEEEPUP +__PF(IEEEPUP,ieeepup) +#endif +#ifdef ETH_P_IEEEPUPAT +__PF(IEEEPUPAT,ieeepupat) +#endif +__PF(DEC,dec) +__PF(DNA_DL,dna_dl) +__PF(DNA_RC,dna_rc) +__PF(DNA_RT,dna_rt) +__PF(LAT,lat) +__PF(DIAG,diag) +__PF(CUST,cust) +__PF(SCA,sca) +__PF(RARP,rarp) +__PF(ATALK,atalk) +__PF(AARP,aarp) +__PF(IPX,ipx) +__PF(IPV6,ipv6) +#ifdef ETH_P_PPP_DISC +__PF(PPP_DISC,ppp_disc) +#endif +#ifdef ETH_P_PPP_SES +__PF(PPP_SES,ppp_ses) +#endif +#ifdef ETH_P_ATMMPOA +__PF(ATMMPOA,atmmpoa) +#endif +#ifdef ETH_P_ATMFATE +__PF(ATMFATE,atmfate) +#endif + +__PF(802_3,802_3) +__PF(AX25,ax25) +__PF(ALL,all) +__PF(802_2,802_2) +__PF(SNAP,snap) +__PF(DDCMP,ddcmp) +__PF(WAN_PPP,wan_ppp) +__PF(PPP_MP,ppp_mp) +__PF(LOCALTALK,localtalk) +__PF(PPPTALK,ppptalk) +__PF(TR_802_2,tr_802_2) +__PF(MOBITEX,mobitex) +__PF(CONTROL,control) +__PF(IRDA,irda) +#ifdef ETH_P_ECONET +__PF(ECONET,econet) +#endif + +{ 0x8100, "802.1Q" }, +{ ETH_P_IP, "ipv4" }, +}; +#undef __PF + + +const char * ll_proto_n2a(unsigned short id, char *buf, int len) +{ + int i; + + id = ntohs(id); + + for (i=0; i<sizeof(llproto_names)/sizeof(llproto_names[0]); i++) { + if (llproto_names[i].id == id) + return llproto_names[i].name; + } + snprintf(buf, len, "[%d]", id); + return buf; +} + +int ll_proto_a2n(unsigned short *id, char *buf) +{ + int i; + for (i=0; i<sizeof(llproto_names)/sizeof(llproto_names[0]); i++) { + if (strcasecmp(llproto_names[i].name, buf) == 0) { + *id = htons(llproto_names[i].id); + return 0; + } + } + if (get_u16(id, buf, 0)) + return -1; + *id = htons(*id); + return 0; +} diff --git a/networking/libiproute/ll_types.c b/networking/libiproute/ll_types.c new file mode 100644 index 0000000..a53c894 --- /dev/null +++ b/networking/libiproute/ll_types.c @@ -0,0 +1,118 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_types.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ +#include <stdio.h> +#include <arpa/inet.h> + +#include <linux/if_arp.h> + +#include "rt_names.h" + +const char * ll_type_n2a(int type, char *buf, int len) +{ +#define __PF(f,n) { ARPHRD_##f, #n }, +static struct { + int type; + char *name; +} arphrd_names[] = { +{ 0, "generic" }, +__PF(ETHER,ether) +__PF(EETHER,eether) +__PF(AX25,ax25) +__PF(PRONET,pronet) +__PF(CHAOS,chaos) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802,ieee802) +#else +__PF(IEEE802,tr) +#endif +__PF(ARCNET,arcnet) +__PF(APPLETLK,atalk) +__PF(DLCI,dlci) +#ifdef ARPHRD_ATM +__PF(ATM,atm) +#endif +__PF(METRICOM,metricom) +#ifdef ARPHRD_IEEE1394 +__PF(IEEE1394,ieee1394) +#endif + +__PF(SLIP,slip) +__PF(CSLIP,cslip) +__PF(SLIP6,slip6) +__PF(CSLIP6,cslip6) +__PF(RSRVD,rsrvd) +__PF(ADAPT,adapt) +__PF(ROSE,rose) +__PF(X25,x25) +#ifdef ARPHRD_HWX25 +__PF(HWX25,hwx25) +#endif +__PF(PPP,ppp) +__PF(HDLC,hdlc) +__PF(LAPB,lapb) +#ifdef ARPHRD_DDCMP +__PF(DDCMP,ddcmp) +__PF(RAWHDLC,rawhdlc) +#endif + +__PF(TUNNEL,ipip) +__PF(TUNNEL6,tunnel6) +__PF(FRAD,frad) +__PF(SKIP,skip) +__PF(LOOPBACK,loopback) +__PF(LOCALTLK,ltalk) +__PF(FDDI,fddi) +__PF(BIF,bif) +__PF(SIT,sit) +__PF(IPDDP,ip/ddp) +__PF(IPGRE,gre) +__PF(PIMREG,pimreg) +__PF(HIPPI,hippi) +__PF(ASH,ash) +__PF(ECONET,econet) +__PF(IRDA,irda) +__PF(FCPP,fcpp) +__PF(FCAL,fcal) +__PF(FCPL,fcpl) +__PF(FCFABRIC,fcfb0) +__PF(FCFABRIC+1,fcfb1) +__PF(FCFABRIC+2,fcfb2) +__PF(FCFABRIC+3,fcfb3) +__PF(FCFABRIC+4,fcfb4) +__PF(FCFABRIC+5,fcfb5) +__PF(FCFABRIC+6,fcfb6) +__PF(FCFABRIC+7,fcfb7) +__PF(FCFABRIC+8,fcfb8) +__PF(FCFABRIC+9,fcfb9) +__PF(FCFABRIC+10,fcfb10) +__PF(FCFABRIC+11,fcfb11) +__PF(FCFABRIC+12,fcfb12) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802_TR,tr) +#endif +#ifdef ARPHRD_IEEE80211 +__PF(IEEE80211,ieee802.11) +#endif +#ifdef ARPHRD_VOID +__PF(VOID,void) +#endif +}; +#undef __PF + + int i; + for (i=0; i<sizeof(arphrd_names)/sizeof(arphrd_names[0]); i++) { + if (arphrd_names[i].type == type) + return arphrd_names[i].name; + } + snprintf(buf, len, "[%d]", type); + return buf; +} diff --git a/networking/libiproute/rt_names.c b/networking/libiproute/rt_names.c new file mode 100644 index 0000000..ed21fbe --- /dev/null +++ b/networking/libiproute/rt_names.c @@ -0,0 +1,384 @@ +/* vi: set sw=4 ts=4: */ +/* + * rt_names.c rtnetlink names DB. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <stdint.h> +#include "rt_names.h" + +static void rtnl_tab_initialize(char *file, const char **tab, int size) +{ + char buf[512]; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) + return; + while (fgets(buf, sizeof(buf), fp)) { + char *p = buf; + int id; + char namebuf[512]; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '#' || *p == '\n' || *p == 0) + continue; + if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2 && + sscanf(p, "0x%x %s #", &id, namebuf) != 2 && + sscanf(p, "%d %s\n", &id, namebuf) != 2 && + sscanf(p, "%d %s #", &id, namebuf) != 2) { + fprintf(stderr, "Database %s is corrupted at %s\n", + file, p); + return; + } + + if (id<0 || id>size) + continue; + + tab[id] = strdup(namebuf); + } + fclose(fp); +} + + +static const char * rtnl_rtprot_tab[256] = { + "none", + "redirect", + "kernel", + "boot", + "static", + NULL, + NULL, + NULL, + "gated", + "ra", + "mrt", + "zebra", + "bird", +}; + + + +static int rtnl_rtprot_init; + +static void rtnl_rtprot_initialize(void) +{ + rtnl_rtprot_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_protos", + rtnl_rtprot_tab, 256); +} + +const char * rtnl_rtprot_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtprot_tab[id]) { + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + } + if (rtnl_rtprot_tab[id]) + return rtnl_rtprot_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtprot_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtprot_tab[i] && + strcmp(rtnl_rtprot_tab[i], arg) == 0) { + cache = rtnl_rtprot_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtscope_tab[256] = { + "global", +}; + +static int rtnl_rtscope_init; + +static void rtnl_rtscope_initialize(void) +{ + rtnl_rtscope_init = 1; + rtnl_rtscope_tab[255] = "nowhere"; + rtnl_rtscope_tab[254] = "host"; + rtnl_rtscope_tab[253] = "link"; + rtnl_rtscope_tab[200] = "site"; + rtnl_tab_initialize("/etc/iproute2/rt_scopes", + rtnl_rtscope_tab, 256); +} + +const char * rtnl_rtscope_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtscope_tab[id]) { + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + } + if (rtnl_rtscope_tab[id]) + return rtnl_rtscope_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtscope_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtscope_tab[i] && + strcmp(rtnl_rtscope_tab[i], arg) == 0) { + cache = rtnl_rtscope_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtrealm_tab[256] = { + "unknown", +}; + +static int rtnl_rtrealm_init; + +static void rtnl_rtrealm_initialize(void) +{ + rtnl_rtrealm_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_realms", + rtnl_rtrealm_tab, 256); +} + +int rtnl_rtrealm_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtrealm_tab[i] && + strcmp(rtnl_rtrealm_tab[i], arg) == 0) { + cache = rtnl_rtrealm_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +const char * rtnl_rtrealm_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtrealm_tab[id]) { + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + } + if (rtnl_rtrealm_tab[id]) + return rtnl_rtrealm_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} +#endif + +static const char * rtnl_rtdsfield_tab[256] = { + "0", +}; + +static int rtnl_rtdsfield_init; + +static void rtnl_rtdsfield_initialize(void) +{ + rtnl_rtdsfield_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_dsfield", + rtnl_rtdsfield_tab, 256); +} + +const char * rtnl_dsfield_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtdsfield_tab[id]) { + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + } + if (rtnl_rtdsfield_tab[id]) + return rtnl_rtdsfield_tab[id]; + snprintf(buf, len, "0x%02x", id); + return buf; +} + + +int rtnl_dsfield_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtdsfield_tab[i] && + strcmp(rtnl_rtdsfield_tab[i], arg) == 0) { + cache = rtnl_rtdsfield_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 16); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +static int rtnl_rttable_init; +static const char * rtnl_rttable_tab[256] = { + "unspec", +}; +static void rtnl_rttable_initialize(void) +{ + rtnl_rttable_init = 1; + rtnl_rttable_tab[255] = "local"; + rtnl_rttable_tab[254] = "main"; + rtnl_rttable_tab[253] = "default"; + rtnl_tab_initialize("/etc/iproute2/rt_tables", rtnl_rttable_tab, 256); +} + +const char *rtnl_rttable_n2a(int id, char *buf, int len) +{ + if (id < 0 || id >= 256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rttable_tab[id]) { + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + } + if (rtnl_rttable_tab[id]) + return rtnl_rttable_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rttable_a2n(uint32_t * id, char *arg) +{ + static char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + + for (i = 0; i < 256; i++) { + if (rtnl_rttable_tab[i] && strcmp(rtnl_rttable_tab[i], arg) == 0) { + cache = (char*)rtnl_rttable_tab[i]; + res = i; + *id = res; + return 0; + } + } + + i = strtoul(arg, &end, 0); + if (!end || end == arg || *end || i > 255) + return -1; + *id = i; + return 0; +} + +#endif diff --git a/networking/libiproute/rt_names.h b/networking/libiproute/rt_names.h new file mode 100644 index 0000000..92b807f --- /dev/null +++ b/networking/libiproute/rt_names.h @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +#ifndef RT_NAMES_H_ +#define RT_NAMES_H_ 1 + +#include <stdint.h> + +extern const char* rtnl_rtprot_n2a(int id, char *buf, int len); +extern const char* rtnl_rtscope_n2a(int id, char *buf, int len); +extern const char* rtnl_rtrealm_n2a(int id, char *buf, int len); +extern const char* rtnl_dsfield_n2a(int id, char *buf, int len); +extern const char* rtnl_rttable_n2a(int id, char *buf, int len); +extern int rtnl_rtprot_a2n(uint32_t *id, char *arg); +extern int rtnl_rtscope_a2n(uint32_t *id, char *arg); +extern int rtnl_rtrealm_a2n(uint32_t *id, char *arg); +extern int rtnl_dsfield_a2n(uint32_t *id, char *arg); +extern int rtnl_rttable_a2n(uint32_t *id, char *arg); + + +extern const char * ll_type_n2a(int type, char *buf, int len); + +extern const char *ll_addr_n2a(unsigned char *addr, int alen, int type, + char *buf, int blen); +extern int ll_addr_a2n(unsigned char *lladdr, int len, char *arg); + +extern const char * ll_proto_n2a(unsigned short id, char *buf, int len); +extern int ll_proto_a2n(unsigned short *id, char *buf); + + +#endif diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c new file mode 100644 index 0000000..8aaee4b --- /dev/null +++ b/networking/libiproute/rtm_map.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * rtm_map.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + */ + +#include <stdlib.h> +#include <string.h> + +#include "rt_names.h" +#include "utils.h" + +char *rtnl_rtntype_n2a(int id, char *buf, int len) +{ + switch (id) { + case RTN_UNSPEC: + return "none"; + case RTN_UNICAST: + return "unicast"; + case RTN_LOCAL: + return "local"; + case RTN_BROADCAST: + return "broadcast"; + case RTN_ANYCAST: + return "anycast"; + case RTN_MULTICAST: + return "multicast"; + case RTN_BLACKHOLE: + return "blackhole"; + case RTN_UNREACHABLE: + return "unreachable"; + case RTN_PROHIBIT: + return "prohibit"; + case RTN_THROW: + return "throw"; + case RTN_NAT: + return "nat"; + case RTN_XRESOLVE: + return "xresolve"; + default: + snprintf(buf, len, "%d", id); + return buf; + } +} + + +int rtnl_rtntype_a2n(int *id, char *arg) +{ + char *end; + unsigned long res; + + if (strcmp(arg, "local") == 0) + res = RTN_LOCAL; + else if (strcmp(arg, "nat") == 0) + res = RTN_NAT; + else if (matches(arg, "broadcast") == 0 || + strcmp(arg, "brd") == 0) + res = RTN_BROADCAST; + else if (matches(arg, "anycast") == 0) + res = RTN_ANYCAST; + else if (matches(arg, "multicast") == 0) + res = RTN_MULTICAST; + else if (matches(arg, "prohibit") == 0) + res = RTN_PROHIBIT; + else if (matches(arg, "unreachable") == 0) + res = RTN_UNREACHABLE; + else if (matches(arg, "blackhole") == 0) + res = RTN_BLACKHOLE; + else if (matches(arg, "xresolve") == 0) + res = RTN_XRESOLVE; + else if (matches(arg, "unicast") == 0) + res = RTN_UNICAST; + else if (strcmp(arg, "throw") == 0) + res = RTN_THROW; + else { + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + } + *id = res; + return 0; +} + +int get_rt_realms(__u32 *realms, char *arg) +{ + __u32 realm = 0; + char *p = strchr(arg, '/'); + + *realms = 0; + if (p) { + *p = 0; + if (rtnl_rtrealm_a2n(realms, arg)) { + *p = '/'; + return -1; + } + *realms <<= 16; + *p = '/'; + arg = p+1; + } + if (*arg && rtnl_rtrealm_a2n(&realm, arg)) + return -1; + *realms |= realm; + return 0; +} diff --git a/networking/libiproute/rtm_map.h b/networking/libiproute/rtm_map.h new file mode 100644 index 0000000..1ffb940 --- /dev/null +++ b/networking/libiproute/rtm_map.h @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __RTM_MAP_H__ +#define __RTM_MAP_H__ 1 + +char *rtnl_rtntype_n2a(int id, char *buf, int len); +int rtnl_rtntype_a2n(int *id, char *arg); + +int get_rt_realms(__u32 *realms, char *arg); + + +#endif /* __RTM_MAP_H__ */ diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c new file mode 100644 index 0000000..fc6aff1 --- /dev/null +++ b/networking/libiproute/utils.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * utils.c + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * Changes: + * + * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses + */ + +#include "libbb.h" + +#include <string.h> +#include <unistd.h> + +#include "utils.h" +#include "inet_common.h" + +int get_integer(int *val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN) + return -1; + *val = res; + return 0; +} + +int get_unsigned(unsigned *val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > UINT_MAX) + return -1; + *val = res; + return 0; +} + +int get_u32(__u32 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL) + return -1; + *val = res; + return 0; +} + +int get_u16(__u16 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFF) + return -1; + *val = res; + return 0; +} + +int get_u8(__u8 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFF) + return -1; + *val = res; + return 0; +} + +int get_s16(__s16 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000) + return -1; + *val = res; + return 0; +} + +int get_s8(__s8 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80) + return -1; + *val = res; + return 0; +} + +int get_addr_1(inet_prefix * addr, char *name, int family) +{ + char *cp; + unsigned char *ap = (unsigned char *) addr->data; + int i; + + memset(addr, 0, sizeof(*addr)); + + if (strcmp(name, bb_str_default) == 0 || + strcmp(name, "all") == 0 || strcmp(name, "any") == 0) { + addr->family = family; + addr->bytelen = (family == AF_INET6 ? 16 : 4); + addr->bitlen = -1; + return 0; + } + + if (strchr(name, ':')) { + addr->family = AF_INET6; + if (family != AF_UNSPEC && family != AF_INET6) + return -1; + if (inet_pton(AF_INET6, name, addr->data) <= 0) + return -1; + addr->bytelen = 16; + addr->bitlen = -1; + return 0; + } + + addr->family = AF_INET; + if (family != AF_UNSPEC && family != AF_INET) + return -1; + addr->bytelen = 4; + addr->bitlen = -1; + for (cp = name, i = 0; *cp; cp++) { + if (*cp <= '9' && *cp >= '0') { + ap[i] = 10 * ap[i] + (*cp - '0'); + continue; + } + if (*cp == '.' && ++i <= 3) + continue; + return -1; + } + return 0; +} + +int get_prefix_1(inet_prefix * dst, char *arg, int family) +{ + int err; + int plen; + char *slash; + + memset(dst, 0, sizeof(*dst)); + + if (strcmp(arg, bb_str_default) == 0 || strcmp(arg, "any") == 0) { + dst->family = family; + dst->bytelen = 0; + dst->bitlen = 0; + return 0; + } + + slash = strchr(arg, '/'); + if (slash) + *slash = 0; + err = get_addr_1(dst, arg, family); + if (err == 0) { + switch (dst->family) { + case AF_INET6: + dst->bitlen = 128; + break; + default: + case AF_INET: + dst->bitlen = 32; + } + if (slash) { + if (get_integer(&plen, slash + 1, 0) || plen > dst->bitlen) { + err = -1; + goto done; + } + dst->bitlen = plen; + } + } + done: + if (slash) + *slash = '/'; + return err; +} + +int get_addr(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_addr_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +int get_prefix(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_prefix_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +__u32 get_addr32(char *name) +{ + inet_prefix addr; + + if (get_addr_1(&addr, name, AF_INET)) { + bb_error_msg_and_die("an IP address is expected rather than \"%s\"", name); + } + return addr.data[0]; +} + +void incomplete_command(void) +{ + bb_error_msg("command line is not complete, try option \"help\""); + exit(-1); +} + +void invarg(const char * const arg, const char * const opt) +{ + bb_error_msg(bb_msg_invalid_arg, arg, opt); + exit(-1); +} + +void duparg(char *key, char *arg) +{ + bb_error_msg("duplicate \"%s\": \"%s\" is the second value", key, arg); + exit(-1); +} + +void duparg2(char *key, char *arg) +{ + bb_error_msg("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg); + exit(-1); +} + +int matches(char *cmd, char *pattern) +{ + int len = strlen(cmd); + + return strncmp(pattern, cmd, len); +} + +int inet_addr_match(inet_prefix * a, inet_prefix * b, int bits) +{ + __u32 *a1 = a->data; + __u32 *a2 = b->data; + int words = bits >> 0x05; + + bits &= 0x1f; + + if (words) + if (memcmp(a1, a2, words << 2)) + return -1; + + if (bits) { + __u32 w1, w2; + __u32 mask; + + w1 = a1[words]; + w2 = a2[words]; + + mask = htonl((0xffffffff) << (0x20 - bits)); + + if ((w1 ^ w2) & mask) + return 1; + } + + return 0; +} + +int __iproute2_hz_internal; + +int __get_hz(void) +{ + int hz = 0; + FILE *fp = fopen("/proc/net/psched", "r"); + + if (fp) { + unsigned nom, denom; + + if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) + if (nom == 1000000) + hz = denom; + fclose(fp); + } + if (hz) + return hz; + return sysconf(_SC_CLK_TCK); +} + +const char *rt_addr_n2a(int af, int ATTRIBUTE_UNUSED len, + void *addr, char *buf, int buflen) +{ + switch (af) { + case AF_INET: + case AF_INET6: + return inet_ntop(af, addr, buf, buflen); + default: + return "???"; + } +} + + +const char *format_host(int af, int len, void *addr, char *buf, int buflen) +{ +#ifdef RESOLVE_HOSTNAMES + if (resolve_hosts) { + struct hostent *h_ent; + + if (len <= 0) { + switch (af) { + case AF_INET: + len = 4; + break; + case AF_INET6: + len = 16; + break; + default:; + } + } + if (len > 0 && (h_ent = gethostbyaddr(addr, len, af)) != NULL) { + snprintf(buf, buflen - 1, "%s", h_ent->h_name); + return buf; + } + } +#endif + return rt_addr_n2a(af, len, addr, buf, buflen); +} diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h new file mode 100644 index 0000000..0b0d754 --- /dev/null +++ b/networking/libiproute/utils.h @@ -0,0 +1,103 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UTILS_H__ +#define __UTILS_H__ 1 + +#include "libbb.h" +#include <asm/types.h> +#include <resolv.h> + +#include "libnetlink.h" +#include "ll_map.h" +#include "rtm_map.h" + +extern int preferred_family; +extern int show_stats; +extern int show_details; +extern int show_raw; +extern int resolve_hosts; +extern int oneline; +extern char * _SL_; + +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif + +#define SPRINT_BSIZE 64 +#define SPRINT_BUF(x) char x[SPRINT_BSIZE] + +extern void incomplete_command(void) ATTRIBUTE_NORETURN; + +#define NEXT_ARG() do { argv++; if (--argc <= 0) incomplete_command(); } while(0) + +typedef struct +{ + __u8 family; + __u8 bytelen; + __s16 bitlen; + __u32 data[4]; +} inet_prefix; + +#define DN_MAXADDL 20 +#ifndef AF_DECnet +#define AF_DECnet 12 +#endif + +struct dn_naddr +{ + unsigned short a_len; + unsigned char a_addr[DN_MAXADDL]; +}; + +#define IPX_NODE_LEN 6 + +struct ipx_addr { + uint32_t ipx_net; + uint8_t ipx_node[IPX_NODE_LEN]; +}; + +extern __u32 get_addr32(char *name); +extern int get_addr_1(inet_prefix *dst, char *arg, int family); +extern int get_prefix_1(inet_prefix *dst, char *arg, int family); +extern int get_addr(inet_prefix *dst, char *arg, int family); +extern int get_prefix(inet_prefix *dst, char *arg, int family); + +extern int get_integer(int *val, char *arg, int base); +extern int get_unsigned(unsigned *val, char *arg, int base); +#define get_byte get_u8 +#define get_ushort get_u16 +#define get_short get_s16 +extern int get_u32(__u32 *val, char *arg, int base); +extern int get_u16(__u16 *val, char *arg, int base); +extern int get_s16(__s16 *val, char *arg, int base); +extern int get_u8(__u8 *val, char *arg, int base); +extern int get_s8(__s8 *val, char *arg, int base); + +extern const char *format_host(int af, int len, void *addr, char *buf, int buflen); +extern const char *rt_addr_n2a(int af, int len, void *addr, char *buf, int buflen); + +void invarg(const char * const, const char * const) ATTRIBUTE_NORETURN; +void duparg(char *, char *) ATTRIBUTE_NORETURN; +void duparg2(char *, char *) ATTRIBUTE_NORETURN; +int matches(char *arg, char *pattern); +extern int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits); + +const char *dnet_ntop(int af, const void *addr, char *str, size_t len); +int dnet_pton(int af, const char *src, void *addr); + +const char *ipx_ntop(int af, const void *addr, char *str, size_t len); +int ipx_pton(int af, const char *src, void *addr); + +extern int __iproute2_hz_internal; +extern int __get_hz(void); + +static __inline__ int get_hz(void) +{ + if (__iproute2_hz_internal == 0) + __iproute2_hz_internal = __get_hz(); + return __iproute2_hz_internal; +} + +#endif /* __UTILS_H__ */ diff --git a/networking/nameif.c b/networking/nameif.c new file mode 100644 index 0000000..52aad28 --- /dev/null +++ b/networking/nameif.c @@ -0,0 +1,170 @@ +/* vi: set sw=4 ts=4: */ +/* + * nameif.c - Naming Interfaces based on MAC address for busybox. + * + * Written 2000 by Andi Kleen. + * Busybox port 2002 by Nick Fedchik <nick@fedchik.org.ua> + * Glenn McGrath <bug1@iinet.net.au> + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <syslog.h> +#include <net/if.h> +#include <netinet/ether.h> + + +/* Older versions of net/if.h do not appear to define IF_NAMESIZE. */ +#ifndef IF_NAMESIZE +# ifdef IFNAMSIZ +# define IF_NAMESIZE IFNAMSIZ +# else +# define IF_NAMESIZE 16 +# endif +#endif + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +/* Octets in one Ethernet addr, from <linux/if_ether.h> */ +#define ETH_ALEN 6 + +#ifndef ifr_newname +#define ifr_newname ifr_ifru.ifru_slave +#endif + +typedef struct mactable_s { + struct mactable_s *next; + struct mactable_s *prev; + char *ifname; + struct ether_addr *mac; +} mactable_t; + +/* Check ascii str_macaddr, convert and copy to *mac */ +static struct ether_addr *cc_macaddr(const char *str_macaddr) +{ + struct ether_addr *lmac, *mac; + + lmac = ether_aton(str_macaddr); + if (lmac == NULL) + bb_error_msg_and_die("cannot parse MAC %s", str_macaddr); + mac = xmalloc(ETH_ALEN); + memcpy(mac, lmac, ETH_ALEN); + + return mac; +} + +int nameif_main(int argc, char **argv) +{ + mactable_t *clist = NULL; + FILE *ifh; + const char *fname = "/etc/mactab"; + char *line; + int ctl_sk; + int if_index = 1; + mactable_t *ch; + + if (1 & getopt32(argc, argv, "sc:", &fname)) { + openlog(applet_name, 0, LOG_LOCAL0); + logmode = LOGMODE_SYSLOG; + } + + if ((argc - optind) & 1) + bb_show_usage(); + + if (optind < argc) { + char **a = argv + optind; + + while (*a) { + if (strlen(*a) > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", *a); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrdup(*a++); + ch->mac = cc_macaddr(*a++); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + } + } else { + ifh = xfopen(fname, "r"); + + while ((line = xmalloc_fgets(ifh)) != NULL) { + char *line_ptr; + size_t name_length; + + line_ptr = line + strspn(line, " \t"); + if ((line_ptr[0] == '#') || (line_ptr[0] == '\n')) { + free(line); + continue; + } + name_length = strcspn(line_ptr, " \t"); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrndup(line_ptr, name_length); + if (name_length > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", ch->ifname); + line_ptr += name_length; + line_ptr += strspn(line_ptr, " \t"); + name_length = strspn(line_ptr, "0123456789ABCDEFabcdef:"); + line_ptr[name_length] = '\0'; + ch->mac = cc_macaddr(line_ptr); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + free(line); + } + fclose(ifh); + } + + ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0); + + while (clist) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(struct ifreq)); + if_index++; + ifr.ifr_ifindex = if_index; + + /* Get ifname by index or die */ + if (ioctl(ctl_sk, SIOCGIFNAME, &ifr)) + break; + + /* Has this device hwaddr? */ + if (ioctl(ctl_sk, SIOCGIFHWADDR, &ifr)) + continue; + + /* Search for mac like in ifr.ifr_hwaddr.sa_data */ + for (ch = clist; ch; ch = ch->next) + if (!memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN)) + break; + + /* Nothing found for current ifr.ifr_hwaddr.sa_data */ + if (ch == NULL) + continue; + + strcpy(ifr.ifr_newname, ch->ifname); + if (ioctl(ctl_sk, SIOCSIFNAME, &ifr) < 0) + bb_perror_msg_and_die("cannot change ifname %s to %s", + ifr.ifr_name, ch->ifname); + + /* Remove list entry of renamed interface */ + if (ch->prev != NULL) { + (ch->prev)->next = ch->next; + } else { + clist = ch->next; + } + if (ch->next != NULL) + (ch->next)->prev = ch->prev; + if (ENABLE_FEATURE_CLEAN_UP) { + free(ch->ifname); + free(ch->mac); + free(ch); + } + } + + return 0; +} diff --git a/networking/nc.c b/networking/nc.c new file mode 100644 index 0000000..5fd9242 --- /dev/null +++ b/networking/nc.c @@ -0,0 +1,201 @@ +/* vi: set sw=4 ts=4: */ +/* nc: mini-netcat - built from the ground up for LRP + * + * Copyright (C) 1998, 1999 Charles P. Wright + * Copyright (C) 1998 Dave Cinege + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void timeout(int signum) +{ + bb_error_msg_and_die("timed out"); +} + +int nc_main(int argc, char **argv) +{ + int sfd = 0; + int cfd = 0; + SKIP_NC_SERVER(const) unsigned do_listen = 0; + SKIP_NC_SERVER(const) unsigned lport = 0; + SKIP_NC_EXTRA (const) unsigned wsecs = 0; + SKIP_NC_EXTRA (const) unsigned delay = 0; + SKIP_NC_EXTRA (const int execparam = 0;) + USE_NC_EXTRA (char **execparam = NULL;) + struct sockaddr_in address; + fd_set readfds, testfds; + int opt; /* must be signed (getopt returns -1) */ + + memset(&address, 0, sizeof(address)); + + if (ENABLE_NC_SERVER || ENABLE_NC_EXTRA) { + /* getopt32 is _almost_ usable: + ** it cannot handle "... -e prog -prog-opt" */ + while ((opt = getopt(argc, argv, + "" USE_NC_SERVER("lp:") USE_NC_EXTRA("w:i:f:e:") )) > 0 + ) { + if (ENABLE_NC_SERVER && opt=='l') USE_NC_SERVER(do_listen++); + else if (ENABLE_NC_SERVER && opt=='p') USE_NC_SERVER(lport = bb_lookup_port(optarg, "tcp", 0)); + else if (ENABLE_NC_EXTRA && opt=='w') USE_NC_EXTRA( wsecs = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='i') USE_NC_EXTRA( delay = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='f') USE_NC_EXTRA( cfd = xopen(optarg, O_RDWR)); + else if (ENABLE_NC_EXTRA && opt=='e' && optind<=argc) { + /* We cannot just 'break'. We should let getopt finish. + ** Or else we won't be able to find where + ** 'host' and 'port' params are + ** (think "nc -w 60 host port -e prog"). */ + USE_NC_EXTRA( + char **p; + // +2: one for progname (optarg) and one for NULL + execparam = xzalloc(sizeof(char*) * (argc - optind + 2)); + p = execparam; + *p++ = optarg; + while (optind < argc) { + *p++ = argv[optind++]; + } + ) + /* optind points to argv[arvc] (NULL) now. + ** FIXME: we assume that getopt will not count options + ** possibly present on "-e prog args" and will not + ** include them into final value of optind + ** which is to be used ... */ + } else bb_show_usage(); + } + argv += optind; /* ... here! */ + argc -= optind; + // -l and -f don't mix + if (do_listen && cfd) bb_show_usage(); + // Listen or file modes need zero arguments, client mode needs 2 + opt = ((do_listen || cfd) ? 0 : 2); + if (argc != opt) + bb_show_usage(); + } else { + if (argc != 3) bb_show_usage(); + argc--; + argv++; + } + + if (wsecs) { + signal(SIGALRM, timeout); + alarm(wsecs); + } + + if (!cfd) { + sfd = xsocket(AF_INET, SOCK_STREAM, 0); + fcntl(sfd, F_SETFD, FD_CLOEXEC); + setsockopt_reuseaddr(sfd); + address.sin_family = AF_INET; + + // Set local port. + + if (lport != 0) { + address.sin_port = lport; + xbind(sfd, (struct sockaddr *) &address, sizeof(address)); + } + + if (do_listen) { + socklen_t addrlen = sizeof(address); + + xlisten(sfd, do_listen); + + // If we didn't specify a port number, query and print it to stderr. + + if (!lport) { + socklen_t len = sizeof(address); + getsockname(sfd, &address, &len); + fdprintf(2, "%d\n", SWAP_BE16(address.sin_port)); + } + repeatyness: + cfd = accept(sfd, (struct sockaddr *) &address, &addrlen); + if (cfd < 0) + bb_perror_msg_and_die("accept"); + + if (!execparam) close(sfd); + } else { + struct hostent *hostinfo; + hostinfo = xgethostbyname(argv[0]); + + address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list; + address.sin_port = bb_lookup_port(argv[1], "tcp", 0); + + if (connect(sfd, (struct sockaddr *) &address, sizeof(address)) < 0) + bb_perror_msg_and_die("connect"); + cfd = sfd; + } + } + + if (wsecs) { + alarm(0); + signal(SIGALRM, SIG_DFL); + } + + /* -e given? */ + if (execparam) { + if (cfd) { + signal(SIGCHLD, SIG_IGN); + dup2(cfd, 0); + close(cfd); + } + dup2(0, 1); + dup2(0, 2); + + // With more than one -l, repeatedly act as server. + + if (do_listen > 1 && vfork()) { + // This is a bit weird as cleanup goes, since we wind up with no + // stdin/stdout/stderr. But it's small and shouldn't hurt anything. + // We check for cfd == 0 above. + logmode = LOGMODE_NONE; + close(0); + close(1); + close(2); + + goto repeatyness; + } + USE_NC_EXTRA(execvp(execparam[0], execparam);) + /* Don't print stuff or it will go over the wire.... */ + _exit(127); + } + + // Select loop copying stdin to cfd, and cfd to stdout. + + FD_ZERO(&readfds); + FD_SET(cfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); + + for (;;) { + int fd; + int ofd; + int nread; + + testfds = readfds; + + if (select(FD_SETSIZE, &testfds, NULL, NULL, NULL) < 0) + bb_perror_msg_and_die("select"); + + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &testfds)) { + nread = safe_read(fd, bb_common_bufsiz1, + sizeof(bb_common_bufsiz1)); + + if (fd == cfd) { + if (nread<1) exit(0); + ofd = STDOUT_FILENO; + } else { + if (nread<1) { + // Close outgoing half-connection so they get EOF, but + // leave incoming alone so we can see response. + shutdown(cfd, 1); + FD_CLR(STDIN_FILENO, &readfds); + } + ofd = cfd; + } + + xwrite(ofd, bb_common_bufsiz1, nread); + if (delay > 0) sleep(delay); + } + } + } +} diff --git a/networking/netstat.c b/networking/netstat.c new file mode 100644 index 0000000..3dad57a --- /dev/null +++ b/networking/netstat.c @@ -0,0 +1,613 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini netstat implementation(s) for busybox + * based in part on the netstat implementation from net-tools. + * + * Copyright (C) 2002 by Bart Visscher <magick@linux-fan.com> + * + * 2002-04-20 + * IPV6 support added by Bart Visscher <magick@linux-fan.com> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "inet_common.h" + +#ifdef CONFIG_ROUTE +extern void displayroutes(int noresolve, int netstatfmt); +#endif + +#define NETSTAT_CONNECTED 0x01 +#define NETSTAT_LISTENING 0x02 +#define NETSTAT_NUMERIC 0x04 +/* Must match getopt32 option string */ +#define NETSTAT_TCP 0x10 +#define NETSTAT_UDP 0x20 +#define NETSTAT_RAW 0x40 +#define NETSTAT_UNIX 0x80 +#define NETSTAT_ALLPROTO (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX) + +static int flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; + +#define PROGNAME_WIDTHs PROGNAME_WIDTH1(PROGNAME_WIDTH) +#define PROGNAME_WIDTH1(s) PROGNAME_WIDTH2(s) +#define PROGNAME_WIDTH2(s) #s + +#define PRG_HASH_SIZE 211 + +enum { + TCP_ESTABLISHED = 1, + TCP_SYN_SENT, + TCP_SYN_RECV, + TCP_FIN_WAIT1, + TCP_FIN_WAIT2, + TCP_TIME_WAIT, + TCP_CLOSE, + TCP_CLOSE_WAIT, + TCP_LAST_ACK, + TCP_LISTEN, + TCP_CLOSING /* now a valid state */ +}; + +static const char * const tcp_state[] = +{ + "", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT1", + "FIN_WAIT2", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN", + "CLOSING" +}; + +typedef enum { + SS_FREE = 0, /* not allocated */ + SS_UNCONNECTED, /* unconnected to any socket */ + SS_CONNECTING, /* in process of connecting */ + SS_CONNECTED, /* connected to socket */ + SS_DISCONNECTING /* in process of disconnecting */ +} socket_state; + +#define SO_ACCEPTCON (1<<16) /* performed a listen */ +#define SO_WAITDATA (1<<17) /* wait data to read */ +#define SO_NOSPACE (1<<18) /* no space to write */ + +static char *get_sname(int port, const char *proto, int num) +{ + char *str=itoa(ntohs(port)); + if (num) { + } else { + struct servent *se=getservbyport(port,proto); + if (se) + str=se->s_name; + } + if (!port) { + str="*"; + } + return str; +} + +static void snprint_ip_port(char *ip_port, int size, struct sockaddr *addr, int port, char *proto, int numeric) +{ + char *port_name; + +#ifdef CONFIG_FEATURE_IPV6 + if (addr->sa_family == AF_INET6) { + INET6_rresolve(ip_port, size, (struct sockaddr_in6 *)addr, + (numeric&NETSTAT_NUMERIC) ? 0x0fff : 0); + } else +#endif + { + INET_rresolve(ip_port, size, (struct sockaddr_in *)addr, + 0x4000 | ((numeric&NETSTAT_NUMERIC) ? 0x0fff : 0), + 0xffffffff); + } + port_name=get_sname(htons(port), proto, numeric); + if ((strlen(ip_port) + strlen(port_name)) > 22) + ip_port[22 - strlen(port_name)] = '\0'; + ip_port+=strlen(ip_port); + strcat(ip_port, ":"); + strcat(ip_port, port_name); +} + +static void tcp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + const char *state_str; + char more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus tcp line"); + return; + } + state_str = tcp_state[state]; + if ((rem_port && (flags&NETSTAT_CONNECTED)) || + (!rem_port && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "tcp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "tcp", flags&NETSTAT_NUMERIC); + + printf("tcp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void udp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + /* Demangle what the kernel gives us */ + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus udp line"); + return; + } + switch (state) { + case TCP_ESTABLISHED: + state_str = "ESTABLISHED"; + break; + + case TCP_CLOSE: + state_str = ""; + break; + + default: + state_str = "UNKNOWN"; + break; + } + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "udp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "udp", flags&NETSTAT_NUMERIC); + + printf("udp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void raw_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus raw line"); + return; + } + state_str=itoa(state); + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "raw", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "raw", flags&NETSTAT_NUMERIC); + + printf("raw %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +#define HAS_INODE 1 + +static void unix_do_one(int nr, const char *line) +{ + static int has = 0; + char path[PATH_MAX], ss_flags[32]; + char *ss_proto, *ss_state, *ss_type; + int num, state, type, inode; + void *d; + unsigned long refcnt, proto, unix_flags; + + if (nr == 0) { + if (strstr(line, "Inode")) + has |= HAS_INODE; + return; + } + path[0] = '\0'; + num = sscanf(line, "%p: %lX %lX %lX %X %X %d %s", + &d, &refcnt, &proto, &unix_flags, &type, &state, &inode, path); + if (num < 6) { + bb_error_msg("warning, got bogus unix line"); + return; + } + if (!(has & HAS_INODE)) + snprintf(path,sizeof(path),"%d",inode); + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))!=(NETSTAT_LISTENING|NETSTAT_CONNECTED)) { + if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) { + if (!(flags&NETSTAT_LISTENING)) + return; + } else { + if (!(flags&NETSTAT_CONNECTED)) + return; + } + } + + switch (proto) { + case 0: + ss_proto = "unix"; + break; + + default: + ss_proto = "??"; + } + + switch (type) { + case SOCK_STREAM: + ss_type = "STREAM"; + break; + + case SOCK_DGRAM: + ss_type = "DGRAM"; + break; + + case SOCK_RAW: + ss_type = "RAW"; + break; + + case SOCK_RDM: + ss_type = "RDM"; + break; + + case SOCK_SEQPACKET: + ss_type = "SEQPACKET"; + break; + + default: + ss_type = "UNKNOWN"; + } + + switch (state) { + case SS_FREE: + ss_state = "FREE"; + break; + + case SS_UNCONNECTED: + /* + * Unconnected sockets may be listening + * for something. + */ + if (unix_flags & SO_ACCEPTCON) { + ss_state = "LISTENING"; + } else { + ss_state = ""; + } + break; + + case SS_CONNECTING: + ss_state = "CONNECTING"; + break; + + case SS_CONNECTED: + ss_state = "CONNECTED"; + break; + + case SS_DISCONNECTING: + ss_state = "DISCONNECTING"; + break; + + default: + ss_state = "UNKNOWN"; + } + + strcpy(ss_flags, "[ "); + if (unix_flags & SO_ACCEPTCON) + strcat(ss_flags, "ACC "); + if (unix_flags & SO_WAITDATA) + strcat(ss_flags, "W "); + if (unix_flags & SO_NOSPACE) + strcat(ss_flags, "N "); + + strcat(ss_flags, "]"); + + printf("%-5s %-6ld %-11s %-10s %-13s ", + ss_proto, refcnt, ss_flags, ss_type, ss_state); + if (has & HAS_INODE) + printf("%-6d ",inode); + else + printf("- "); + puts(path); +} + +#define _PATH_PROCNET_UDP "/proc/net/udp" +#define _PATH_PROCNET_UDP6 "/proc/net/udp6" +#define _PATH_PROCNET_TCP "/proc/net/tcp" +#define _PATH_PROCNET_TCP6 "/proc/net/tcp6" +#define _PATH_PROCNET_RAW "/proc/net/raw" +#define _PATH_PROCNET_RAW6 "/proc/net/raw6" +#define _PATH_PROCNET_UNIX "/proc/net/unix" + +static void do_info(const char *file, const char *name, void (*proc)(int, const char *)) +{ + int lnr = 0; + FILE *procinfo; + + procinfo = fopen(file, "r"); + if (procinfo == NULL) { + if (errno != ENOENT) { + perror(file); + } else { + bb_error_msg("no support for '%s' on this system", name); + } + } else { + do { + char *buffer = xmalloc_fgets(procinfo); + if (buffer) { + (proc)(lnr++, buffer); + free(buffer); + } + } while (!feof(procinfo)); + fclose(procinfo); + } +} + +/* + * Our main function. + */ + +int netstat_main(int argc, char **argv) +{ + enum { + OPT_extended = 0x4, + OPT_showroute = 0x100, + }; + unsigned opt; +#ifdef CONFIG_FEATURE_IPV6 + int inet = 1; + int inet6 = 1; +#else +# define inet 1 +# define inet6 0 +#endif + + /* Option string must match NETSTAT_xxx constants */ + opt = getopt32(argc, argv, "laentuwxr"); + if (opt & 0x1) { // -l + flags &= ~NETSTAT_CONNECTED; + flags |= NETSTAT_LISTENING; + } + if (opt & 0x2) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a + //if (opt & 0x4) // -e + if (opt & 0x8) flags |= NETSTAT_NUMERIC; // -n + //if (opt & 0x10) // -t: NETSTAT_TCP + //if (opt & 0x20) // -u: NETSTAT_UDP + //if (opt & 0x40) // -w: NETSTAT_RAW + //if (opt & 0x80) // -x: NETSTAT_UNIX + if (opt & OPT_showroute) { // -r +#ifdef CONFIG_ROUTE + displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended)); + return 0; +#else + bb_error_msg_and_die("-r (display routing table) is not compiled in"); +#endif + } + + opt &= NETSTAT_ALLPROTO; + if (opt) { + flags &= ~NETSTAT_ALLPROTO; + flags |= opt; + } + if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) { + printf("Active Internet connections "); /* xxx */ + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags & NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + printf("\nProto Recv-Q Send-Q Local Address Foreign Address State\n"); + } + if (inet && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP,"AF INET (tcp)",tcp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP6,"AF INET6 (tcp)",tcp_do_one); +#endif + if (inet && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP,"AF INET (udp)",udp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP6,"AF INET6 (udp)",udp_do_one); +#endif + if (inet && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW,"AF INET (raw)",raw_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW6,"AF INET6 (raw)",raw_do_one); +#endif + if (flags&NETSTAT_UNIX) { + printf("Active UNIX domain sockets "); + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags&NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + + printf("\nProto RefCnt Flags Type State I-Node Path\n"); + do_info(_PATH_PROCNET_UNIX,"AF UNIX",unix_do_one); + } + return 0; +} diff --git a/networking/nslookup.c b/networking/nslookup.c new file mode 100644 index 0000000..cc5ff95 --- /dev/null +++ b/networking/nslookup.c @@ -0,0 +1,149 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini nslookup implementation for busybox + * + * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org> + * + * Correct default name server display and explicit name server option + * added by Ben Zeckel <bzeckel@hmc.edu> June 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <resolv.h> +#include "busybox.h" + +/* + * I'm only implementing non-interactive mode; + * I totally forgot nslookup even had an interactive mode. + */ + +/* Examples of 'standard' nslookup output + * $ nslookup yahoo.com + * Server: 128.193.0.10 + * Address: 128.193.0.10#53 + * + * Non-authoritative answer: + * Name: yahoo.com + * Address: 216.109.112.135 + * Name: yahoo.com + * Address: 66.94.234.13 + * + * $ nslookup 204.152.191.37 + * Server: 128.193.4.20 + * Address: 128.193.4.20#53 + * + * Non-authoritative answer: + * 37.191.152.204.in-addr.arpa canonical name = 37.32-27.191.152.204.in-addr.arpa. + * 37.32-27.191.152.204.in-addr.arpa name = zeus-pub2.kernel.org. + * + * Authoritative answers can be found from: + * 32-27.191.152.204.in-addr.arpa nameserver = ns1.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns2.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns3.kernel.org. + * ns1.kernel.org internet address = 140.211.167.34 + * ns2.kernel.org internet address = 204.152.191.4 + * ns3.kernel.org internet address = 204.152.191.36 + */ + +static int sockaddr_to_dotted(struct sockaddr *saddr, char *buf, int buflen) +{ + if (buflen <= 0) return -1; + buf[0] = '\0'; + if (saddr->sa_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in*)saddr)->sin_addr, buf, buflen); + return 0; + } + if (saddr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6*)saddr)->sin6_addr, buf, buflen); + return 0; + } + return -1; +} + +static int print_host(const char *hostname, const char *header) +{ + char str[128]; /* IPv6 address will fit, hostnames hopefully too */ + struct addrinfo *result = NULL; + int rc; + struct addrinfo hint; + + memset(&hint, 0 , sizeof(hint)); + /* hint.ai_family = AF_UNSPEC; - zero anyway */ + /* Needed. Or else we will get each address thrice (or more) + * for each possible socket type (tcp,udp,raw...): */ + hint.ai_socktype = SOCK_STREAM; + // hint.ai_flags = AI_CANONNAME; + rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result); + if (!rc) { + struct addrinfo *cur = result; + // printf("%s\n", cur->ai_canonname); ? + while (cur) { + sockaddr_to_dotted(cur->ai_addr, str, sizeof(str)); + printf("%s %s\nAddress: %s", header, hostname, str); + str[0] = ' '; + if (getnameinfo(cur->ai_addr, cur->ai_addrlen, str+1, sizeof(str)-1, NULL, 0, NI_NAMEREQD)) + str[0] = '\0'; + puts(str); + cur = cur->ai_next; + } + } else { + bb_error_msg("getaddrinfo('%s') failed: %s", hostname, gai_strerror(rc)); + } + freeaddrinfo(result); + return (rc != 0); +} + + +/* alter the global _res nameserver structure to use + an explicit dns server instead of what is in /etc/resolv.h */ +static void set_default_dns(char *server) +{ + struct in_addr server_in_addr; + + if (inet_pton(AF_INET, server, &server_in_addr) > 0) { + _res.nscount = 1; + _res.nsaddr_list[0].sin_addr = server_in_addr; + } +} + + +/* lookup the default nameserver and display it */ +static void server_print(void) +{ + char str[INET6_ADDRSTRLEN]; + + sockaddr_to_dotted((struct sockaddr*)&_res.nsaddr_list[0], str, sizeof(str)); + print_host(str, "Server:"); + puts(""); +} + + +int nslookup_main(int argc, char **argv) +{ + /* + * initialize DNS structure _res used in printing the default + * name server and in the explicit name server option feature. + */ + + res_init(); + + /* + * We allow 1 or 2 arguments. + * The first is the name to be looked up and the second is an + * optional DNS server with which to do the lookup. + * More than 3 arguments is an error to follow the pattern of the + * standard nslookup + */ + + if (argc < 2 || *argv[1] == '-' || argc > 3) + bb_show_usage(); + else if(argc == 3) + set_default_dns(argv[2]); + + server_print(); + return print_host(argv[1], "Name: "); +} + +/* $Id: nslookup.c,v 1.33 2004/10/13 07:25:01 andersen Exp $ */ diff --git a/networking/ping.c b/networking/ping.c new file mode 100644 index 0000000..658c015 --- /dev/null +++ b/networking/ping.c @@ -0,0 +1,440 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping.c,v 1.56 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * Adapted from the ping in netkit-base 0.10: + * Copyright (c) 1989 The Regents of the University of California. + * Derived from software contributed to Berkeley by Mike Muuss. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/file.h> +#include <sys/times.h> +#include <signal.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +/* common routines */ + +static int in_cksum(unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +#ifndef CONFIG_FEATURE_FANCY_PING + +/* simple version */ + +static char *hostname; + +static void noresp(int ign) +{ + printf("No response from %s\n", hostname); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct hostent *h; + struct sockaddr_in pingaddr; + struct icmp *pkt; + int pingsock, c; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + h = xgethostbyname(host); + memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr)); + hostname = h->h_name; + + pkt = (struct icmp *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + c = sendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN, 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (c < 0) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 76) { /* ip + icmp */ + struct iphdr *iphdr = (struct iphdr *) packet; + + pkt = (struct icmp *) (packet + (iphdr->ihl << 2)); /* skip ip hdr */ + if (pkt->icmp_type == ICMP_ECHOREPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", hostname); + return; +} + +int ping_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING */ + +/* full(er) version */ + +#define OPT_STRING "qc:s:I:" +enum { + OPT_QUIET = 1 << 0, +}; + +static struct sockaddr_in pingaddr; +static struct sockaddr_in sourceaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in *); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp *pkt; + int i; + char packet[datalen + ICMP_MINLEN]; + + pkt = (struct icmp *) packet; + + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_code = 0; + pkt->icmp_cksum = 0; + pkt->icmp_seq = htons(ntransmitted++); + pkt->icmp_id = myid; + CLR(ntohs(pkt->icmp_seq) % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp_dun, NULL); + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +static char *icmp_type_name(int id) +{ + switch (id) { + case ICMP_ECHOREPLY: return "Echo Reply"; + case ICMP_DEST_UNREACH: return "Destination Unreachable"; + case ICMP_SOURCE_QUENCH: return "Source Quench"; + case ICMP_REDIRECT: return "Redirect (change route)"; + case ICMP_ECHO: return "Echo Request"; + case ICMP_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP_PARAMETERPROB: return "Parameter Problem"; + case ICMP_TIMESTAMP: return "Timestamp Request"; + case ICMP_TIMESTAMPREPLY: return "Timestamp Reply"; + case ICMP_INFO_REQUEST: return "Information Request"; + case ICMP_INFO_REPLY: return "Information Reply"; + case ICMP_ADDRESS: return "Address Mask Request"; + case ICMP_ADDRESSREPLY: return "Address Mask Reply"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *buf, int sz, struct sockaddr_in *from) +{ + struct icmp *icmppkt; + struct iphdr *iphdr; + struct timeval tv, *tp; + int hlen, dupflag; + unsigned long triptime; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + ICMP_MINLEN)) + return; + + /* check IP header */ + iphdr = (struct iphdr *) buf; + hlen = iphdr->ihl << 2; + sz -= hlen; + icmppkt = (struct icmp *) (buf + hlen); + if (icmppkt->icmp_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp_type == ICMP_ECHOREPLY) { + u_int16_t recv_seq = ntohs(icmppkt->icmp_seq); + ++nreceived; + tp = (struct timeval *) icmppkt->icmp_data; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(recv_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(recv_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp_seq=%u", sz, + inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), + recv_seq); + printf(" ttl=%d", iphdr->ttl); + printf(" time=%lu.%lu ms", triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp_type != ICMP_ECHO) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp_type, icmp_type_name(icmppkt->icmp_type)); + fflush(stdout); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + int sockopt; + + pingsock = create_icmp_socket(); + + if (sourceaddr.sin_addr.s_addr) { + xbind(pingsock, (struct sockaddr*)&sourceaddr, sizeof(sourceaddr)); + } + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + hostent = xgethostbyname(host); + if (hostent->h_addrtype != AF_INET) + bb_error_msg_and_die("unknown address type; only AF_INET is currently supported"); + + memcpy(&pingaddr.sin_addr, hostent->h_addr, sizeof(pingaddr.sin_addr)); + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + printf("PING %s (%s)", + hostent->h_name, + inet_ntoa(*(struct in_addr *) &pingaddr.sin_addr.s_addr)); + if (sourceaddr.sin_addr.s_addr) { + printf(" from %s", + inet_ntoa(*(struct in_addr *) &sourceaddr.sin_addr.s_addr)); + } + printf(": %d data bytes\n", datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = (socklen_t) sizeof(from); + int c; + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + unpack(packet, c, &from); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +/* TODO: consolidate ether-wake.c, dnsd.c, ifupdown.c, nslookup.c + * versions of below thing. BTW we have far too many "%u.%u.%u.%u" too... +*/ +static int parse_nipquad(const char *str, struct sockaddr_in* addr) +{ + char dummy; + unsigned i1, i2, i3, i4; + if (sscanf(str, "%u.%u.%u.%u%c", + &i1, &i2, &i3, &i4, &dummy) == 4 + && ( (i1|i2|i3|i4) <= 0xff ) + ) { + uint8_t* ptr = (uint8_t*)&addr->sin_addr; + ptr[0] = i1; + ptr[1] = i2; + ptr[2] = i3; + ptr[3] = i4; + return 0; + } + return 1; /* error */ +} + +int ping_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed */ + opt_complementary = "=1"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 2) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 4) datalen = xatou16(opt_s); // -s + if (option_mask32 & 8) { // -I +/* TODO: ping6 accepts iface too: + if_index = if_nametoindex(*argv); + if (!if_index) ... +make it true for ping. */ + if (parse_nipquad(opt_I, &sourceaddr)) + bb_show_usage(); + } + + myid = (int16_t) getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING */ diff --git a/networking/ping6.c b/networking/ping6.c new file mode 100644 index 0000000..9f0509e --- /dev/null +++ b/networking/ping6.c @@ -0,0 +1,480 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping6.c,v 1.6 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This version of ping is adapted from the ping in netkit-base 0.10, + * which is: + * + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Original copyright notice is retained at the end of this file. + * + * This version is an adaptation of ping.c from busybox. + * The code was modified by Bart Visscher <magick@linux-fan.com> + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/file.h> +#include <sys/times.h> +#include <signal.h> + +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> /* offsetof */ +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +#ifndef CONFIG_FEATURE_FANCY_PING6 + +/* simple version */ + +static struct hostent *h; + +static void noresp(int ign) +{ + printf("No response from %s\n", h->h_name); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct sockaddr_in6 pingaddr; + struct icmp6_hdr *pkt; + int pingsock, c; + int sockopt; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + h = xgethostbyname2(host, AF_INET6); + memcpy(&pingaddr.sin6_addr, h->h_addr, sizeof(pingaddr.sin6_addr)); + + pkt = (struct icmp6_hdr *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + c = sendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (c < 0 || c != sizeof(packet)) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in6 from; + size_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 8) { /* icmp6_hdr */ + pkt = (struct icmp6_hdr *) packet; + if (pkt->icmp6_type == ICMP6_ECHO_REPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", h->h_name); + return; +} + +int ping6_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* full(er) version */ + +#define OPT_STRING "qvc:s:I:" +enum { + OPT_QUIET = 1 << 0, + OPT_VERBOSE = 1 << 1, +}; + +static struct sockaddr_in6 pingaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ +static int if_index; + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in6 *, int); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp6_hdr *pkt; + int i; + char packet[datalen + sizeof (struct icmp6_hdr)]; + + pkt = (struct icmp6_hdr *) packet; + + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + pkt->icmp6_code = 0; + pkt->icmp6_cksum = 0; + pkt->icmp6_seq = htons(ntransmitted++); + pkt->icmp6_id = myid; + CLR(pkt->icmp6_seq % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp6_data8[4], NULL); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +/* RFC3542 changed some definitions from RFC2292 for no good reason, whee ! + * the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */ +#ifndef MLD_LISTENER_QUERY +# define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY +#endif +#ifndef MLD_LISTENER_REPORT +# define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT +#endif +#ifndef MLD_LISTENER_REDUCTION +# define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION +#endif +static char *icmp6_type_name(int id) +{ + switch (id) { + case ICMP6_DST_UNREACH: return "Destination Unreachable"; + case ICMP6_PACKET_TOO_BIG: return "Packet too big"; + case ICMP6_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP6_PARAM_PROB: return "Parameter Problem"; + case ICMP6_ECHO_REPLY: return "Echo Reply"; + case ICMP6_ECHO_REQUEST: return "Echo Request"; + case MLD_LISTENER_QUERY: return "Listener Query"; + case MLD_LISTENER_REPORT: return "Listener Report"; + case MLD_LISTENER_REDUCTION: return "Listener Reduction"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit) +{ + struct icmp6_hdr *icmppkt; + struct timeval tv, *tp; + int dupflag; + unsigned long triptime; + char buf[INET6_ADDRSTRLEN]; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + sizeof(struct icmp6_hdr))) + return; + + icmppkt = (struct icmp6_hdr *) packet; + if (icmppkt->icmp6_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) { + ++nreceived; + tp = (struct timeval *) &icmppkt->icmp6_data8[4]; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(icmppkt->icmp6_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(icmppkt->icmp6_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp6_seq=%u", sz, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + icmppkt->icmp6_seq); + printf(" ttl=%d time=%lu.%lu ms", hoplimit, + triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp6_type, icmp6_type_name(icmppkt->icmp6_type)); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + char buf[INET6_ADDRSTRLEN]; + int sockopt; + struct msghdr msg; + struct sockaddr_in6 from; + struct iovec iov; + char control_buf[CMSG_SPACE(36)]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + hostent = xgethostbyname2(host, AF_INET6); + if (hostent->h_addrtype != AF_INET6) + bb_error_msg_and_die("unknown address type; only AF_INET6 is currently supported"); + + memcpy(&pingaddr.sin6_addr, hostent->h_addr, sizeof(pingaddr.sin6_addr)); + +#ifdef ICMP6_FILTER + { + struct icmp6_filter filt; + if (!(option_mask32 & OPT_VERBOSE)) { + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt); + } else { + ICMP6_FILTER_SETPASSALL(&filt); + } + if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, + sizeof(filt)) < 0) + bb_error_msg_and_die("setsockopt(ICMP6_FILTER)"); + } +#endif /*ICMP6_FILTER*/ + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = 1; + setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, (char *) &sockopt, + sizeof(sockopt)); + + if (if_index) + pingaddr.sin6_scope_id = if_index; + + printf("PING %s (%s): %d data bytes\n", + hostent->h_name, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf; + iov.iov_base = packet; + iov.iov_len = sizeof(packet); + while (1) { + int c; + struct cmsghdr *cmsgptr = NULL; + int hoplimit = -1; + msg.msg_controllen = sizeof(control_buf); + + if ((c = recvmsg(pingsock, &msg, 0)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; + cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + if (cmsgptr->cmsg_level == SOL_IPV6 && + cmsgptr->cmsg_type == IPV6_HOPLIMIT ) { + hoplimit = *(int*)CMSG_DATA(cmsgptr); + } + } + unpack(packet, c, &from, hoplimit); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +int ping6_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed, -v and -q don't mix */ + opt_complementary = "=1:q--v:v--q"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 4) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 8) datalen = xatou16(opt_s); // -s + if (option_mask32 & 0x10) { // -I + if_index = if_nametoindex(opt_I); + if (!if_index) + bb_error_msg_and_die( + "%s: invalid interface name", opt_I); + } + + myid = (int16_t)getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * 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. + * + * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change + * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ diff --git a/networking/route.c b/networking/route.c new file mode 100644 index 0000000..a8926f4 --- /dev/null +++ b/networking/route.c @@ -0,0 +1,703 @@ +/* vi: set sw=4 ts=4: */ +/* route + * + * Similar to the standard Unix route, but with only the necessary + * parts for AF_INET and AF_INET6 + * + * Bjorn Wesen, Axis Communications AB + * + * Author of the original route: + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * (derived from FvK's 'route.c 1.70 01/04/94') + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * $Id: route.c,v 1.26 2004/03/19 23:27:08 mjn3 Exp $ + * + * displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru> + * adjustments by Larry Doolittle <LRDoolittle@lbl.gov> + * + * IPV6 support added by Bart Visscher <magick@linux-fan.com> + */ + +/* 2004/03/09 Manuel Novoa III <mjn3@codepoet.org> + * + * Rewritten to fix several bugs, add additional error checking, and + * remove ridiculous amounts of bloat. + */ + +#include "busybox.h" +#include "inet_common.h" +#include <getopt.h> +#include <net/route.h> +#include <net/if.h> + + +#ifndef RTF_UP +/* Keep this in sync with /usr/src/linux/include/linux/route.h */ +#define RTF_UP 0x0001 /* route usable */ +#define RTF_GATEWAY 0x0002 /* destination is a gateway */ +#define RTF_HOST 0x0004 /* host entry (net otherwise) */ +#define RTF_REINSTATE 0x0008 /* reinstate route after tmout */ +#define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */ +#define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */ +#define RTF_MTU 0x0040 /* specific MTU for this route */ +#ifndef RTF_MSS +#define RTF_MSS RTF_MTU /* Compatibility :-( */ +#endif +#define RTF_WINDOW 0x0080 /* per route window clamping */ +#define RTF_IRTT 0x0100 /* Initial round trip time */ +#define RTF_REJECT 0x0200 /* Reject route */ +#endif + +#if defined (SIOCADDRTOLD) || defined (RTF_IRTT) /* route */ +#define HAVE_NEW_ADDRT 1 +#endif + +#if HAVE_NEW_ADDRT +#define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr) +#define full_mask(x) (x) +#else +#define mask_in_addr(x) ((x).rt_genmask) +#define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr) +#endif + +/* The RTACTION entries must agree with tbl_verb[] below! */ +#define RTACTION_ADD 1 +#define RTACTION_DEL 2 + +/* For the various tbl_*[] arrays, the 1st byte is the offset to + * the next entry and the 2nd byte is return value. */ + +#define NET_FLAG 1 +#define HOST_FLAG 2 + +/* We remap '-' to '#' to avoid problems with getopt. */ +static const char tbl_hash_net_host[] = + "\007\001#net\0" +/* "\010\002#host\0" */ + "\007\002#host" /* Since last, we can save a byte. */ +; + +#define KW_TAKES_ARG 020 +#define KW_SETS_FLAG 040 + +#define KW_IPVx_METRIC 020 +#define KW_IPVx_NETMASK 021 +#define KW_IPVx_GATEWAY 022 +#define KW_IPVx_MSS 023 +#define KW_IPVx_WINDOW 024 +#define KW_IPVx_IRTT 025 +#define KW_IPVx_DEVICE 026 + +#define KW_IPVx_FLAG_ONLY 040 +#define KW_IPVx_REJECT 040 +#define KW_IPVx_MOD 041 +#define KW_IPVx_DYN 042 +#define KW_IPVx_REINSTATE 043 + +static const char tbl_ipvx[] = + /* 020 is the "takes an arg" bit */ +#if HAVE_NEW_ADDRT + "\011\020metric\0" +#endif + "\012\021netmask\0" + "\005\022gw\0" + "\012\022gateway\0" + "\006\023mss\0" + "\011\024window\0" +#ifdef RTF_IRTT + "\007\025irtt\0" +#endif + "\006\026dev\0" + "\011\026device\0" + /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */ +#ifdef RTF_REJECT + "\011\040reject\0" +#endif + "\006\041mod\0" + "\006\042dyn\0" +/* "\014\043reinstate\0" */ + "\013\043reinstate" /* Since last, we can save a byte. */ +; + +static const int flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */ +#ifdef RTF_REJECT + RTF_REJECT, +#endif + RTF_MODIFIED, + RTF_DYNAMIC, + RTF_REINSTATE +}; + +static int kw_lookup(const char *kwtbl, char ***pargs) +{ + if (**pargs) { + do { + if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */ + *pargs += 1; + if (kwtbl[1] & KW_TAKES_ARG) { + if (!**pargs) { /* No more args! */ + bb_show_usage(); + } + *pargs += 1; /* Calling routine will use args[-1]. */ + } + return kwtbl[1]; + } + kwtbl += *kwtbl; + } while (*kwtbl); + } + return 0; +} + +/* Add or delete a route, depending on action. */ + +static void INET_setroute(int action, char **args) +{ + struct rtentry rt; + const char *netmask = NULL; + int skfd, isnet, xflag; + + /* Grab the -net or -host options. Remember they were transformed. */ + xflag = kw_lookup(tbl_hash_net_host, &args); + + /* If we did grab -net or -host, make sure we still have an arg left. */ + if (*args == NULL) { + bb_show_usage(); + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct rtentry)); + + { + const char *target = *args++; + char *prefix; + + /* recognize x.x.x.x/mask format. */ + prefix = strchr(target, '/'); + if(prefix) { + int prefix_len; + + prefix_len = xatoul_range(prefix+1, 0, 32); + mask_in_addr(rt) = htonl( ~ (0xffffffffUL >> prefix_len)); + *prefix = '\0'; +#if HAVE_NEW_ADDRT + rt.rt_genmask.sa_family = AF_INET; +#endif + } else { + /* Default netmask. */ + netmask = bb_str_default; + } + /* Prefer hostname lookup is -host flag (xflag==1) was given. */ + isnet = INET_resolve(target, (struct sockaddr_in *) &rt.rt_dst, + (xflag & HOST_FLAG)); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", target); + } + if(prefix) { + /* do not destroy prefix for process args */ + *prefix = '/'; + } + } + + if (xflag) { /* Reinit isnet if -net or -host was specified. */ + isnet = (xflag & NET_FLAG); + } + + /* Fill in the other fields. */ + rt.rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST)); + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if (k & KW_IPVx_FLAG_ONLY) { + rt.rt_flags |= flags_ipvx[k & 3]; + continue; + } + +#if HAVE_NEW_ADDRT + if (k == KW_IPVx_METRIC) { + rt.rt_metric = xatoul(args_m1) + 1; + continue; + } +#endif + + if (k == KW_IPVx_NETMASK) { + struct sockaddr mask; + + if (mask_in_addr(rt)) { + bb_show_usage(); + } + + netmask = args_m1; + isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", netmask); + } + rt.rt_genmask = full_mask(mask); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rt_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + isnet = INET_resolve(args_m1, + (struct sockaddr_in *) &rt.rt_gateway, 1); + rt.rt_flags |= RTF_GATEWAY; + + if (isnet) { + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + bb_error_msg_and_die("gateway %s is a NETWORK", args_m1); + } + continue; + } + + if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */ + rt.rt_flags |= RTF_MSS; + rt.rt_mss = xatoul_range(args_m1, 64, 32768); + continue; + } + + if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */ + rt.rt_flags |= RTF_WINDOW; + rt.rt_window = xatoul_range(args_m1, 128, INT_MAX); + continue; + } + +#ifdef RTF_IRTT + if (k == KW_IPVx_IRTT) { + rt.rt_flags |= RTF_IRTT; + rt.rt_irtt = xatoul(args_m1); + rt.rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */ +#if 0 /* FIXME: do we need to check anything of this? */ + if (rt.rt_irtt < 1 || rt.rt_irtt > (120 * HZ)) { + bb_error_msg_and_die("bad irtt"); + } +#endif + continue; + } +#endif + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!rt.rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + rt.rt_dev = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + +#ifdef RTF_REJECT + if ((rt.rt_flags & RTF_REJECT) && !rt.rt_dev) { + rt.rt_dev = "lo"; + } +#endif + + /* sanity checks.. */ + if (mask_in_addr(rt)) { + unsigned long mask = mask_in_addr(rt); + + mask = ~ntohl(mask); + if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) { + bb_error_msg_and_die("netmask %.8x and host route conflict", + (unsigned int) mask); + } + if (mask & (mask + 1)) { + bb_error_msg_and_die("bogus netmask %s", netmask); + } + mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr; + if (mask & ~mask_in_addr(rt)) { + bb_error_msg_and_die("netmask and route address conflict"); + } + } + + /* Fill out netmask if still unset */ + if ((action == RTACTION_ADD) && (rt.rt_flags & RTF_HOST)) { + mask_in_addr(rt) = 0xffffffff; + } + + /* Create a socket to the INET kernel. */ + skfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_setroute(int action, char **args) +{ + struct sockaddr_in6 sa6; + struct in6_rtmsg rt; + int prefix_len, skfd; + const char *devname; + + /* We know args isn't NULL from the check in route_main. */ + const char *target = *args++; + + if (strcmp(target, bb_str_default) == 0) { + prefix_len = 0; + memset(&sa6, 0, sizeof(sa6)); + } else { + char *cp; + if ((cp = strchr(target, '/'))) { /* Yes... const to non is ok. */ + *cp = 0; + prefix_len = xatoul_range(cp+1, 0, 128); + } else { + prefix_len = 128; + } + if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", target); + } + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct in6_rtmsg)); + + memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr)); + + /* Fill in the other fields. */ + rt.rtmsg_dst_len = prefix_len; + rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP); + rt.rtmsg_metric = 1; + + devname = NULL; + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) { + rt.rtmsg_flags |= flags_ipvx[k & 3]; + continue; + } + + if (k == KW_IPVx_METRIC) { + rt.rtmsg_metric = xatoul(args_m1); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rtmsg_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr, + sizeof(struct in6_addr)); + rt.rtmsg_flags |= RTF_GATEWAY; + continue; + } + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + devname = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + + /* Create a socket to the INET6 kernel. */ + skfd = xsocket(AF_INET6, SOCK_DGRAM, 0); + + rt.rtmsg_ifindex = 0; + + if (devname) { + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name)); + + if (ioctl(skfd, SIOGIFINDEX, &ifr) < 0) { + bb_perror_msg_and_die("SIOGIFINDEX"); + } + rt.rtmsg_ifindex = ifr.ifr_ifindex; + } + + /* Tell the kernel to accept this route. */ + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} +#endif + +static const unsigned int flagvals[] = { /* Must agree with flagchars[]. */ + RTF_GATEWAY, + RTF_HOST, + RTF_REINSTATE, + RTF_DYNAMIC, + RTF_MODIFIED, +#ifdef CONFIG_FEATURE_IPV6 + RTF_DEFAULT, + RTF_ADDRCONF, + RTF_CACHE +#endif +}; + +#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED) +#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE) + +static const char flagchars[] = /* Must agree with flagvals[]. */ + "GHRDM" +#ifdef CONFIG_FEATURE_IPV6 + "DAC" +#endif +; + +static +#ifndef CONFIG_FEATURE_IPV6 +__inline +#endif +void set_flags(char *flagstr, int flags) +{ + int i; + + *flagstr++ = 'U'; + + for (i=0 ; (*flagstr = flagchars[i]) != 0 ; i++) { + if (flags & flagvals[i]) { + ++flagstr; + } + } +} + +/* also used in netstat */ +void displayroutes(int noresolve, int netstatfmt); +void displayroutes(int noresolve, int netstatfmt) +{ + char devname[64], flags[16], sdest[16], sgw[16]; + unsigned long int d, g, m; + int flgs, ref, use, metric, mtu, win, ir; + struct sockaddr_in s_addr; + struct in_addr mask; + + FILE *fp = xfopen("/proc/net/route", "r"); + + printf("Kernel IP routing table\n" + "Destination Gateway Genmask" + " Flags %s Iface\n", + netstatfmt ? " MSS Window irtt" : "Metric Ref Use"); + + if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */ + goto ERROR; /* Empty or missing line, or read error. */ + } + while (1) { + int r; + r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", + devname, &d, &g, &flgs, &ref, &use, &metric, &m, + &mtu, &win, &ir); + if (r != 11) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (flgs & IPV4_MASK)); +#ifdef RTF_REJECT + if (flgs & RTF_REJECT) { + flags[0] = '!'; + } +#endif + + memset(&s_addr, 0, sizeof(struct sockaddr_in)); + s_addr.sin_family = AF_INET; + s_addr.sin_addr.s_addr = d; + INET_rresolve(sdest, sizeof(sdest), &s_addr, + (noresolve | 0x8000), m); /* Default instead of *. */ + + s_addr.sin_addr.s_addr = g; + INET_rresolve(sgw, sizeof(sgw), &s_addr, + (noresolve | 0x4000), m); /* Host instead of net. */ + + mask.s_addr = m; + printf("%-16s%-16s%-16s%-6s", sdest, sgw, inet_ntoa(mask), flags); + if (netstatfmt) { + printf("%5d %-5d %6d %s\n", mtu, win, ir, devname); + } else { + printf("%-6d %-2d %7d %s\n", metric, ref, use, devname); + } + } +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_displayroutes(int noresolve) +{ + char addr6[128], naddr6[128]; + /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses. + * We read the non-delimited strings into the tail of the buffer + * using fscanf and then modify the buffer by shifting forward + * while inserting ':'s and the nul terminator for the first string. + * Hence the strings are at addr6x and addr6x+40. This generates + * _much_ less code than the previous (upstream) approach. */ + char addr6x[80]; + char iface[16], flags[16]; + int iflags, metric, refcnt, use, prefix_len, slen; + struct sockaddr_in6 snaddr6; + + FILE *fp = xfopen("/proc/net/ipv6_route", "r"); + + printf("Kernel IPv6 routing table\n%-44s%-40s" + "Flags Metric Ref Use Iface\n", + "Destination", "Next Hop"); + + while (1) { + int r; + r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n", + addr6x+14, &prefix_len, &slen, addr6x+40+7, + &metric, &use, &refcnt, &iflags, iface); + if (r != 9) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + /* Do the addr6x shift-and-insert changes to ':'-delimit addresses. + * For now, always do this to validate the proc route format, even + * if the interface is down. */ + { + int i = 0; + char *p = addr6x+14; + + do { + if (!*p) { + if (i==40) { /* nul terminator for 1st address? */ + addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */ + ++p; /* Skip and continue. */ + continue; + } + goto ERROR; + } + addr6x[i++] = *p++; + if (!((i+1)%5)) { + addr6x[i++] = ':'; + } + } while (i < 40+28+7); + } + + if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (iflags & IPV6_MASK)); + + r = 0; + do { + inet_pton(AF_INET6, addr6x + r, + (struct sockaddr *) &snaddr6.sin6_addr); + snaddr6.sin6_family = AF_INET6; + INET6_rresolve(naddr6, sizeof(naddr6), + (struct sockaddr_in6 *) &snaddr6, + 0x0fff /* Apparently, upstream never resolves. */ + ); + + if (!r) { /* 1st pass */ + snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len); + r += 40; + } else { /* 2nd pass */ + /* Print the info. */ + printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n", + addr6, naddr6, flags, metric, refcnt, use, iface); + break; + } + } while (1); + } +} + +#endif + +#define ROUTE_OPT_A 0x01 +#define ROUTE_OPT_n 0x02 +#define ROUTE_OPT_e 0x04 +#define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */ + +/* 1st byte is offset to next entry offset. 2nd byte is return value. */ +static const char tbl_verb[] = /* 2nd byte matches RTACTION_* code */ + "\006\001add\0" + "\006\002del\0" +/* "\011\002delete\0" */ + "\010\002delete" /* Since last, we can save a byte. */ +; + +int route_main(int argc, char **argv) +{ + unsigned opt; + int what; + char *family; + char **p; + + /* First, remap '-net' and '-host' to avoid getopt problems. */ + p = argv; + while (*++p) { + if ((strcmp(*p, "-net") == 0) || (strcmp(*p, "-host") == 0)) { + p[0][0] = '#'; + } + } + + opt = getopt32(argc, argv, "A:ne", &family); + + if ((opt & ROUTE_OPT_A) && strcmp(family, "inet")) { +#ifdef CONFIG_FEATURE_IPV6 + if (strcmp(family, "inet6") == 0) { + opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */ + } else +#endif + bb_show_usage(); + } + + argv += optind; + + /* No more args means display the routing table. */ + if (!*argv) { + int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0; +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_displayroutes(noresolve); + else +#endif + displayroutes(noresolve, opt & ROUTE_OPT_e); + + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + /* Check verb. At the moment, must be add, del, or delete. */ + what = kw_lookup(tbl_verb, &argv); + if (!what || !*argv) { /* Unknown verb or no more args. */ + bb_show_usage(); + } + +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_setroute(what, argv); + else +#endif + INET_setroute(what, argv); + + return EXIT_SUCCESS; +} diff --git a/networking/telnet.c b/networking/telnet.c new file mode 100644 index 0000000..6085d88 --- /dev/null +++ b/networking/telnet.c @@ -0,0 +1,714 @@ +/* vi: set sw=4 ts=4: */ +/* + * telnet implementation for busybox + * + * Author: Tomi Ollila <too@iki.fi> + * Copyright (C) 1994-2000 by Tomi Ollila + * + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> + * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan + * <jam@ltsp.org> + * Modified 2004/02/11 to add ability to pass the USER variable to remote host + * by Fernando Silveira <swrh@gmx.net> + * + */ + +#include <termios.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <arpa/telnet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include "busybox.h" + +#ifdef DOTRACE +#include <arpa/inet.h> /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 + +enum { + CHM_TRY = 0, + CHM_ON = 1, + CHM_OFF = 2, + + UF_ECHO = 0x01, + UF_SGA = 0x02, + + TS_0 = 1, + TS_IAC = 2, + TS_OPT = 3, + TS_SUB1 = 4, + TS_SUB2 = 5, +}; + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +static struct Globalvars { + int netfd; /* console fd:s are 0 and 1 (and 2) */ + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; /* allocating so static size is smaller */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + byte do_termios; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + short iaclen; /* could even use byte */ + struct termios termios_def; + struct termios termios_raw; +} G; + +#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ + +#ifdef USE_GLOBALVAR_PTR +struct Globalvars * Gptr; +#define G (*Gptr) +#endif + +static void iacflush(void) +{ + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +/* Function prototypes */ +static void rawmode(void); +static void cookmode(void); +static void do_linemode(void); +static void will_charmode(void); +static void telopt(byte c); +static int subneg(byte c); + +/* Some globals */ +static const int one = 1; + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static char *ttype; +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static const char *autologin; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static int win_width, win_height; +#endif + +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape(void) +{ + char b; + + if (G.gotsig) /* came from line mode... go raw */ + rawmode(); + + WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(0, &b, 1) <= 0) + doexit(1); + + switch (b) + { + case 'l': + if (!G.gotsig) + { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (G.gotsig) + { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(0); + } + + WriteCS(1, "continuing...\r\n"); + + if (G.gotsig) + cookmode(); + + rrturn: + G.gotsig = 0; + +} +static void handlenetoutput(int len) +{ + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s + * + * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) + * I don't agree. + * first - I cannot use programs like sz/rz + * second - the 0x0D is sent as one character and if the next + * char is 0x0A then it's eaten by a server side. + * third - whay doy you have to make 'many write()s'? + * I don't understand. + * So I implemented it. It's realy useful for me. I hope that + * others people will find it interesting to. + */ + + int i, j; + byte * p = (byte*)G.buf; + byte outbuf[4*DATABUFSIZE]; + + for (i = len, j = 0; i > 0; i--, p++) + { + if (*p == 0x1d) + { + conescape(); + return; + } + outbuf[j++] = *p; + if (*p == 0xff) + outbuf[j++] = 0xff; + else if (*p == 0x0d) + outbuf[j++] = 0x00; + } + if (j > 0 ) + write(G.netfd, outbuf, j); +} + + +static void handlenetinput(int len) +{ + int i; + int cstart = 0; + + for (i = 0; i < len; i++) + { + byte c = G.buf[i]; + + if (G.telstate == 0) /* most of the time state == 0 */ + { + if (c == IAC) + { + cstart = i; + G.telstate = TS_IAC; + } + } + else + switch (G.telstate) + { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + break; + + case TS_IAC: + if (c == IAC) /* IAC IAC -> 0xFF */ + { + G.buf[cstart++] = c; + G.telstate = TS_0; + break; + } + /* else */ + switch (c) + { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + default: + G.telstate = TS_0; /* DATA MARK must be added later */ + } + break; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; + break; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c)) + G.telstate = TS_0; + break; + } + } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + len = cstart; + } + + if (len) + write(1, G.buf, len); +} + + +/* ******************************* */ + +static void putiac(int c) +{ + G.iacbuf[G.iaclen++] = c; +} + + +static void putiac2(byte wwdd, byte c) +{ + if (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void putiac_subopt(byte c, char *str) +{ + int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + putiac(0); + + while(*str) + putiac(*str++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(autologin) + 6; // (2 + 1 + 1 + strlen + 2) + char *user = "USER"; + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(TELOPT_NEW_ENVIRON); + putiac(TELQUAL_IS); + putiac(NEW_ENV_VAR); + + while(*user) + putiac(*user++); + + putiac(NEW_ENV_VALUE); + + while(*autologin) + putiac(*autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void putiac_naws(byte c, int x, int y) +{ + if (G.iaclen + 9 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + + putiac((x >> 8) & 0xff); + putiac(x & 0xff); + putiac((y >> 8) & 0xff); + putiac(y & 0xff); + + putiac(IAC); + putiac(SE); +} +#endif + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +static char const escapecharis[] = "\r\nEscape character is "; + +static void setConMode(void) +{ + if (G.telflags & UF_ECHO) + { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); + } + } +} + +/* ******************************* */ + +static void will_charmode(void) +{ + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +static void do_linemode(void) +{ + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); +} + +/* ******************************* */ + +static void to_notsup(char c) +{ + if (G.telwish == WILL) putiac2(DONT, c); + else if (G.telwish == DO) putiac2(WONT, c); +} + +static void to_echo(void) +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } + else if (G.telwish == DONT) return; + + if (G.telflags & UF_ECHO) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + WriteCS(1, "\r\n"); /* sudden modec */ +} + +static void to_sga(void) +{ + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + + if(ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + + if (autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void to_naws(void) +{ + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); + return; +} +#endif + +static void telopt(byte c) +{ + switch (c) + { + case TELOPT_ECHO: to_echo(); break; + case TELOPT_SGA: to_sga(); break; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: to_ttype();break; +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: to_new_environ(); break; +#endif +#ifdef CONFIG_FEATURE_AUTOWIDTH + case TELOPT_NAWS: to_naws(); + putiac_naws(c, win_width, win_height); + break; +#endif + default: to_notsup(c); + break; + } +} + + +/* ******************************* */ + +/* subnegotiation -- ignore all (except TTYPE,NAWS) */ + +static int subneg(byte c) +{ + switch (G.telstate) + { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE,ttype); +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + else + if (c == TELOPT_NEW_ENVIRON) + putiac_subopt_autologin(); +#endif + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +/* ******************************* */ + +static void fgotsig(int sig) +{ + G.gotsig = sig; +} + + +static void rawmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +int telnet_main(int argc, char** argv) +{ + int len; + struct sockaddr_in s_in; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &win_width, &win_height); +#endif + +#ifdef CONFIG_FEATURE_TELNET_TTYPE + ttype = getenv("TERM"); +#endif + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + + if (argc < 2) + bb_show_usage(); + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + if (1 & getopt32(argc, argv, "al:", &autologin)) + autologin = getenv("USER"); + + if (optind < argc) { + bb_lookup_host(&s_in, argv[optind++]); + s_in.sin_port = bb_lookup_port((optind < argc) ? argv[optind++] : + "telnet", "tcp", 23); + if (optind < argc) + bb_show_usage(); + } else + bb_show_usage(); +#else + bb_lookup_host(&s_in, argv[1]); + s_in.sin_port = bb_lookup_port((argc == 3) ? argv[2] : "telnet", "tcp", 23); +#endif + + G.netfd = xconnect_tcp_v4(&s_in); + + setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + signal(SIGINT, fgotsig); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = G.netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(G.netfd, &readfds); + maxfd = G.netfd + 1; +#endif + + while (1) + { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (G.gotsig) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(0, &rfds)) +#endif + { + len = read(0, G.buf, DATABUFSIZE); + + if (len <= 0) + doexit(0); + + TRACE(0, ("Read con: %d\n", len)); + + handlenetoutput(len); + } + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(G.netfd, &rfds)) +#endif + { + len = read(G.netfd, G.buf, DATABUFSIZE); + + if (len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); + + handlenetinput(len); + } + } + } +} diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 0000000..604f65c --- /dev/null +++ b/networking/telnetd.c @@ -0,0 +1,578 @@ +/* vi: set sw=4 ts=4: */ +/* + * Simple telnet server + * Bjorn Wesen, Axis Communications AB (bjornw@axis.com) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN + **************************************************************************** + * + * The telnetd manpage says it all: + * + * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for + * a client, then creating a login process which has the slave side of the + * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the + * master side of the pseudo-terminal, implementing the telnet protocol and + * passing characters between the remote client and the login process. + * + * Vladimir Oleynik <dzo@simtreas.ru> 2001 + * Set process group corrections, initial busybox port + */ + +/*#define DEBUG 1 */ +#define DEBUG 0 + +#include "busybox.h" + +#if DEBUG +#define TELCMDS +#define TELOPTS +#endif +#include <arpa/telnet.h> +#include <sys/syslog.h> + + +#define BUFSIZE 4000 + +#if ENABLE_FEATURE_IPV6 +typedef struct sockaddr_in6 sockaddr_type; +#else +typedef struct sockaddr_in sockaddr_type; +#endif + +#if ENABLE_LOGIN +static const char *loginpath = "/bin/login"; +#else +static const char *loginpath = DEFAULT_SHELL; +#endif + +static const char *issuefile = "/etc/issue.net"; + +/* shell name and arguments */ + +static const char *argv_init[2]; + +/* structure that describes a session */ + +struct tsession { + struct tsession *next; + int sockfd_read, sockfd_write, ptyfd; + int shell_pid; + /* two circular buffers */ + char *buf1, *buf2; + int rdidx1, wridx1, size1; + int rdidx2, wridx2, size2; +}; + +/* + This is how the buffers are used. The arrows indicate the movement + of data. + + +-------+ wridx1++ +------+ rdidx1++ +----------+ + | | <-------------- | buf1 | <-------------- | | + | | size1-- +------+ size1++ | | + | pty | | socket | + | | rdidx2++ +------+ wridx2++ | | + | | --------------> | buf2 | --------------> | | + +-------+ size2++ +------+ size2-- +----------+ + + Each session has got two buffers. +*/ + +static int maxfd; + +static struct tsession *sessions; + + +/* + Remove all IAC's from the buffer pointed to by bf (received IACs are ignored + and must be removed so as to not be interpreted by the terminal). Make an + uninterrupted string of characters fit for the terminal. Do this by packing + all characters meant for the terminal sequentially towards the end of bf. + + Return a pointer to the beginning of the characters meant for the terminal. + and make *num_totty the number of characters that should be sent to + the terminal. + + Note - If an IAC (3 byte quantity) starts before (bf + len) but extends + past (bf + len) then that IAC will be left unprocessed and *processed will be + less than len. + + FIXME - if we mean to send 0xFF to the terminal then it will be escaped, + what is the escape character? We aren't handling that situation here. + + CR-LF ->'s CR mapping is also done here, for convenience + */ +static char * +remove_iacs(struct tsession *ts, int *pnum_totty) +{ + unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1; + unsigned char *ptr = ptr0; + unsigned char *totty = ptr; + unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); + int processed; + int num_totty; + + while (ptr < end) { + if (*ptr != IAC) { + int c = *ptr; + *totty++ = *ptr++; + /* We now map \r\n ==> \r for pragmatic reasons. + * Many client implementations send \r\n when + * the user hits the CarriageReturn key. + */ + if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end) + ptr++; + } else { + /* + * TELOPT_NAWS support! + */ + if ((ptr+2) >= end) { + /* only the beginning of the IAC is in the + buffer we were asked to process, we can't + process this char. */ + break; + } + + /* + * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE + */ + else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { + struct winsize ws; + if ((ptr+8) >= end) + break; /* incomplete, can't process */ + ws.ws_col = (ptr[3] << 8) | ptr[4]; + ws.ws_row = (ptr[5] << 8) | ptr[6]; + ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); + ptr += 9; + } else { + /* skip 3-byte IAC non-SB cmd */ +#if DEBUG + fprintf(stderr, "Ignoring IAC %s,%s\n", + TELCMD(ptr[1]), TELOPT(ptr[2])); +#endif + ptr += 3; + } + } + } + + processed = ptr - ptr0; + num_totty = totty - ptr0; + /* the difference between processed and num_to tty + is all the iacs we removed from the stream. + Adjust buf1 accordingly. */ + ts->wridx1 += processed - num_totty; + ts->size1 -= processed - num_totty; + *pnum_totty = num_totty; + /* move the chars meant for the terminal towards the end of the + buffer. */ + return memmove(ptr - num_totty, ptr0, num_totty); +} + + +static int +getpty(char *line, int size) +{ + int p; +#if ENABLE_FEATURE_DEVPTS + p = open("/dev/ptmx", O_RDWR); + if (p > 0) { + const char *name; + grantpt(p); + unlockpt(p); + name = ptsname(p); + if (!name) { + bb_perror_msg("ptsname error (is /dev/pts mounted?)"); + return -1; + } + safe_strncpy(line, name, size); + return p; + } +#else + struct stat stb; + int i; + int j; + + strcpy(line, "/dev/ptyXX"); + + for (i = 0; i < 16; i++) { + line[8] = "pqrstuvwxyzabcde"[i]; + line[9] = '0'; + if (stat(line, &stb) < 0) { + continue; + } + for (j = 0; j < 16; j++) { + line[9] = j < 10 ? j + '0' : j - 10 + 'a'; + if (DEBUG) + fprintf(stderr, "Trying to open device: %s\n", line); + p = open(line, O_RDWR | O_NOCTTY); + if (p >= 0) { + line[5] = 't'; + return p; + } + } + } +#endif /* FEATURE_DEVPTS */ + return -1; +} + + +static void +send_iac(struct tsession *ts, unsigned char command, int option) +{ + /* We rely on that there is space in the buffer for now. */ + char *b = ts->buf2 + ts->rdidx2; + *b++ = IAC; + *b++ = command; + *b++ = option; + ts->rdidx2 += 3; + ts->size2 += 3; +} + + +static struct tsession * +make_new_session( + USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w) + SKIP_FEATURE_TELNETD_STANDALONE(void) +) { + struct termios termbuf; + int fd, pid; + char tty_name[32]; + struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); + + ts->buf1 = (char *)(&ts[1]); + ts->buf2 = ts->buf1 + BUFSIZE; + + /* Got a new connection, set up a tty. */ + fd = getpty(tty_name, 32); + if (fd < 0) { + bb_error_msg("all terminals in use"); + return NULL; + } + if (fd > maxfd) maxfd = fd; + ndelay_on(ts->ptyfd = fd); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (sock_w > maxfd) maxfd = sock_w; + if (sock_r > maxfd) maxfd = sock_r; + ndelay_on(ts->sockfd_write = sock_w); + ndelay_on(ts->sockfd_read = sock_r); +#else + ts->sockfd_write = 1; + /* xzalloc: ts->sockfd_read = 0; */ + ndelay_on(0); + ndelay_on(1); +#endif + /* Make the telnet client understand we will echo characters so it + * should not do it locally. We don't tell the client to run linemode, + * because we want to handle line editing and tab completion and other + * stuff that requires char-by-char support. */ + send_iac(ts, DO, TELOPT_ECHO); + send_iac(ts, DO, TELOPT_NAWS); + send_iac(ts, DO, TELOPT_LFLOW); + send_iac(ts, WILL, TELOPT_ECHO); + send_iac(ts, WILL, TELOPT_SGA); + + pid = fork(); + if (pid < 0) { + free(ts); + close(fd); + bb_perror_msg("fork"); + return NULL; + } + if (pid > 0) { + /* parent */ + ts->shell_pid = pid; + return ts; + } + + /* child */ + + /* open the child's side of the tty. */ + fd = xopen(tty_name, O_RDWR /*| O_NOCTTY*/); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + while (fd > 2) close(fd--); + /* make new process group */ + setsid(); + tcsetpgrp(0, getpid()); + + /* The pseudo-terminal allocated to the client is configured to operate in + * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */ + tcgetattr(0, &termbuf); + termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ + termbuf.c_oflag |= ONLCR|XTABS; + termbuf.c_iflag |= ICRNL; + termbuf.c_iflag &= ~IXOFF; + /*termbuf.c_lflag &= ~ICANON;*/ + tcsetattr(0, TCSANOW, &termbuf); + + print_login_issue(issuefile, NULL); + + /* exec shell, with correct argv and env */ + execv(loginpath, (char *const *)argv_init); + bb_perror_msg_and_die("execv"); +} + +#if ENABLE_FEATURE_TELNETD_STANDALONE + +static void +free_session(struct tsession *ts) +{ + struct tsession *t = sessions; + + /* unlink this telnet session from the session list */ + if (t == ts) + sessions = ts->next; + else { + while (t->next != ts) + t = t->next; + t->next = ts->next; + } + + kill(ts->shell_pid, SIGKILL); + wait4(ts->shell_pid, NULL, 0, NULL); + close(ts->ptyfd); + close(ts->sockfd_read); + /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */ + close(ts->sockfd_write); + free(ts); + + /* scan all sessions and find new maxfd */ + ts = sessions; + maxfd = 0; + while (ts) { + if (maxfd < ts->ptyfd) + maxfd = ts->ptyfd; + if (maxfd < ts->sockfd_read) + maxfd = ts->sockfd_read; + if (maxfd < ts->sockfd_write) + maxfd = ts->sockfd_write; + ts = ts->next; + } +} + +#else /* !FEATURE_TELNETD_STANDALONE */ + +/* Never actually called */ +void free_session(struct tsession *ts); + +#endif + + +int +telnetd_main(int argc, char **argv) +{ + fd_set rdfdset, wrfdset; + unsigned opt; + int selret, maxlen, w, r; + struct tsession *ts; +#if ENABLE_FEATURE_TELNETD_STANDALONE +#define IS_INETD (opt & OPT_INETD) + int master_fd = -1; /* be happy, gcc */ + unsigned portnbr = 23; + char *opt_bindaddr = NULL; + char *opt_portnbr; +#else + enum { + IS_INETD = 1, + master_fd = -1, + portnbr = 23, + }; +#endif + enum { + OPT_PORT = 4 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_FOREGROUND = 0x10 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_INETD = 0x20 * ENABLE_FEATURE_TELNETD_STANDALONE, + }; + + opt = getopt32(argc, argv, "f:l:" USE_FEATURE_TELNETD_STANDALONE("p:b:Fi"), + &issuefile, &loginpath + USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); + /* Redirect log to syslog early, if needed */ + if (IS_INETD || !(opt & OPT_FOREGROUND)) { + openlog(applet_name, 0, LOG_USER); + logmode = LOGMODE_SYSLOG; + } + //if (opt & 1) // -f + //if (opt & 2) // -l + USE_FEATURE_TELNETD_STANDALONE( + if (opt & OPT_PORT) // -p + portnbr = xatou16(opt_portnbr); + //if (opt & 8) // -b + //if (opt & 0x10) // -F + //if (opt & 0x20) // -i + ); + + /* Used to check access(loginpath, X_OK) here. Pointless. + * exec will do this for us for free later. */ + argv_init[0] = loginpath; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (IS_INETD) { + sessions = make_new_session(0, 1); + } else { + master_fd = create_and_bind_socket_ip4or6(opt_bindaddr, portnbr); + xlisten(master_fd, 1); + if (!(opt & OPT_FOREGROUND)) + xdaemon(0, 0); + } +#else + sessions = make_new_session(); +#endif + + /* We don't want to die if just one session is broken */ + signal(SIGPIPE, SIG_IGN); + + again: + FD_ZERO(&rdfdset); + FD_ZERO(&wrfdset); + if (!IS_INETD) { + FD_SET(master_fd, &rdfdset); + /* This is needed because free_session() does not + * take into account master_fd when it finds new + * maxfd among remaining fd's: */ + if (master_fd > maxfd) + maxfd = master_fd; + } + + /* select on the master socket, all telnet sockets and their + * ptys if there is room in their session buffers. */ + ts = sessions; + while (ts) { + /* buf1 is used from socket to pty + * buf2 is used from pty to socket */ + if (ts->size1 > 0) /* can write to pty */ + FD_SET(ts->ptyfd, &wrfdset); + if (ts->size1 < BUFSIZE) /* can read from socket */ + FD_SET(ts->sockfd_read, &rdfdset); + if (ts->size2 > 0) /* can write to socket */ + FD_SET(ts->sockfd_write, &wrfdset); + if (ts->size2 < BUFSIZE) /* can read from pty */ + FD_SET(ts->ptyfd, &rdfdset); + ts = ts->next; + } + + selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); + if (!selret) + return 0; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + /* First check for and accept new sessions. */ + if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { + sockaddr_type sa; + int fd; + socklen_t salen; + struct tsession *new_ts; + + salen = sizeof(sa); + fd = accept(master_fd, (struct sockaddr *)&sa, &salen); + if (fd < 0) + goto again; + /* Create a new session and link it into our active list */ + new_ts = make_new_session(fd, fd); + if (new_ts) { + new_ts->next = sessions; + sessions = new_ts; + } else { + close(fd); + } + } +#endif + + /* Then check for data tunneling. */ + ts = sessions; + while (ts) { /* For all sessions... */ + struct tsession *next = ts->next; /* in case we free ts. */ + + if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { + int num_totty; + char *ptr; + /* Write to pty from buffer 1. */ + ptr = remove_iacs(ts, &num_totty); + w = safe_write(ts->ptyfd, ptr, num_totty); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx1 += w; + ts->size1 -= w; + if (ts->wridx1 == BUFSIZE) + ts->wridx1 = 0; + } + + if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) { + /* Write to socket from buffer 2. */ + maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2); + w = safe_write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx2 += w; + ts->size2 -= w; + if (ts->wridx2 == BUFSIZE) + ts->wridx2 = 0; + } + + if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) { + /* Read from socket to buffer 1. */ + maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); + r = safe_read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + if (!ts->buf1[ts->rdidx1 + r - 1]) + if (!--r) + continue; + ts->rdidx1 += r; + ts->size1 += r; + if (ts->rdidx1 == BUFSIZE) + ts->rdidx1 = 0; + } + + if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { + /* Read from pty to buffer 2. */ + maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); + r = safe_read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->rdidx2 += r; + ts->size2 += r; + if (ts->rdidx2 == BUFSIZE) + ts->rdidx2 = 0; + } + + if (ts->size1 == 0) { + ts->rdidx1 = 0; + ts->wridx1 = 0; + } + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } + ts = next; + } + goto again; +} diff --git a/networking/tftp.c b/networking/tftp.c new file mode 100644 index 0000000..64d376f --- /dev/null +++ b/networking/tftp.c @@ -0,0 +1,561 @@ +/* vi: set sw=4 ts=4: */ +/* ------------------------------------------------------------------------- + * tftp.c + * + * A simple tftp client for busybox. + * Tries to follow RFC1350. + * Only "octet" mode supported. + * Optional blocksize negotiation (RFC2347 + RFC2348) + * + * Copyright (C) 2001 Magnus Damm <damm@opensource.se> + * + * Parts of the code based on: + * + * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca> + * and Remi Lefebvre <remi@debian.org> + * + * utftp: Copyright (C) 1999 Uwe Ohse <uwe@ohse.de> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ + +#include "busybox.h" + + +#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_TIMEOUT 5 /* seconds */ +#define TFTP_NUM_RETRIES 5 /* number of retries */ + +static const char * const MODE_OCTET = "octet"; +#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/ + +static const char * const OPTION_BLOCKSIZE = "blksize"; +#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */ + +/* opcodes we support */ +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +static const char *const tftp_bb_error_msg[] = { + "Undefined error", + "File not found", + "Access violation", + "Disk full or allocation error", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET + +#if ENABLE_FEATURE_TFTP_PUT +# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT) +#else +# define tftp_cmd_put 0 +#endif + + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + +static int tftp_blocksize_check(int blocksize, int bufsize) +{ + /* Check if the blocksize is valid: + * RFC2348 says between 8 and 65464, + * but our implementation makes it impossible + * to use blocksizes smaller than 22 octets. + */ + + if ((bufsize && (blocksize > bufsize)) || + (blocksize < 8) || (blocksize > 65564)) { + bb_error_msg("bad blocksize"); + return 0; + } + + return blocksize; +} + +static char *tftp_option_get(char *buf, int len, const char * const option) +{ + int opt_val = 0; + int opt_found = 0; + int k; + + while (len > 0) { + + /* Make sure the options are terminated correctly */ + + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + break; + } + } + + if (k >= len) { + break; + } + + if (opt_val == 0) { + if (strcasecmp(buf, option) == 0) { + opt_found = 1; + } + } else { + if (opt_found) { + return buf; + } + } + + k++; + + buf += k; + len -= k; + + opt_val ^= 1; + } + + return NULL; +} + +#endif + +static int tftp(const int cmd, const struct hostent *host, + const char *remotefile, const int localfd, + const unsigned short port, int tftp_bufsize) +{ + struct sockaddr_in sa; + struct sockaddr_in from; + struct timeval tv; + socklen_t fromlen; + fd_set rfds; + int socketfd; + int len; + int opcode = 0; + int finished = 0; + int timeout = TFTP_NUM_RETRIES; + unsigned short block_nr = 1; + unsigned short tmp; + char *cp; + + USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;) + + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ + char *buf=xmalloc(tftp_bufsize += 4); + + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + /* need to unlink the localfile, so don't use xsocket here. */ + bb_perror_msg("socket"); + return EXIT_FAILURE; + } + + len = sizeof(sa); + + memset(&sa, 0, len); + xbind(socketfd, (struct sockaddr *)&sa, len); + + sa.sin_family = host->h_addrtype; + sa.sin_port = port; + memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, + sizeof(sa.sin_addr)); + + /* build opcode */ + if (cmd & tftp_cmd_get) { + opcode = TFTP_RRQ; + } + if (cmd & tftp_cmd_put) { + opcode = TFTP_WRQ; + } + + while (1) { + + cp = buf; + + /* first create the opcode part */ + *((unsigned short *) cp) = htons(opcode); + cp += 2; + + /* add filename and mode */ + if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ))) + { + int too_long = 0; + + /* see if the filename fits into buf + * and fill in packet. */ + len = strlen(remotefile) + 1; + + if ((cp + len) >= &buf[tftp_bufsize - 1]) { + too_long = 1; + } else { + safe_strncpy(cp, remotefile, len); + cp += len; + } + + if (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "mode" part of the package */ + memcpy(cp, MODE_OCTET, MODE_OCTET_LEN); + cp += MODE_OCTET_LEN; + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + + len = tftp_bufsize - 4; /* data block size */ + + if (len != TFTP_BLOCKSIZE_DEFAULT) { + + if ((&buf[tftp_bufsize - 1] - cp) < 15) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "blksize" + number of blocks */ + memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN); + cp += OPTION_BLOCKSIZE_LEN; + cp += snprintf(cp, 6, "%d", len) + 1; + + want_option_ack = 1; + } +#endif + } + + /* add ack and data */ + + if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) { + + *((unsigned short *) cp) = htons(block_nr); + + cp += 2; + + block_nr++; + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) { + len = full_read(localfd, cp, tftp_bufsize - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + cp += len; + } + } + + + /* send packet */ + + + timeout = TFTP_NUM_RETRIES; /* re-initialize */ + do { + + len = cp - buf; + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "sending %u bytes\n", len); + for (cp = buf; cp < &buf[len]; cp++) + fprintf(stderr, "%02x ", (unsigned char) *cp); + fprintf(stderr, "\n"); +#endif + if (sendto(socketfd, buf, len, 0, + (struct sockaddr *) &sa, sizeof(sa)) < 0) { + bb_perror_msg("send"); + len = -1; + break; + } + + + if (finished && (opcode == TFTP_ACK)) { + break; + } + + /* receive packet */ + + memset(&from, 0, sizeof(from)); + fromlen = sizeof(from); + + tv.tv_sec = TFTP_TIMEOUT; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(socketfd, &rfds); + + switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { + case 1: + len = recvfrom(socketfd, buf, tftp_bufsize, 0, + (struct sockaddr *) &from, &fromlen); + + if (len < 0) { + bb_perror_msg("recvfrom"); + break; + } + + timeout = 0; + + if (sa.sin_port == port) { + sa.sin_port = from.sin_port; + } + if (sa.sin_port == from.sin_port) { + break; + } + + /* fall-through for bad packets! */ + /* discard the packet - treat as timeout */ + timeout = TFTP_NUM_RETRIES; + case 0: + bb_error_msg("timeout"); + + timeout--; + if (timeout == 0) { + len = -1; + bb_error_msg("last timeout"); + } + break; + default: + bb_perror_msg("select"); + len = -1; + } + + } while (timeout && (len >= 0)); + + if ((finished) || (len < 0)) { + break; + } + + /* process received packet */ + + opcode = ntohs(*((unsigned short *) buf)); + tmp = ntohs(*((unsigned short *) &buf[2])); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); +#endif + + if (opcode == TFTP_ERROR) { + const char *msg = NULL; + + if (buf[4] != '\0') { + msg = &buf[4]; + buf[tftp_bufsize - 1] = '\0'; + } else if (tmp < (sizeof(tftp_bb_error_msg) + / sizeof(char *))) { + + msg = tftp_bb_error_msg[tmp]; + } + + if (msg) { + bb_error_msg("server says: %s", msg); + } + + break; + } +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (want_option_ack) { + + want_option_ack = 0; + + if (opcode == TFTP_OACK) { + + /* server seems to support options */ + + char *res; + + res = tftp_option_get(&buf[2], len - 2, OPTION_BLOCKSIZE); + + if (res) { + int blksize = xatoi_u(res); + + if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { + + if (cmd & tftp_cmd_put) { + opcode = TFTP_DATA; + } else { + opcode = TFTP_ACK; + } +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE, + blksize); +#endif + tftp_bufsize = blksize + 4; + block_nr = 0; + continue; + } + } + /* FIXME: + * we should send ERROR 8 */ + bb_error_msg("bad server option"); + break; + } + + bb_error_msg("warning: blksize not supported by server" + " - reverting to 512"); + + tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; + } +#endif + + if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) { + + if (tmp == block_nr) { + + len = full_write(localfd, &buf[4], len - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_write_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + opcode = TFTP_ACK; + continue; + } + /* in case the last ack disappeared into the ether */ + if (tmp == (block_nr - 1)) { + --block_nr; + opcode = TFTP_ACK; + continue; + } else if (tmp + 1 == block_nr) { + /* Server lost our TFTP_ACK. Resend it */ + block_nr = tmp; + opcode = TFTP_ACK; + continue; + } + } + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) { + + if (tmp == (unsigned short) (block_nr - 1)) { + if (finished) { + break; + } + + opcode = TFTP_DATA; + continue; + } + } + } + +#if ENABLE_FEATURE_CLEAN_UP + close(socketfd); + free(buf); +#endif + + return finished ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int tftp_main(int argc, char **argv) +{ + struct hostent *host = NULL; + const char *localfile = NULL; + const char *remotefile = NULL; + int port; + int cmd = 0; + int fd = -1; + int flags = 0; + int result; + int blocksize = TFTP_BLOCKSIZE_DEFAULT; + + /* figure out what to pass to getopt */ + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + char *sblocksize = NULL; + +#define BS "b:" +#define BS_ARG , &sblocksize +#else +#define BS +#define BS_ARG +#endif + +#if ENABLE_FEATURE_TFTP_GET +#define GET "g" +#define GET_COMPL ":g" +#else +#define GET +#define GET_COMPL +#endif + +#if ENABLE_FEATURE_TFTP_PUT +#define PUT "p" +#define PUT_COMPL ":p" +#else +#define PUT +#define PUT_COMPL +#endif + +#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL ":?g--p:p--g"; +#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL; +#endif + + cmd = getopt32(argc, argv, GET PUT "l:r:" BS, &localfile, &remotefile BS_ARG); + + cmd &= (tftp_cmd_get | tftp_cmd_put); +#if ENABLE_FEATURE_TFTP_GET + if (cmd == tftp_cmd_get) + flags = O_WRONLY | O_CREAT | O_TRUNC; +#endif +#if ENABLE_FEATURE_TFTP_PUT + if (cmd == tftp_cmd_put) + flags = O_RDONLY; +#endif + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (sblocksize) { + blocksize = xatoi_u(sblocksize); + if (!tftp_blocksize_check(blocksize, 0)) { + return EXIT_FAILURE; + } + } +#endif + + if (localfile == NULL) + localfile = remotefile; + if (remotefile == NULL) + remotefile = localfile; + if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL)) + bb_show_usage(); + + if (localfile == NULL || strcmp(localfile, "-") == 0) { + fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO; + } else { + fd = open(localfile, flags, 0644); /* fail below */ + } + if (fd < 0) { + bb_perror_msg_and_die("local file"); + } + + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using server \"%s\", remotefile \"%s\", " + "localfile \"%s\".\n", + inet_ntoa(*((struct in_addr *) host->h_addr)), + remotefile, localfile); +#endif + + result = tftp(cmd, host, remotefile, fd, port, blocksize); + + if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) { + if (ENABLE_FEATURE_CLEAN_UP) + close(fd); + if (cmd == tftp_cmd_get && result != EXIT_SUCCESS) + unlink(localfile); + } + return result; +} diff --git a/networking/traceroute.c b/networking/traceroute.c new file mode 100644 index 0000000..4900765 --- /dev/null +++ b/networking/traceroute.c @@ -0,0 +1,1350 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (c) 1988, 1989, 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000 + * The Regents of the University of California. All rights reserved. + * + * Busybox port by Vladimir Oleynik (C) 2005 <dzo@simtreas.ru> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: (1) source code distributions + * retain the above copyright notice and this paragraph in its entirety, (2) + * distributions including binary code include the above copyright notice and + * this paragraph in its entirety in the documentation or other materials + * provided with the distribution, and (3) all advertising materials mentioning + * features or use of this software display the following acknowledgement: + * ``This product includes software developed by the University of California, + * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of + * the University nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +//#define version "1.4a12" + + +/* + * traceroute host - trace the route ip packets follow going to "host". + * + * Attempt to trace the route an ip packet would follow to some + * internet host. We find out intermediate hops by launching probe + * packets with a small ttl (time to live) then listening for an + * icmp "time exceeded" reply from a gateway. We start our probes + * with a ttl of one and increase by one until we get an icmp "port + * unreachable" (which means we got to "host") or hit a max (which + * defaults to 30 hops & can be changed with the -m flag). Three + * probes (change with -q flag) are sent at each ttl setting and a + * line is printed showing the ttl, address of the gateway and + * round trip time of each probe. If the probe answers come from + * different gateways, the address of each responding system will + * be printed. If there is no response within a 5 sec. timeout + * interval (changed with the -w flag), a "*" is printed for that + * probe. + * + * Probe packets are UDP format. We don't want the destination + * host to process them so the destination port is set to an + * unlikely value (if some clod on the destination is using that + * value, it can be changed with the -p flag). + * + * A sample use might be: + * + * [yak 71]% traceroute nis.nsf.net. + * traceroute to nis.nsf.net (35.1.1.48), 30 hops max, 56 byte packet + * 1 helios.ee.lbl.gov (128.3.112.1) 19 ms 19 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 39 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 40 ms 59 ms 59 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 59 ms + * 8 129.140.70.13 (129.140.70.13) 99 ms 99 ms 80 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 239 ms 319 ms + * 10 129.140.81.7 (129.140.81.7) 220 ms 199 ms 199 ms + * 11 nic.merit.edu (35.1.1.48) 239 ms 239 ms 239 ms + * + * Note that lines 2 & 3 are the same. This is due to a buggy + * kernel on the 2nd hop system -- lbl-csam.arpa -- that forwards + * packets with a zero ttl. + * + * A more interesting example is: + * + * [yak 72]% traceroute allspice.lcs.mit.edu. + * traceroute to allspice.lcs.mit.edu (18.26.0.115), 30 hops max + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 19 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 19 ms 39 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 20 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 59 ms 119 ms 39 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 39 ms + * 8 129.140.70.13 (129.140.70.13) 80 ms 79 ms 99 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 139 ms 159 ms + * 10 129.140.81.7 (129.140.81.7) 199 ms 180 ms 300 ms + * 11 129.140.72.17 (129.140.72.17) 300 ms 239 ms 239 ms + * 12 * * * + * 13 128.121.54.72 (128.121.54.72) 259 ms 499 ms 279 ms + * 14 * * * + * 15 * * * + * 16 * * * + * 17 * * * + * 18 ALLSPICE.LCS.MIT.EDU (18.26.0.115) 339 ms 279 ms 279 ms + * + * (I start to see why I'm having so much trouble with mail to + * MIT.) Note that the gateways 12, 14, 15, 16 & 17 hops away + * either don't send ICMP "time exceeded" messages or send them + * with a ttl too small to reach us. 14 - 17 are running the + * MIT C Gateway code that doesn't send "time exceeded"s. God + * only knows what's going on with 12. + * + * The silent gateway 12 in the above may be the result of a bug in + * the 4.[23]BSD network code (and its derivatives): 4.x (x <= 3) + * sends an unreachable message using whatever ttl remains in the + * original datagram. Since, for gateways, the remaining ttl is + * zero, the icmp "time exceeded" is guaranteed to not make it back + * to us. The behavior of this bug is slightly more interesting + * when it appears on the destination system: + * + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 39 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 19 ms + * 5 ccn-nerif35.Berkeley.EDU (128.32.168.35) 39 ms 39 ms 39 ms + * 6 csgw.Berkeley.EDU (128.32.133.254) 39 ms 59 ms 39 ms + * 7 * * * + * 8 * * * + * 9 * * * + * 10 * * * + * 11 * * * + * 12 * * * + * 13 rip.Berkeley.EDU (128.32.131.22) 59 ms ! 39 ms ! 39 ms ! + * + * Notice that there are 12 "gateways" (13 is the final + * destination) and exactly the last half of them are "missing". + * What's really happening is that rip (a Sun-3 running Sun OS3.5) + * is using the ttl from our arriving datagram as the ttl in its + * icmp reply. So, the reply will time out on the return path + * (with no notice sent to anyone since icmp's aren't sent for + * icmp's) until we probe with a ttl that's at least twice the path + * length. I.e., rip is really only 7 hops away. A reply that + * returns with a ttl of 1 is a clue this problem exists. + * Traceroute prints a "!" after the time if the ttl is <= 1. + * Since vendors ship a lot of obsolete (DEC's Ultrix, Sun 3.x) or + * non-standard (HPUX) software, expect to see this problem + * frequently and/or take care picking the target host of your + * probes. + * + * Other possible annotations after the time are !H, !N, !P (got a host, + * network or protocol unreachable, respectively), !S or !F (source + * route failed or fragmentation needed -- neither of these should + * ever occur and the associated gateway is busted if you see one). If + * almost all the probes result in some kind of unreachable, traceroute + * will give up and exit. + * + * Notes + * ----- + * This program must be run by root or be setuid. (I suggest that + * you *don't* make it setuid -- casual use could result in a lot + * of unnecessary traffic on our poor, congested nets.) + * + * This program requires a kernel mod that does not appear in any + * system available from Berkeley: A raw ip socket using proto + * IPPROTO_RAW must interpret the data sent as an ip datagram (as + * opposed to data to be wrapped in a ip datagram). See the README + * file that came with the source to this program for a description + * of the mods I made to /sys/netinet/raw_ip.c. Your mileage may + * vary. But, again, ANY 4.x (x < 4) BSD KERNEL WILL HAVE TO BE + * MODIFIED TO RUN THIS PROGRAM. + * + * The udp port usage may appear bizarre (well, ok, it is bizarre). + * The problem is that an icmp message only contains 8 bytes of + * data from the original datagram. 8 bytes is the size of a udp + * header so, if we want to associate replies with the original + * datagram, the necessary information must be encoded into the + * udp header (the ip id could be used but there's no way to + * interlock with the kernel's assignment of ip id's and, anyway, + * it would have taken a lot more kernel hacking to allow this + * code to set the ip id). So, to allow two or more users to + * use traceroute simultaneously, we use this task's pid as the + * source port (the high bit is set to move the port number out + * of the "likely" range). To keep track of which probe is being + * replied to (so times and/or hop counts don't get confused by a + * reply that was delayed in transit), we increment the destination + * port number before each probe. + * + * Don't use this as a coding example. I was trying to find a + * routing problem and this code sort-of popped out after 48 hours + * without sleep. I was amazed it ever compiled, much less ran. + * + * I stole the idea for this program from Steve Deering. Since + * the first release, I've learned that had I attended the right + * IETF working group meetings, I also could have stolen it from Guy + * Almes or Matt Mathis. I don't know (or care) who came up with + * the idea first. I envy the originators' perspicacity and I'm + * glad they didn't keep the idea a secret. + * + * Tim Seaver, Ken Adelman and C. Philip Wood provided bug fixes and/or + * enhancements to the original distribution. + * + * I've hacked up a round-trip-route version of this that works by + * sending a loose-source-routed udp datagram through the destination + * back to yourself. Unfortunately, SO many gateways botch source + * routing, the thing is almost worthless. Maybe one day... + * + * -- Van Jacobson (van@ee.lbl.gov) + * Tue Dec 20 03:50:13 PST 1988 + */ + +#define TRACEROUTE_SO_DEBUG 0 + +/* TODO: undefs were uncommented - ??! we have config system for that! */ +/* probably ok to remove altogether */ +//#undef CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#define CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#undef CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#define CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#undef CONFIG_FEATURE_TRACEROUTE_USE_ICMP +//#define CONFIG_FEATURE_TRACEROUTE_USE_ICMP + +#include "inet_common.h" + +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netinet/udp.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> + +#include "busybox.h" + + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +#ifndef IPPROTO_ICMP +/* Grrrr.... */ +#define IPPROTO_ICMP 1 +#endif +#ifndef IPPROTO_IP +#define IPPROTO_IP 0 +#endif + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +struct ipovly { + unsigned char ih_x1[9]; /* (unused) */ + unsigned char ih_pr; /* protocol */ + short ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_next ui_i.ih_next +#define ui_prev ui_i.ih_prev +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + + +/* Host name and address list */ +struct hostinfo { + char *name; + int n; + u_int32_t *addrs; +}; + +/* Data section of the probe packet */ +struct outdata { + unsigned char seq; /* sequence number of this packet */ + unsigned char ttl; /* ttl packet left with */ + struct timeval tv ATTRIBUTE_PACKED; /* time packet left */ +}; + +struct IFADDRLIST { + u_int32_t addr; + char device[sizeof(struct ifreq)]; +}; + + +static const char route[] = "/proc/net/route"; + +/* last inbound (icmp) packet */ +static unsigned char packet[512] ATTRIBUTE_ALIGNED(32); + +static struct ip *outip; /* last output (udp) packet */ +static struct udphdr *outudp; /* last output (udp) packet */ +static struct outdata *outdata; /* last output (udp) packet */ + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static struct icmp *outicmp; /* last output (icmp) packet */ +#endif + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +/* Maximum number of gateways (include room for one noop) */ +#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(u_int32_t))) +/* loose source route gateway list (including room for final destination) */ +static u_int32_t gwlist[NGATEWAYS + 1]; +#endif + +static int s; /* receive (icmp) socket file descriptor */ +static int sndsock; /* send (udp/icmp) socket file descriptor */ + +static struct sockaddr_storage whereto; /* Who to try to reach */ +static struct sockaddr_storage wherefrom; /* Who we are */ +static int packlen; /* total length of packet */ +static int minpacket; /* min ip packet size */ +static int maxpacket = 32 * 1024; /* max ip packet size */ +static int pmtu; /* Path MTU Discovery (RFC1191) */ + +static char *hostname; + +static u_short ident; +static u_short port = 32768 + 666; /* start udp dest port # for probe packets */ + +static int waittime = 5; /* time to wait for response (in seconds) */ +static int nflag; /* print addresses numerically */ +static int doipcksum = 1; /* calculate ip checksums by default */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static int optlen; /* length of ip options */ +#else +#define optlen 0 +#endif + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static int useicmp; /* use icmp echo instead of udp packets */ +#endif +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +static int verbose; +#endif + +/* + * Return the interface list + */ +static int +ifaddrlist(struct IFADDRLIST **ipaddrp) +{ + int fd, nipaddr; +#ifdef HAVE_SOCKADDR_SA_LEN + int n; +#endif + struct ifreq *ifrp, *ifend, *ifnext; + struct sockaddr_in *addr_sin; + struct IFADDRLIST *al; + struct ifconf ifc; + struct ifreq ibuf[(32 * 1024) / sizeof(struct ifreq)], ifr; + struct IFADDRLIST *st_ifaddrlist; + + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + ifc.ifc_len = sizeof(ibuf); + ifc.ifc_buf = (caddr_t)ibuf; + + if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 || + ifc.ifc_len < sizeof(struct ifreq)) { + if (errno == EINVAL) + bb_error_msg_and_die( + "SIOCGIFCONF: ifreq struct too small (%d bytes)", + (int)sizeof(ibuf)); + else + bb_perror_msg_and_die("SIOCGIFCONF"); + } + ifrp = ibuf; + ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len); + + nipaddr = 1 + (ifc.ifc_len / sizeof(struct ifreq)); + st_ifaddrlist = xzalloc(nipaddr * sizeof(struct IFADDRLIST)); + al = st_ifaddrlist; + nipaddr = 0; + + for (; ifrp < ifend; ifrp = ifnext) { +#ifdef HAVE_SOCKADDR_SA_LEN + n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name); + if (n < sizeof(*ifrp)) + ifnext = ifrp + 1; + else + ifnext = (struct ifreq *)((char *)ifrp + n); + if (ifrp->ifr_addr.sa_family != AF_INET) + continue; +#else + ifnext = ifrp + 1; +#endif + /* + * Need a template to preserve address info that is + * used below to locate the next entry. (Otherwise, + * SIOCGIFFLAGS stomps over it because the requests + * are returned in a union.) + */ + strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) { + if (errno == ENXIO) + continue; + bb_perror_msg_and_die("SIOCGIFFLAGS: %.*s", + (int)sizeof(ifr.ifr_name), ifr.ifr_name); + } + + /* Must be up */ + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + + safe_strncpy(al->device, ifr.ifr_name, sizeof(ifr.ifr_name) + 1); +#ifdef sun + /* Ignore sun virtual interfaces */ + if (strchr(al->device, ':') != NULL) + continue; +#endif + if (ioctl(fd, SIOCGIFADDR, (char *)&ifr) < 0) + bb_perror_msg_and_die("SIOCGIFADDR: %s", al->device); + + addr_sin = (struct sockaddr_in *)&ifr.ifr_addr; + al->addr = addr_sin->sin_addr.s_addr; + ++al; + ++nipaddr; + } + if (nipaddr == 0) + bb_error_msg_and_die ("can't find any network interfaces"); + (void)close(fd); + + *ipaddrp = st_ifaddrlist; + return nipaddr; +} + + +static void +setsin(struct sockaddr_in *addr_sin, u_int32_t addr) +{ + memset(addr_sin, 0, sizeof(*addr_sin)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr_sin->sin_len = sizeof(*addr_sin); +#endif + addr_sin->sin_family = AF_INET; + addr_sin->sin_addr.s_addr = addr; +} + + +/* + * Return the source address for the given destination address + */ +static void +findsaddr(const struct sockaddr_in *to, struct sockaddr_in *from) +{ + int i, n; + FILE *f; + u_int32_t mask; + u_int32_t dest, tmask; + struct IFADDRLIST *al; + char buf[256], tdevice[256], device[256]; + + f = xfopen(route, "r"); + + /* Find the appropriate interface */ + n = 0; + mask = 0; + device[0] = '\0'; + while (fgets(buf, sizeof(buf), f) != NULL) { + ++n; + if (n == 1 && strncmp(buf, "Iface", 5) == 0) + continue; + if ((i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x", + tdevice, &dest, &tmask)) != 3) + bb_error_msg_and_die ("junk in buffer"); + if ((to->sin_addr.s_addr & tmask) == dest && + (tmask > mask || mask == 0)) { + mask = tmask; + strcpy(device, tdevice); + } + } + fclose(f); + + if (device[0] == '\0') + bb_error_msg_and_die ("can't find interface"); + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Find our appropriate source address */ + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) + bb_error_msg_and_die("can't find interface %s", device); + + setsin(from, al->addr); +} + +/* +"Usage: %s [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]\n" +"\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]\n" +"\t[-w waittime] [-z pausemsecs] host [packetlen]" + +*/ + +/* + * Subtract 2 timeval structs: out = out - in. + * Out is assumed to be >= in. + */ +static inline void +tvsub(struct timeval *out, struct timeval *in) +{ + + if ((out->tv_usec -= in->tv_usec) < 0) { + --out->tv_sec; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +static int +wait_for_reply(int sock, struct sockaddr_in *fromp, const struct timeval *tp) +{ + fd_set fds; + struct timeval now, tvwait; + struct timezone tz; + int cc = 0; + socklen_t fromlen = sizeof(*fromp); + + FD_ZERO(&fds); + FD_SET(sock, &fds); + + tvwait.tv_sec = tp->tv_sec + waittime; + tvwait.tv_usec = tp->tv_usec; + (void)gettimeofday(&now, &tz); + tvsub(&tvwait, &now); + + if (select(sock + 1, &fds, NULL, NULL, &tvwait) > 0) + cc = recvfrom(sock, (char *)packet, sizeof(packet), 0, + (struct sockaddr *)fromp, &fromlen); + + return cc; +} + +/* + * Checksum routine for Internet Protocol family headers (C Version) + */ +static u_short +in_cksum(u_short *addr, int len) +{ + int nleft = len; + u_short *w = addr; + u_short answer; + int sum = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) + sum += *(unsigned char *)w; + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return answer; +} + + +static void +send_probe(int seq, int ttl, struct timeval *tp) +{ + int cc; + struct udpiphdr *ui, *oui; + struct ip tip; + + outip->ip_ttl = ttl; + outip->ip_id = htons(ident + seq); + + /* + * In most cases, the kernel will recalculate the ip checksum. + * But we must do it anyway so that the udp checksum comes out + * right. + */ + if (doipcksum) { + outip->ip_sum = + in_cksum((u_short *)outip, sizeof(*outip) + optlen); + if (outip->ip_sum == 0) + outip->ip_sum = 0xffff; + } + + /* Payload */ + outdata->seq = seq; + outdata->ttl = ttl; + memcpy(&outdata->tv, tp, sizeof(outdata->tv)); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + outicmp->icmp_seq = htons(seq); + else +#endif + outudp->dest = htons(port + seq); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + /* Always calculate checksum for icmp packets */ + outicmp->icmp_cksum = 0; + outicmp->icmp_cksum = in_cksum((u_short *)outicmp, + packlen - (sizeof(*outip) + optlen)); + if (outicmp->icmp_cksum == 0) + outicmp->icmp_cksum = 0xffff; + } else +#endif + if (doipcksum) { + /* Checksum (we must save and restore ip header) */ + tip = *outip; + ui = (struct udpiphdr *)outip; + oui = (struct udpiphdr *)&tip; + /* Easier to zero and put back things that are ok */ + memset((char *)ui, 0, sizeof(ui->ui_i)); + ui->ui_src = oui->ui_src; + ui->ui_dst = oui->ui_dst; + ui->ui_pr = oui->ui_pr; + ui->ui_len = outudp->len; + outudp->check = 0; + outudp->check = in_cksum((u_short *)ui, packlen); + if (outudp->check == 0) + outudp->check = 0xffff; + *outip = tip; + } + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + /* XXX undocumented debugging hack */ + if (verbose > 1) { + const u_short *sp; + int nshorts, i; + + sp = (u_short *)outip; + nshorts = (u_int)packlen / sizeof(u_short); + i = 0; + printf("[ %d bytes", packlen); + while (--nshorts >= 0) { + if ((i++ % 8) == 0) + printf("\n\t"); + printf(" %04x", ntohs(*sp)); + sp++; + } + if (packlen & 1) { + if ((i % 8) == 0) + printf("\n\t"); + printf(" %02x", *(unsigned char *)sp); + } + printf("]\n"); + } +#endif + +#if !defined(IP_HDRINCL) && defined(IP_TTL) + if (setsockopt(sndsock, IPPROTO_IP, IP_TTL, + (char *)&ttl, sizeof(ttl)) < 0) { + bb_perror_msg_and_die("setsockopt ttl %d", ttl); + } +#endif + + cc = sendto(sndsock, (char *)outip, + packlen, 0, (struct sockaddr *)&whereto, sizeof(whereto)); + if (cc < 0 || cc != packlen) { + if (cc < 0) + bb_perror_msg_and_die("sendto"); + printf("%s: wrote %s %d chars, ret=%d\n", + applet_name, hostname, packlen, cc); + (void)fflush(stdout); + } +} + +static inline double +deltaT(struct timeval *t1p, struct timeval *t2p) +{ + double dt; + + dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 + + (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0; + return dt; +} + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +/* + * Convert an ICMP "type" field to a printable string. + */ +static inline const char * +pr_type(unsigned char t) +{ + static const char * const ttab[] = { + "Echo Reply", "ICMP 1", "ICMP 2", "Dest Unreachable", + "Source Quench", "Redirect", "ICMP 6", "ICMP 7", + "Echo", "Router Advert", "Router Solicit", "Time Exceeded", + "Param Problem", "Timestamp", "Timestamp Reply", "Info Request", + "Info Reply", "Mask Request", "Mask Reply" + }; + + if (t > 18) + return "OUT-OF-RANGE"; + + return ttab[t]; +} +#endif + +static int +packet_ok(unsigned char *buf, int cc, struct sockaddr_in *from, int seq) +{ + struct icmp *icp; + unsigned char type, code; + int hlen; + struct ip *ip; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) { +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf("packet too short (%d bytes) from %s\n", cc, + inet_ntoa(from->sin_addr)); +#endif + return 0; + } + cc -= hlen; + icp = (struct icmp *)(buf + hlen); + type = icp->icmp_type; + code = icp->icmp_code; + /* Path MTU Discovery (RFC1191) */ + if (code != ICMP_UNREACH_NEEDFRAG) + pmtu = 0; + else { + pmtu = ntohs(icp->icmp_nextmtu); + } + if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) || + type == ICMP_UNREACH || type == ICMP_ECHOREPLY) { + struct ip *hip; + struct udphdr *up; + + hip = &icp->icmp_ip; + hlen = hip->ip_hl << 2; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + struct icmp *hicmp; + + /* XXX */ + if (type == ICMP_ECHOREPLY && + icp->icmp_id == htons(ident) && + icp->icmp_seq == htons(seq)) + return -2; + + hicmp = (struct icmp *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 8 <= cc && + hip->ip_p == IPPROTO_ICMP && + hicmp->icmp_id == htons(ident) && + hicmp->icmp_seq == htons(seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } else +#endif + { + up = (struct udphdr *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 12 <= cc && + hip->ip_p == IPPROTO_UDP && + up->source == htons(ident) && + up->dest == htons(port + seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } + } +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) { + int i; + u_int32_t *lp = (u_int32_t *)&icp->icmp_ip; + + printf("\n%d bytes from %s to " + "%s: icmp type %d (%s) code %d\n", + cc, inet_ntoa(from->sin_addr), + inet_ntoa(ip->ip_dst), type, pr_type(type), icp->icmp_code); + for (i = 4; i < cc ; i += sizeof(*lp)) + printf("%2d: x%8.8x\n", i, *lp++); + } +#endif + return 0; +} + + +/* + * Construct an Internet address representation. + * If the nflag has been supplied, give + * numeric value, otherwise try for symbolic name. + */ +static inline void +inetname(struct sockaddr_in *from) +{ + const char *n = NULL; + const char *ina; + char name[257]; + + if (!nflag && from->sin_addr.s_addr != INADDR_ANY) { + if (INET_rresolve(name, sizeof(name), from, 0x4000, 0xffffffff) >= 0) + n = name; + } + ina = inet_ntoa(from->sin_addr); + if (nflag) + printf(" %s", ina); + else + printf(" %s (%s)", (n ? n : ina), ina); +} + +static inline void +print(unsigned char *buf, int cc, struct sockaddr_in *from) +{ + struct ip *ip; + int hlen; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + cc -= hlen; + + inetname(from); +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf(" %d bytes to %s", cc, inet_ntoa (ip->ip_dst)); +#endif +} + + +static struct hostinfo * +gethostinfo(const char *host) +{ + int n; + struct hostent *hp; + struct hostinfo *hi; + char **p; + u_int32_t addr, *ap; + + hi = xzalloc(sizeof(*hi)); + addr = inet_addr(host); + if ((int32_t)addr != -1) { + hi->name = xstrdup(host); + hi->n = 1; + hi->addrs = xzalloc(sizeof(hi->addrs[0])); + hi->addrs[0] = addr; + return hi; + } + + hp = xgethostbyname(host); + if (hp->h_addrtype != AF_INET || hp->h_length != 4) + bb_perror_msg_and_die("bad host %s", host); + hi->name = xstrdup(hp->h_name); + for (n = 0, p = hp->h_addr_list; *p != NULL; ++n, ++p) + continue; + hi->n = n; + hi->addrs = xzalloc(n * sizeof(hi->addrs[0])); + for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p) + memcpy(ap, *p, sizeof(*ap)); + return hi; +} + +static void +freehostinfo(struct hostinfo *hi) +{ + free(hi->name); + hi->name = NULL; + free((char *)hi->addrs); + free((char *)hi); +} + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static void +getaddr(u_int32_t *ap, const char *host) +{ + struct hostinfo *hi; + + hi = gethostinfo(host); + *ap = hi->addrs[0]; + freehostinfo(hi); +} +#endif + + +int +traceroute_main(int argc, char *argv[]) +{ + static const int on = 1; + + int code, n; + unsigned char *outp; + u_int32_t *ap; + struct sockaddr_in *from = (struct sockaddr_in *)&wherefrom; + struct sockaddr_in *to = (struct sockaddr_in *)&whereto; + struct hostinfo *hi; + int ttl, probe, i; + int seq = 0; + int tos = 0; + char *tos_str = NULL; + char *source = NULL; + unsigned long op; + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + int lsrr = 0; +#endif + u_short off = 0; + struct IFADDRLIST *al; + char *device = NULL; + int max_ttl = 30; + char *max_ttl_str = NULL; + char *port_str = NULL; + int nprobes = 3; + char *nprobes_str = NULL; + char *waittime_str = NULL; + u_int pausemsecs = 0; + char *pausemsecs_str = NULL; + int first_ttl = 1; + char *first_ttl_str = NULL; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + llist_t *sourse_route_list = NULL; +#endif + + opterr = 0; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + opt_complementary = "x-x:g::"; +#else + opt_complementary = "x-x"; +#endif + + op = getopt32(argc, argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:" +#define USAGE_OP_DONT_FRAGMNT (1<<0) /* F */ +#define USAGE_OP_USE_ICMP (1<<1) /* I */ +#define USAGE_OP_TTL_FLAG (1<<2) /* l */ +#define USAGE_OP_ADDR_NUM (1<<3) /* n */ +#define USAGE_OP_BYPASS_ROUTE (1<<4) /* r */ +#define USAGE_OP_DEBUG (1<<5) /* d */ +#define USAGE_OP_VERBOSE (1<<6) /* v */ +#define USAGE_OP_IP_CHKSUM (1<<7) /* x */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + "g:" +#endif + , &tos_str, &device, &max_ttl_str, &port_str, &nprobes_str, + &source, &waittime_str, &pausemsecs_str, &first_ttl_str +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + , &sourse_route_list +#endif + ); + + if (op & USAGE_OP_DONT_FRAGMNT) + off = IP_DF; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + useicmp = op & USAGE_OP_USE_ICMP; +#endif + nflag = op & USAGE_OP_ADDR_NUM; +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + verbose = op & USAGE_OP_VERBOSE; +#endif + if (op & USAGE_OP_IP_CHKSUM) { + doipcksum = 0; + bb_error_msg("warning: ip checksums disabled"); + } + if (tos_str) + tos = xatoul_range(tos_str, 0, 255); + if (max_ttl_str) + max_ttl = xatoul_range(max_ttl_str, 1, 255); + if (port_str) + port = xatou16(port_str); + if (nprobes_str) + nprobes = xatoul_range(nprobes_str, 1, INT_MAX); + if (source) { + /* + * set the ip source address of the outbound + * probe (e.g., on a multi-homed host). + */ + if (getuid()) bb_error_msg_and_die("-s %s: permission denied", source); + } + if (waittime_str) + waittime = xatoul_range(waittime_str, 2, 24 * 60 * 60); + if (pausemsecs_str) + pausemsecs = xatoul_range(pausemsecs_str, 0, 60 * 60 * 1000); + if (first_ttl_str) + first_ttl = xatoul_range(first_ttl_str, 1, 255); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + if (sourse_route_list) { + llist_t *l_sr; + + for(l_sr = sourse_route_list; l_sr; ) { + if (lsrr >= NGATEWAYS) + bb_error_msg_and_die("no more than %d gateways", NGATEWAYS); + getaddr(gwlist + lsrr, l_sr->data); + ++lsrr; + l_sr = l_sr->link; + free(sourse_route_list); + sourse_route_list = l_sr; + } + optlen = (lsrr + 1) * sizeof(gwlist[0]); + } +#endif + + if (first_ttl > max_ttl) { + bb_error_msg_and_die( + "first ttl (%d) may not be greater than max ttl (%d)", + first_ttl, max_ttl); + } + + minpacket = sizeof(*outip) + sizeof(*outdata) + optlen; + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + minpacket += 8; /* XXX magic number */ + else +#endif + minpacket += sizeof(*outudp); + packlen = minpacket; /* minimum sized packet */ + + /* Process destination and optional packet size */ + switch (argc - optind) { + + case 2: + packlen = xatoul_range(argv[optind + 1], minpacket, maxpacket); + /* Fall through */ + + case 1: + hostname = argv[optind]; + hi = gethostinfo(hostname); + setsin(to, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg("warning: %s has multiple addresses; using %s", + hostname, inet_ntoa(to->sin_addr)); + hostname = hi->name; + hi->name = NULL; + freehostinfo(hi); + break; + + default: + bb_show_usage(); + } + + /* Insure the socket fds won't be 0, 1 or 2 */ + do n = xopen(bb_dev_null, O_RDONLY); while (n < 2); + if (n > 2) + close(n); + + s = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(s, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + sndsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +#if defined(IP_OPTIONS) + if (lsrr > 0) { + unsigned char optlist[MAX_IPOPTLEN]; + + /* final hop */ + gwlist[lsrr] = to->sin_addr.s_addr; + ++lsrr; + + /* force 4 byte alignment */ + optlist[0] = IPOPT_NOP; + /* loose source route option */ + optlist[1] = IPOPT_LSRR; + i = lsrr * sizeof(gwlist[0]); + optlist[2] = i + 3; + /* Pointer to LSRR addresses */ + optlist[3] = IPOPT_MINOFF; + memcpy(optlist + 4, gwlist, i); + + if ((setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS, + (char *)optlist, i + sizeof(gwlist[0]))) < 0) { + bb_perror_msg_and_die("IP_OPTIONS"); + } + } +#endif /* IP_OPTIONS */ +#endif /* CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE */ + +#ifdef SO_SNDBUF + if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, (char *)&packlen, + sizeof(packlen)) < 0) { + bb_perror_msg_and_die("SO_SNDBUF"); + } +#endif +#ifdef IP_HDRINCL + if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, (char *)&on, + sizeof(on)) < 0 && errno != ENOPROTOOPT) { + bb_perror_msg_and_die("IP_HDRINCL"); + } +#else +#ifdef IP_TOS + if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, + (char *)&tos, sizeof(tos)) < 0) { + bb_perror_msg_and_die("setsockopt tos %d", tos); + } +#endif +#endif +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + /* Revert to non-privileged user after opening sockets */ + xsetgid(getgid()); + xsetuid(getuid()); + + outip = (struct ip *)xzalloc(packlen); + + outip->ip_v = IPVERSION; + if (tos_str) + outip->ip_tos = tos; + outip->ip_len = htons(packlen); + outip->ip_off = htons(off); + outp = (unsigned char *)(outip + 1); + outip->ip_dst = to->sin_addr; + + outip->ip_hl = (outp - (unsigned char *)outip) >> 2; + ident = (getpid() & 0xffff) | 0x8000; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + outip->ip_p = IPPROTO_ICMP; + + outicmp = (struct icmp *)outp; + outicmp->icmp_type = ICMP_ECHO; + outicmp->icmp_id = htons(ident); + + outdata = (struct outdata *)(outp + 8); /* XXX magic number */ + } else +#endif + { + outip->ip_p = IPPROTO_UDP; + + outudp = (struct udphdr *)outp; + outudp->source = htons(ident); + outudp->len = + htons((u_short)(packlen - (sizeof(*outip) + optlen))); + outdata = (struct outdata *)(outudp + 1); + } + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Look for a specific device */ + if (device != NULL) { + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) { + bb_error_msg_and_die("can't find interface %s", device); + } + } + + /* Determine our source address */ + if (source == NULL) { + /* + * If a device was specified, use the interface address. + * Otherwise, try to determine our source address. + */ + if (device != NULL) + setsin(from, al->addr); + findsaddr(to, from); + } else { + hi = gethostinfo(source); + source = hi->name; + hi->name = NULL; + /* + * If the device was specified make sure it + * corresponds to the source address specified. + * Otherwise, use the first address (and warn if + * there are more than one). + */ + if (device != NULL) { + for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap) + if (*ap == al->addr) + break; + if (i <= 0) { + bb_error_msg_and_die( + "%s is not on interface %s", + source, device); + } + setsin(from, *ap); + } else { + setsin(from, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg( + "Warning: %s has multiple addresses; using %s", + source, inet_ntoa(from->sin_addr)); + } + freehostinfo(hi); + } + + outip->ip_src = from->sin_addr; +#ifndef IP_HDRINCL + xbind(sndsock, (struct sockaddr *)from, sizeof(*from)); +#endif + + fprintf(stderr, "traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr)); + if (source) + fprintf(stderr, " from %s", source); + fprintf(stderr, ", %d hops max, %d byte packets\n", max_ttl, packlen); + (void)fflush(stderr); + + for (ttl = first_ttl; ttl <= max_ttl; ++ttl) { + u_int32_t lastaddr = 0; + int gotlastaddr = 0; + int got_there = 0; + int unreachable = 0; + int sentfirst = 0; + + printf("%2d ", ttl); + for (probe = 0; probe < nprobes; ++probe) { + int cc; + struct timeval t1, t2; + struct timezone tz; + struct ip *ip; + + if (sentfirst && pausemsecs > 0) + usleep(pausemsecs * 1000); + (void)gettimeofday(&t1, &tz); + send_probe(++seq, ttl, &t1); + ++sentfirst; + while ((cc = wait_for_reply(s, from, &t1)) != 0) { + (void)gettimeofday(&t2, &tz); + i = packet_ok(packet, cc, from, seq); + /* Skip short packet */ + if (i == 0) + continue; + if (!gotlastaddr || + from->sin_addr.s_addr != lastaddr) { + print(packet, cc, from); + lastaddr = from->sin_addr.s_addr; + ++gotlastaddr; + } + printf(" %.3f ms", deltaT(&t1, &t2)); + ip = (struct ip *)packet; + if (op & USAGE_OP_TTL_FLAG) + printf(" (%d)", ip->ip_ttl); + if (i == -2) { + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + } + /* time exceeded in transit */ + if (i == -1) + break; + code = i - 1; + switch (code) { + + case ICMP_UNREACH_PORT: + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + + case ICMP_UNREACH_NET: + ++unreachable; + printf(" !N"); + break; + + case ICMP_UNREACH_HOST: + ++unreachable; + printf(" !H"); + break; + + case ICMP_UNREACH_PROTOCOL: + ++got_there; + printf(" !P"); + break; + + case ICMP_UNREACH_NEEDFRAG: + ++unreachable; + printf(" !F-%d", pmtu); + break; + + case ICMP_UNREACH_SRCFAIL: + ++unreachable; + printf(" !S"); + break; + + case ICMP_UNREACH_FILTER_PROHIB: + case ICMP_UNREACH_NET_PROHIB: /* misuse */ + ++unreachable; + printf(" !A"); + break; + + case ICMP_UNREACH_HOST_PROHIB: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_HOST_PRECEDENCE: + ++unreachable; + printf(" !V"); + break; + + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_HOST_UNKNOWN: + ++unreachable; + printf(" !U"); + break; + + case ICMP_UNREACH_ISOLATED: + ++unreachable; + printf(" !I"); + break; + + case ICMP_UNREACH_TOSNET: + case ICMP_UNREACH_TOSHOST: + ++unreachable; + printf(" !T"); + break; + + default: + ++unreachable; + printf(" !<%d>", code); + break; + } + break; + } + if (cc == 0) + printf(" *"); + (void)fflush(stdout); + } + putchar('\n'); + if (got_there || + (unreachable > 0 && unreachable >= nprobes - 1)) + break; + } + return 0; +} diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in new file mode 100644 index 0000000..f633473 --- /dev/null +++ b/networking/udhcp/Config.in @@ -0,0 +1,67 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +config APP_UDHCPD + bool "udhcp Server (udhcpd)" + default n + help + uDHCPd is a DHCP server geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + See http://udhcp.busybox.net for further details. + +config APP_DHCPRELAY + bool "dhcprelay" + default n + depends on APP_UDHCPD + help + dhcprelay listens for dhcp requests on one or more interfaces + and forwards these requests to a different interface or dhcp + server. + +config APP_DUMPLEASES + bool "Lease display utility (dumpleases)" + default n + depends on APP_UDHCPD + help + dumpleases displays the leases written out by the udhcpd server. + Lease times are stored in the file by time remaining in lease, or + by the absolute time that it expires in seconds from epoch. + + See http://udhcp.busybox.net for further details. + +config APP_UDHCPC + bool "udhcp Client (udhcpc)" + default n + help + uDHCPc is a DHCP client geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + The udhcp client negotiates a lease with the DHCP server and + notifies a set of scripts when a lease is obtained or lost. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_SYSLOG + bool "Log udhcp messages to syslog" + default n + depends on APP_UDHCPD || APP_UDHCPC + select FEATURE_SYSLOG + help + If not daemonized, udhcpd prints its messages to stdout/stderr. + If this option is selected, it will also log them to syslog. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_DEBUG + bool "Compile udhcp with noisy debugging messages" + default n + depends on APP_UDHCPD || APP_UDHCPC + help + If selected, udhcpd will output extra debugging output. If using + this option, compile uDHCP with "-g", and do not fork the daemon to + the background. + + See http://udhcp.busybox.net for further details. diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild new file mode 100644 index 0000000..dc2c01f --- /dev/null +++ b/networking/udhcp/Kbuild @@ -0,0 +1,18 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_APP_UDHCPC) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPD) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPC) += dhcpc.o clientpacket.o clientsocket.o \ + script.o +lib-$(CONFIG_APP_UDHCPD) += dhcpd.o arpping.o files.o leases.o \ + serverpacket.o static_leases.o +lib-$(CONFIG_APP_DUMPLEASES) += dumpleases.o +lib-$(CONFIG_APP_DHCPRELAY) += dhcprelay.o diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c new file mode 100644 index 0000000..9c8b9c5 --- /dev/null +++ b/networking/udhcp/arpping.c @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +/* + * arpping.c + * + * Mostly stolen from: dhcpcd - DHCP client daemon + * by Yoichi Hariguchi <yoichi@fore.com> + */ + +#include <netinet/if_ether.h> +#include <net/if_arp.h> + +#include "common.h" +#include "dhcpd.h" + + +struct arpMsg { + /* Ethernet header */ + u_char h_dest[6]; /* destination ether addr */ + u_char h_source[6]; /* source ether addr */ + u_short h_proto; /* packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* hardware address length (must be 6) */ + uint8_t plen; /* protocol address length (must be 4) */ + uint16_t operation; /* ARP opcode */ + uint8_t sHaddr[6]; /* sender's hardware address */ + uint8_t sInaddr[4]; /* sender's IP address */ + uint8_t tHaddr[6]; /* target's hardware address */ + uint8_t tInaddr[4]; /* target's IP address */ + uint8_t pad[18]; /* pad for min. Ethernet payload (60 bytes) */ +} ATTRIBUTE_PACKED; + +/* args: yiaddr - what IP to ping + * ip - our ip + * mac - our arp address + * interface - interface to use + * retn: 1 addr free + * 0 addr used + * -1 error + */ + +/* FIXME: match response against chaddr */ +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *mac, char *interface) +{ + int timeout = 2; + int s; /* socket */ + int rv = 1; /* return value */ + struct sockaddr addr; /* for interface name */ + struct arpMsg arp; + fd_set fdset; + struct timeval tm; + time_t prevTime; + + + s = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + if (s == -1) { + bb_perror_msg(bb_msg_can_not_create_raw_socket); + return -1; + } + + if (setsockopt_broadcast(s) == -1) { + bb_perror_msg("cannot setsocketopt on raw socket"); + close(s); + return -1; + } + + /* send arp request */ + memset(&arp, 0, sizeof(arp)); + memcpy(arp.h_dest, MAC_BCAST_ADDR, 6); /* MAC DA */ + memcpy(arp.h_source, mac, 6); /* MAC SA */ + arp.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ + arp.htype = htons(ARPHRD_ETHER); /* hardware type */ + arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ + arp.hlen = 6; /* hardware address length */ + arp.plen = 4; /* protocol address length */ + arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ + memcpy(arp.sInaddr, &ip, sizeof(ip)); /* source IP address */ + memcpy(arp.sHaddr, mac, 6); /* source hardware address */ + memcpy(arp.tInaddr, &yiaddr, sizeof(yiaddr)); /* target IP address */ + + memset(&addr, 0, sizeof(addr)); + strcpy(addr.sa_data, interface); + if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) + rv = 0; + + /* wait arp reply, and check it */ + tm.tv_usec = 0; + prevTime = uptime(); + while (timeout > 0) { + FD_ZERO(&fdset); + FD_SET(s, &fdset); + tm.tv_sec = timeout; + if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) { + bb_perror_msg("error on ARPING request"); + if (errno != EINTR) rv = 0; + } else if (FD_ISSET(s, &fdset)) { + if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0; + if (arp.operation == htons(ARPOP_REPLY) && + memcmp(arp.tHaddr, mac, 6) == 0 && + *((uint32_t *) arp.sInaddr) == yiaddr) { + DEBUG("Valid arp reply received for this address"); + rv = 0; + break; + } + } + timeout -= uptime() - prevTime; + prevTime = uptime(); + } + close(s); + DEBUG("%salid arp replies for this address", rv ? "No v" : "V"); + return rv; +} diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c new file mode 100644 index 0000000..15cbda2 --- /dev/null +++ b/networking/udhcp/clientpacket.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: */ +/* clientpacket.c + * + * Packet generation and dispatching functions for the DHCP client. + * + * Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <features.h> +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <asm/types.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#endif + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* Create a random xid */ +unsigned long random_xid(void) +{ + static int initialized; + if (!initialized) { + unsigned long seed; + + if (open_read_close("/dev/urandom", &seed, sizeof(seed)) < 0) { + bb_info_msg("Cannot load seed " + "from /dev/urandom: %s", strerror(errno)); + seed = time(0); + } + srand(seed); + initialized++; + } + return rand(); +} + + +/* initialize a packet with the proper defaults */ +static void init_packet(struct dhcpMessage *packet, char type) +{ + udhcp_init_header(packet, type); + memcpy(packet->chaddr, client_config.arp, 6); + if (client_config.clientid) + add_option_string(packet->options, client_config.clientid); + if (client_config.hostname) add_option_string(packet->options, client_config.hostname); + if (client_config.fqdn) add_option_string(packet->options, client_config.fqdn); + add_option_string(packet->options, client_config.vendorclass); +} + + +/* Add a parameter request list for stubborn DHCP servers. Pull the data + * from the struct in options.c. Don't do bounds checking here because it + * goes towards the head of the packet. */ +static void add_requests(struct dhcpMessage *packet) +{ + int end = end_option(packet->options); + int i, len = 0; + + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + for (i = 0; dhcp_options[i].code; i++) + if (dhcp_options[i].flags & OPTION_REQ) + packet->options[end + OPT_DATA + len++] = dhcp_options[i].code; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + +} + + +/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ +int send_discover(unsigned long xid, unsigned long requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDISCOVER); + packet.xid = xid; + if (requested) + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + + add_requests(&packet); + bb_info_msg("Sending discover..."); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Broadcasts a DHCP request message */ +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested) +{ + struct dhcpMessage packet; + struct in_addr addr; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + add_requests(&packet); + addr.s_addr = requested; + bb_info_msg("Sending select for %s...", inet_ntoa(addr)); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Unicasts or broadcasts a DHCP renew message */ +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + int ret = 0; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + packet.ciaddr = ciaddr; + + add_requests(&packet); + bb_info_msg("Sending renew..."); + if (server) + ret = udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); + else ret = udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); + return ret; +} + + +/* Unicasts a DHCP release message */ +int send_release(unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPRELEASE); + packet.xid = random_xid(); + packet.ciaddr = ciaddr; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + bb_info_msg("Sending release..."); + return udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); +} + + +/* return -1 on errors that are fatal for the socket, -2 for those that aren't */ +int get_raw_packet(struct dhcpMessage *payload, int fd) +{ + int bytes; + struct udp_dhcp_packet packet; + uint32_t source, dest; + uint16_t check; + + memset(&packet, 0, sizeof(struct udp_dhcp_packet)); + bytes = read(fd, &packet, sizeof(struct udp_dhcp_packet)); + if (bytes < 0) { + DEBUG("Cannot read on raw listening socket - ignoring"); + usleep(500000); /* possible down interface, looping condition */ + return -1; + } + + if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { + DEBUG("Message too short, ignoring"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + DEBUG("Truncated packet"); + return -2; + } + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + /* Make sure its the right packet for us, and that it passes sanity checks */ + if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION + || packet.ip.ihl != sizeof(packet.ip) >> 2 + || packet.udp.dest != htons(CLIENT_PORT) + || bytes > (int) sizeof(struct udp_dhcp_packet) + || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) + ) { + DEBUG("Unrelated/bogus packet"); + return -2; + } + + /* check IP checksum */ + check = packet.ip.check; + packet.ip.check = 0; + if (check != udhcp_checksum(&(packet.ip), sizeof(packet.ip))) { + DEBUG("bad IP header checksum, ignoring"); + return -1; + } + + /* verify the UDP checksum by replacing the header with a psuedo header */ + source = packet.ip.saddr; + dest = packet.ip.daddr; + check = packet.udp.check; + packet.udp.check = 0; + memset(&packet.ip, 0, sizeof(packet.ip)); + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source; + packet.ip.daddr = dest; + packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ + if (check && check != udhcp_checksum(&packet, bytes)) { + bb_error_msg("packet with bad UDP checksum received, ignoring"); + return -2; + } + + memcpy(payload, &(packet.data), bytes - (sizeof(packet.ip) + sizeof(packet.udp))); + + if (ntohl(payload->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message (bad magic) - ignoring"); + return -2; + } + DEBUG("oooooh!!! got some!"); + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); + +} diff --git a/networking/udhcp/clientsocket.c b/networking/udhcp/clientsocket.c new file mode 100644 index 0000000..8520619 --- /dev/null +++ b/networking/udhcp/clientsocket.c @@ -0,0 +1,59 @@ +/* vi: set sw=4 ts=4: */ +/* + * clientsocket.c -- DHCP client socket creation + * + * udhcp client + * + * Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <features.h> +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <asm/types.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#endif + +#include "common.h" + + +int raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + DEBUG("Opening raw socket on ifindex %d", ifindex); + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c new file mode 100644 index 0000000..3e916f4 --- /dev/null +++ b/networking/udhcp/common.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* common.c + * + * Functions for debugging and logging as well as some other + * simple helper functions. + * + * Russ Dill <Russ.Dill@asu.edu> 2001-2003 + * Rewritten by Vladimir Oleynik <dzo@simtreas.ru> (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <syslog.h> + +#include "common.h" + + +long uptime(void) +{ + struct sysinfo info; + sysinfo(&info); + return info.uptime; +} + +/* + * This function makes sure our first socket calls + * aren't going to fd 1 (printf badness...) and are + * not later closed by daemon() + */ +static inline void sanitize_fds(void) +{ + int fd = xopen(bb_dev_null, O_RDWR); + while (fd < 3) + fd = dup(fd); + close(fd); +} + + +void udhcp_background(const char *pidfile) +{ +#ifdef __uClinux__ + bb_error_msg("cannot background in uclinux (yet)"); +#else /* __uClinux__ */ + int pid_fd; + + /* hold lock during fork. */ + pid_fd = pidfile_acquire(pidfile); + setsid(); + xdaemon(0, 0); + logmode &= ~LOGMODE_STDIO; + pidfile_write_release(pid_fd); +#endif /* __uClinux__ */ +} + +void udhcp_start_log_and_pid(const char *pidfile) +{ + int pid_fd; + + /* Make sure our syslog fd isn't overwritten */ + sanitize_fds(); + + /* do some other misc startup stuff while we are here to save bytes */ + pid_fd = pidfile_acquire(pidfile); + pidfile_write_release(pid_fd); + + /* equivelent of doing a fflush after every \n */ + setlinebuf(stdout); + + if (ENABLE_FEATURE_UDHCP_SYSLOG) { + openlog(applet_name, LOG_PID, LOG_LOCAL0); + logmode |= LOGMODE_SYSLOG; + } + + bb_info_msg("%s (v%s) started", applet_name, BB_VER); +} diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h new file mode 100644 index 0000000..70a7693 --- /dev/null +++ b/networking/udhcp/common.h @@ -0,0 +1,108 @@ +/* vi: set sw=4 ts=4: */ +/* common.h + * + * Russ Dill <Russ.Dill@asu.edu> September 2001 + * Rewritten by Vladimir Oleynik <dzo@simtreas.ru> (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include "busybox.h" + +#ifdef CONFIG_INSTALL_NO_USR +# define DEFAULT_SCRIPT "/share/udhcpc/default.script" +#else +# define DEFAULT_SCRIPT "/usr/share/udhcpc/default.script" +#endif + +#define COMBINED_BINARY + + +/*** packet.h ***/ + +#include <netinet/udp.h> +#include <netinet/ip.h> + +struct dhcpMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[308]; /* 312 - cookie */ +}; + +struct udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcpMessage data; +}; + +void udhcp_init_header(struct dhcpMessage *packet, char type); +int udhcp_get_packet(struct dhcpMessage *packet, int fd); +uint16_t udhcp_checksum(void *addr, int count); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex); +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port); + + +/**/ + +void udhcp_background(const char *pidfile); +void udhcp_start_log_and_pid(const char *pidfile); + +void udhcp_run_script(struct dhcpMessage *packet, const char *name); + +// Still need to clean these up... + +/* from pidfile.h */ +#define pidfile_acquire udhcp_pidfile_acquire +#define pidfile_write_release udhcp_pidfile_write_release +/* from options.h */ +#define get_option udhcp_get_option +#define end_option udhcp_end_option +#define add_option_string udhcp_add_option_string +#define add_simple_option udhcp_add_simple_option +#define option_lengths udhcp_option_lengths +/* from socket.h */ +#define listen_socket udhcp_listen_socket +#define read_interface udhcp_read_interface +/* from dhcpc.h */ +#define client_config udhcp_client_config +/* from dhcpd.h */ +#define server_config udhcp_server_config + +long uptime(void); +void udhcp_sp_setup(void); +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd); +int udhcp_sp_read(fd_set *rfds); +int raw_socket(int ifindex); +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp); +int listen_socket(uint32_t ip, int port, char *inf); +int pidfile_acquire(const char *pidfile); +void pidfile_write_release(int pid_fd); +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *arp, char *interface); + +#if ENABLE_FEATURE_UDHCP_DEBUG +# define DEBUG(str, args...) bb_info_msg(str, ## args) +#else +# define DEBUG(str, args...) do {;} while (0) +#endif + +#endif diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 0000000..71315ff --- /dev/null +++ b/networking/udhcp/dhcpc.c @@ -0,0 +1,509 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.c + * + * udhcp DHCP client + * + * Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include <getopt.h> + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +static int state; +/* Something is definitely wrong here. IPv4 addresses + * in variables of type long?? BTW, we use inet_ntoa() + * in the code. Manpage says that struct in_addr has a member of type long (!) + * which holds IPv4 address, and the struct is passed by value (!!) + */ +static unsigned long requested_ip; /* = 0 */ +static unsigned long server_addr; +static unsigned long timeout; +static int packet_num; /* = 0 */ +static int fd = -1; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static int listen_mode; + +struct client_config_t client_config; + + +/* just a little helper */ +static void change_mode(int new_mode) +{ + DEBUG("entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + if (fd >= 0) close(fd); + fd = -1; + listen_mode = new_mode; +} + + +/* perform a renew */ +static void perform_renew(void) +{ + bb_info_msg("Performing a DHCP renew"); + switch (state) { + case BOUND: + change_mode(LISTEN_KERNEL); + case RENEWING: + case REBINDING: + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ + udhcp_run_script(NULL, "deconfig"); + case REQUESTING: + case RELEASED: + change_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } + + /* start things over */ + packet_num = 0; + + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; +} + + +/* perform a release */ +static void perform_release(void) +{ + char buffer[16]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + sprintf(buffer, "%s", inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + bb_info_msg("Unicasting a release of %s to %s", + inet_ntoa(temp_addr), buffer); + send_release(server_addr, requested_ip); /* unicast */ + udhcp_run_script(NULL, "deconfig"); + } + bb_info_msg("Entering released state"); + + change_mode(LISTEN_NONE); + state = RELEASED; + timeout = 0x7fffffff; +} + + +static void client_background(void) +{ + udhcp_background(client_config.pidfile); + client_config.foreground = 1; /* Do not fork again. */ + client_config.background_if_no_lease = 0; +} + + +static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) +{ + uint8_t *storage; + int len = strlen(str); + if (len > 255) len = 255; + storage = xzalloc(len + extra + OPT_DATA); + storage[OPT_CODE] = code; + storage[OPT_LEN] = len + extra; + memcpy(storage + extra + OPT_DATA, str, len); + return storage; +} + + +int udhcpc_main(int argc, char *argv[]) +{ + uint8_t *temp, *message; + char *str_c, *str_V, *str_h, *str_F, *str_r, *str_T, *str_t; + unsigned long t1 = 0, t2 = 0, xid = 0; + unsigned long start = 0, lease = 0; + long now; + unsigned opt; + int max_fd; + int sig; + int retval; + int len; + int no_clientid = 0; + fd_set rfds; + struct timeval tv; + struct dhcpMessage packet; + struct in_addr temp_addr; + + enum { + OPT_c = 1 << 0, + OPT_C = 1 << 1, + OPT_V = 1 << 2, + OPT_f = 1 << 3, + OPT_b = 1 << 4, + OPT_H = 1 << 5, + OPT_h = 1 << 6, + OPT_F = 1 << 7, + OPT_i = 1 << 8, + OPT_n = 1 << 9, + OPT_p = 1 << 10, + OPT_q = 1 << 11, + OPT_R = 1 << 12, + OPT_r = 1 << 13, + OPT_s = 1 << 14, + OPT_T = 1 << 15, + OPT_t = 1 << 16, + OPT_v = 1 << 17, + }; +#if ENABLE_GETOPT_LONG + static const struct option arg_options[] = { + { "clientid", required_argument, 0, 'c' }, + { "clientid-none", no_argument, 0, 'C' }, + { "vendorclass", required_argument, 0, 'V' }, + { "foreground", no_argument, 0, 'f' }, + { "background", no_argument, 0, 'b' }, + { "hostname", required_argument, 0, 'H' }, + { "hostname", required_argument, 0, 'h' }, + { "fqdn", required_argument, 0, 'F' }, + { "interface", required_argument, 0, 'i' }, + { "now", no_argument, 0, 'n' }, + { "pidfile", required_argument, 0, 'p' }, + { "quit", no_argument, 0, 'q' }, + { "release", no_argument, 0, 'R' }, + { "request", required_argument, 0, 'r' }, + { "script", required_argument, 0, 's' }, + { "timeout", required_argument, 0, 'T' }, + { "version", no_argument, 0, 'v' }, + { "retries", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; +#endif + /* Default options. */ + client_config.interface = "eth0"; + client_config.script = DEFAULT_SCRIPT; + client_config.retries = 3; + client_config.timeout = 3; + + /* Parse command line */ + opt_complementary = "?:c--C:C--c" // mutually exclusive + ":hH:Hh"; // -h and -H are the same +#if ENABLE_GETOPT_LONG + applet_long_options = arg_options; +#endif + opt = getopt32(argc, argv, "c:CV:fbH:h:F:i:np:qRr:s:T:t:v", + &str_c, &str_V, &str_h, &str_h, &str_F, + &client_config.interface, &client_config.pidfile, &str_r, + &client_config.script, &str_T, &str_t + ); + + if (opt & OPT_c) + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0); + if (opt & OPT_C) + no_clientid = 1; + if (opt & OPT_V) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); + if (opt & OPT_f) + client_config.foreground = 1; + if (opt & OPT_b) + client_config.background_if_no_lease = 1; + if (opt & OPT_h) + client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0); + if (opt & OPT_F) { + client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3); + /* Flags: 0000NEOS + S: 1 => Client requests Server to update A RR in DNS as well as PTR + O: 1 => Server indicates to client that DNS has been updated regardless + E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com" + N: 1 => Client requests Server to not update DNS + */ + client_config.fqdn[OPT_DATA + 0] = 0x1; + /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */ + /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */ + } + // if (opt & OPT_i) client_config.interface = ... + if (opt & OPT_n) + client_config.abort_if_no_lease = 1; + // if (opt & OPT_p) client_config.pidfile = ... + if (opt & OPT_q) + client_config.quit_after_lease = 1; + if (opt & OPT_R) + client_config.release_on_quit = 1; + if (opt & OPT_r) + requested_ip = inet_addr(str_r); + // if (opt & OPT_s) client_config.script = ... + if (opt & OPT_T) + client_config.timeout = xatoi_u(str_T); + if (opt & OPT_t) + client_config.retries = xatoi_u(str_t); + if (opt & OPT_v) { + printf("version %s\n\n", BB_VER); + return 0; + } + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(client_config.pidfile); + + if (read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp) < 0) + return 1; + + /* if not set, and not suppressed, setup the default client ID */ + if (!client_config.clientid && !no_clientid) { + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7); + client_config.clientid[OPT_DATA] = 1; + memcpy(client_config.clientid + OPT_DATA+1, client_config.arp, 6); + } + + if (!client_config.vendorclass) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, "udhcp "BB_VER, 0); + + /* setup the signal pipe */ + udhcp_sp_setup(); + + state = INIT_SELECTING; + udhcp_run_script(NULL, "deconfig"); + change_mode(LISTEN_RAW); + + for (;;) { + tv.tv_sec = timeout - uptime(); + tv.tv_usec = 0; + + if (listen_mode != LISTEN_NONE && fd < 0) { + if (listen_mode == LISTEN_KERNEL) + fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); + else + fd = raw_socket(client_config.ifindex); + if (fd < 0) { + bb_perror_msg("FATAL: cannot listen on socket"); + return 0; + } + } + max_fd = udhcp_sp_fd_set(&rfds, fd); + + if (tv.tv_sec > 0) { + DEBUG("Waiting on select..."); + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + } else retval = 0; /* If we already timed out, fall through */ + + now = uptime(); + if (retval == 0) { + /* timeout dropped to zero */ + switch (state) { + case INIT_SELECTING: + if (packet_num < client_config.retries) { + if (packet_num == 0) + xid = random_xid(); + + /* send discover packet */ + send_discover(xid, requested_ip); /* broadcast */ + + timeout = now + client_config.timeout; + packet_num++; + } else { + udhcp_run_script(NULL, "leasefail"); + if (client_config.background_if_no_lease) { + bb_info_msg("No lease, forking to background"); + client_background(); + } else if (client_config.abort_if_no_lease) { + bb_info_msg("No lease, failing"); + return 1; + } + /* wait to try again */ + packet_num = 0; + timeout = now + 60; + } + break; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < client_config.retries) { + /* send request packet */ + if (state == RENEW_REQUESTED) + send_renew(xid, server_addr, requested_ip); /* unicast */ + else send_selecting(xid, server_addr, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 10 : 2); + packet_num++; + } else { + /* timed out, go back to init state */ + if (state == RENEW_REQUESTED) udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } + break; + case BOUND: + /* Lease is starting to run out, time to enter renewing state */ + state = RENEWING; + change_mode(LISTEN_KERNEL); + DEBUG("Entering renew state"); + /* fall right through */ + case RENEWING: + /* Either set a new T1, or enter REBINDING state */ + if ((t2 - t1) <= (lease / 14400 + 1)) { + /* timed out, enter rebinding state */ + state = REBINDING; + timeout = now + (t2 - t1); + DEBUG("Entering rebinding state"); + } else { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + + t1 = (t2 - t1) / 2 + t1; + timeout = t1 + start; + } + break; + case REBINDING: + /* Either set a new T2, or enter INIT state */ + if ((lease - t2) <= (lease / 14400 + 1)) { + /* timed out, enter init state */ + state = INIT_SELECTING; + bb_info_msg("Lease lost, entering init state"); + udhcp_run_script(NULL, "deconfig"); + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } else { + /* send a request packet */ + send_renew(xid, 0, requested_ip); /* broadcast */ + + t2 = (lease - t2) / 2 + t2; + timeout = t2 + start; + } + break; + case RELEASED: + /* yah, I know, *you* say it would never happen */ + timeout = 0x7fffffff; + break; + } + } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { + /* a packet is ready, read it */ + + if (listen_mode == LISTEN_KERNEL) + len = udhcp_get_packet(&packet, fd); + else len = get_raw_packet(&packet, fd); + + if (len == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + change_mode(listen_mode); /* just close and reopen */ + } + if (len < 0) continue; + + if (packet.xid != xid) { + DEBUG("Ignoring XID %lx (our xid is %lx)", + (unsigned long) packet.xid, xid); + continue; + } + + /* Ignore packets that aren't for us */ + if (memcmp(packet.chaddr, client_config.arp, 6)) { + DEBUG("Packet does not have our chaddr - ignoring"); + continue; + } + + if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet - ignoring"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + temp = get_option(&packet, DHCP_SERVER_ID); + if (temp) { + server_addr = *(uint32_t*)temp; + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = now; + packet_num = 0; + } else { + bb_error_msg("no server ID in message"); + } + } + break; + case RENEW_REQUESTED: + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message == DHCPACK) { + temp = get_option(&packet, DHCP_LEASE_TIME); + if (!temp) { + bb_error_msg("no lease time with ACK, using 1 hour lease"); + lease = 60 * 60; + } else { + lease = ntohl(*(uint32_t*)temp); + } + + /* enter bound state */ + t1 = lease / 2; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = packet.yiaddr; + bb_info_msg("Lease of %s obtained, lease time %ld", + inet_ntoa(temp_addr), lease); + start = now; + timeout = t1 + start; + requested_ip = packet.yiaddr; + udhcp_run_script(&packet, + ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); + + state = BOUND; + change_mode(LISTEN_NONE); + if (client_config.quit_after_lease) { + if (client_config.release_on_quit) + perform_release(); + return 0; + } + if (!client_config.foreground) + client_background(); + + } else if (*message == DHCPNAK) { + /* return to init state */ + bb_info_msg("Received DHCP NAK"); + udhcp_run_script(&packet, "nak"); + if (state != REQUESTING) + udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + requested_ip = 0; + packet_num = 0; + change_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + } + break; + /* case BOUND, RELEASED: - ignore all packets */ + } + } else if (retval > 0 && (sig = udhcp_sp_read(&rfds))) { + switch (sig) { + case SIGUSR1: + perform_renew(); + break; + case SIGUSR2: + perform_release(); + break; + case SIGTERM: + bb_info_msg("Received SIGTERM"); + if (client_config.release_on_quit) + perform_release(); + return 0; + } + } else if (retval == -1 && errno == EINTR) { + /* a signal was caught */ + } else { + /* An error occured */ + bb_perror_msg("select"); + } + + } + return 0; +} diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h new file mode 100644 index 0000000..fd17917 --- /dev/null +++ b/networking/udhcp/dhcpc.h @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.h */ +#ifndef _DHCPC_H +#define _DHCPC_H + +#define INIT_SELECTING 0 +#define REQUESTING 1 +#define BOUND 2 +#define RENEWING 3 +#define REBINDING 4 +#define INIT_REBOOT 5 +#define RENEW_REQUESTED 6 +#define RELEASED 7 + +struct client_config_t { + /* TODO: combine flag fields into single "unsigned opt" */ + /* (can be set directly to the result of getopt32) */ + char foreground; /* Do not fork */ + char quit_after_lease; /* Quit after obtaining lease */ + char release_on_quit; /* perform release on quit */ + char abort_if_no_lease; /* Abort if no lease */ + char background_if_no_lease; /* Fork to background if no lease */ + char *interface; /* The name of the interface to use */ + char *pidfile; /* Optionally store the process ID */ + char *script; /* User script to run at dhcp events */ + uint8_t *clientid; /* Optional client id to use */ + uint8_t *vendorclass; /* Optional vendor class-id to use */ + uint8_t *hostname; /* Optional hostname to use */ + uint8_t *fqdn; /* Optional fully qualified domain name to use */ + int ifindex; /* Index number of the interface to use */ + int retries; /* Max number of request packets */ + int timeout; /* Number of seconds to try to get a lease */ + uint8_t arp[6]; /* Our arp address */ +}; + +extern struct client_config_t client_config; + + +/*** clientpacket.h ***/ + +unsigned long random_xid(void); +int send_discover(unsigned long xid, unsigned long requested); +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_release(unsigned long server, unsigned long ciaddr); +int get_raw_packet(struct dhcpMessage *payload, int fd); + + +#endif diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c new file mode 100644 index 0000000..7438036 --- /dev/null +++ b/networking/udhcp/dhcpd.c @@ -0,0 +1,226 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.c + * + * udhcp Server + * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au> + * Chris Trew <ctrew@moreton.com.au> + * + * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* globals */ +struct dhcpOfferedAddr *leases; +struct server_config_t server_config; + + +int udhcpd_main(int argc, char *argv[]) +{ + fd_set rfds; + struct timeval tv; + int server_socket = -1, bytes, retval, max_sock; + struct dhcpMessage packet; + uint8_t *state, *server_id, *requested; + uint32_t server_id_align, requested_align, static_lease_ip; + unsigned long timeout_end, num_ips; + struct option_set *option; + struct dhcpOfferedAddr *lease, static_lease; + + read_config(argc < 2 ? DHCPD_CONF_FILE : argv[1]); + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(server_config.pidfile); + + if ((option = find_option(server_config.options, DHCP_LEASE_TIME))) { + memcpy(&server_config.lease, option->data + 2, 4); + server_config.lease = ntohl(server_config.lease); + } + else server_config.lease = LEASE_TIME; + + /* Sanity check */ + num_ips = ntohl(server_config.end) - ntohl(server_config.start) + 1; + if (server_config.max_leases > num_ips) { + bb_error_msg("max_leases value (%lu) not sane, " + "setting to %lu instead", + server_config.max_leases, num_ips); + server_config.max_leases = num_ips; + } + + leases = xzalloc(server_config.max_leases * sizeof(struct dhcpOfferedAddr)); + read_leases(server_config.lease_file); + + if (read_interface(server_config.interface, &server_config.ifindex, + &server_config.server, server_config.arp) < 0) + return 1; + + if (!ENABLE_FEATURE_UDHCP_DEBUG) + udhcp_background(server_config.pidfile); /* hold lock during fork. */ + + /* Setup the signal pipe */ + udhcp_sp_setup(); + + timeout_end = time(0) + server_config.auto_time; + while (1) { /* loop until universe collapses */ + + if (server_socket < 0) { + server_socket = listen_socket(INADDR_ANY, SERVER_PORT, server_config.interface); + if (server_socket < 0) { + bb_perror_msg("FATAL: cannot create server socket"); + return 2; + } + } + + max_sock = udhcp_sp_fd_set(&rfds, server_socket); + if (server_config.auto_time) { + tv.tv_sec = timeout_end - time(0); + tv.tv_usec = 0; + } + if (!server_config.auto_time || tv.tv_sec > 0) { + retval = select(max_sock + 1, &rfds, NULL, NULL, + server_config.auto_time ? &tv : NULL); + } else retval = 0; /* If we already timed out, fall through */ + + if (retval == 0) { + write_leases(); + timeout_end = time(0) + server_config.auto_time; + continue; + } else if (retval < 0 && errno != EINTR) { + DEBUG("error on select"); + continue; + } + + switch (udhcp_sp_read(&rfds)) { + case SIGUSR1: + bb_info_msg("Received a SIGUSR1"); + write_leases(); + /* why not just reset the timeout, eh */ + timeout_end = time(0) + server_config.auto_time; + continue; + case SIGTERM: + bb_info_msg("Received a SIGTERM"); + return 0; + case 0: break; /* no signal */ + default: continue; /* signal or error (probably EINTR) */ + } + + if ((bytes = udhcp_get_packet(&packet, server_socket)) < 0) { /* this waits for a packet - idle */ + if (bytes == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + close(server_socket); + server_socket = -1; + } + continue; + } + + if ((state = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet, ignoring"); + continue; + } + + /* Look for a static lease */ + static_lease_ip = getIpByMac(server_config.static_leases, &packet.chaddr); + + if (static_lease_ip) { + bb_info_msg("Found static lease: %x", static_lease_ip); + + memcpy(&static_lease.chaddr, &packet.chaddr, 16); + static_lease.yiaddr = static_lease_ip; + static_lease.expires = 0; + + lease = &static_lease; + + } else { + lease = find_lease_by_chaddr(packet.chaddr); + } + + switch (state[0]) { + case DHCPDISCOVER: + DEBUG("Received DISCOVER"); + + if (sendOffer(&packet) < 0) { + bb_error_msg("send OFFER failed"); + } + break; + case DHCPREQUEST: + DEBUG("received REQUEST"); + + requested = get_option(&packet, DHCP_REQUESTED_IP); + server_id = get_option(&packet, DHCP_SERVER_ID); + + if (requested) memcpy(&requested_align, requested, 4); + if (server_id) memcpy(&server_id_align, server_id, 4); + + if (lease) { + if (server_id) { + /* SELECTING State */ + DEBUG("server_id = %08x", ntohl(server_id_align)); + if (server_id_align == server_config.server && requested && + requested_align == lease->yiaddr) { + sendACK(&packet, lease->yiaddr); + } + } else { + if (requested) { + /* INIT-REBOOT State */ + if (lease->yiaddr == requested_align) + sendACK(&packet, lease->yiaddr); + else sendNAK(&packet); + } else { + /* RENEWING or REBINDING State */ + if (lease->yiaddr == packet.ciaddr) + sendACK(&packet, lease->yiaddr); + else { + /* don't know what to do!!!! */ + sendNAK(&packet); + } + } + } + + /* what to do if we have no record of the client */ + } else if (server_id) { + /* SELECTING State */ + + } else if (requested) { + /* INIT-REBOOT State */ + if ((lease = find_lease_by_yiaddr(requested_align))) { + if (lease_expired(lease)) { + /* probably best if we drop this lease */ + memset(lease->chaddr, 0, 16); + /* make some contention for this address */ + } else sendNAK(&packet); + } else if (requested_align < server_config.start || + requested_align > server_config.end) { + sendNAK(&packet); + } /* else remain silent */ + + } else { + /* RENEWING or REBINDING State */ + } + break; + case DHCPDECLINE: + DEBUG("Received DECLINE"); + if (lease) { + memset(lease->chaddr, 0, 16); + lease->expires = time(0) + server_config.decline_time; + } + break; + case DHCPRELEASE: + DEBUG("Received RELEASE"); + if (lease) lease->expires = time(0); + break; + case DHCPINFORM: + DEBUG("Received INFORM"); + send_inform(&packet); + break; + default: + bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]); + } + } + + return 0; +} diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h new file mode 100644 index 0000000..40959e4 --- /dev/null +++ b/networking/udhcp/dhcpd.h @@ -0,0 +1,190 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.h */ +#ifndef _DHCPD_H +#define _DHCPD_H + +/************************************/ +/* Defaults _you_ may want to tweak */ +/************************************/ + +/* the period of time the client is allowed to use that address */ +#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */ +#define LEASES_FILE "/var/lib/misc/udhcpd.leases" + +/* where to find the DHCP server configuration file */ +#define DHCPD_CONF_FILE "/etc/udhcpd.conf" + +/*****************************************************************/ +/* Do not modify below here unless you know what you are doing!! */ +/*****************************************************************/ + +/* DHCP protocol -- see RFC 2131 */ +#define SERVER_PORT 67 +#define CLIENT_PORT 68 + +#define DHCP_MAGIC 0x63825363 + +/* DHCP option codes (partial list) */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +#define DHCP_TIME_OFFSET 0x02 +#define DHCP_ROUTER 0x03 +#define DHCP_TIME_SERVER 0x04 +#define DHCP_NAME_SERVER 0x05 +#define DHCP_DNS_SERVER 0x06 +#define DHCP_LOG_SERVER 0x07 +#define DHCP_COOKIE_SERVER 0x08 +#define DHCP_LPR_SERVER 0x09 +#define DHCP_HOST_NAME 0x0c +#define DHCP_BOOT_SIZE 0x0d +#define DHCP_DOMAIN_NAME 0x0f +#define DHCP_SWAP_SERVER 0x10 +#define DHCP_ROOT_PATH 0x11 +#define DHCP_IP_TTL 0x17 +#define DHCP_MTU 0x1a +#define DHCP_BROADCAST 0x1c +#define DHCP_NTP_SERVER 0x2a +#define DHCP_WINS_SERVER 0x2c +#define DHCP_REQUESTED_IP 0x32 +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVER 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 +#define DHCP_PARAM_REQ 0x37 +#define DHCP_MESSAGE 0x38 +#define DHCP_MAX_SIZE 0x39 +#define DHCP_T1 0x3a +#define DHCP_T2 0x3b +#define DHCP_VENDOR 0x3c +#define DHCP_CLIENT_ID 0x3d +#define DHCP_FQDN 0x51 + +#define DHCP_END 0xFF + + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define ETH_10MB 1 +#define ETH_10MB_LEN 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +#define BROADCAST_FLAG 0x8000 + +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* miscellaneous defines */ +#define MAC_BCAST_ADDR (uint8_t *) "\xff\xff\xff\xff\xff\xff" +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 + +struct option_set { + uint8_t *data; + struct option_set *next; +}; + +struct static_lease { + uint8_t *mac; + uint32_t *ip; + struct static_lease *next; +}; + +struct server_config_t { + uint32_t server; /* Our IP, in network order */ + uint32_t start; /* Start address of leases, network order */ + uint32_t end; /* End of leases, network order */ + struct option_set *options; /* List of DHCP options loaded from the config file */ + char *interface; /* The name of the interface to use */ + int ifindex; /* Index number of the interface to use */ + uint8_t arp[6]; /* Our arp address */ + unsigned long lease; /* lease time in seconds (host order) */ + unsigned long max_leases; /* maximum number of leases (including reserved address) */ + char remaining; /* should the lease file be interpreted as lease time remaining, or + * as the time the lease expires */ + unsigned long auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + unsigned long decline_time; /* how long an address is reserved if a client returns a + * decline message */ + unsigned long conflict_time; /* how long an arp conflict offender is leased for */ + unsigned long offer_time; /* how long an offered address is reserved */ + unsigned long min_lease; /* minimum lease a client can request*/ + char *lease_file; + char *pidfile; + char *notify_file; /* What to run whenever leases are written */ + uint32_t siaddr; /* next server bootp option */ + char *sname; /* bootp server name */ + char *boot_file; /* bootp boot file option */ + struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */ +}; + +extern struct server_config_t server_config; +extern struct dhcpOfferedAddr *leases; + + +/*** leases.h ***/ + +struct dhcpOfferedAddr { + uint8_t chaddr[16]; + uint32_t yiaddr; /* network order */ + uint32_t expires; /* host order */ +}; + +extern uint8_t blank_chaddr[]; + +void clear_lease(uint8_t *chaddr, uint32_t yiaddr); +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease); +int lease_expired(struct dhcpOfferedAddr *lease); +struct dhcpOfferedAddr *oldest_expired_lease(void); +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr); +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr); +uint32_t find_address(int check_expired); + + +/*** static_leases.h ***/ + +/* Config file will pass static lease info to this function which will add it + * to a data structure that can be searched later */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip); +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg); +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip); +/* Print out static leases just to check what's going on (debug code) */ +void printStaticLeases(struct static_lease **lease_struct); + + +/*** serverpacket.h ***/ + +int sendOffer(struct dhcpMessage *oldpacket); +int sendNAK(struct dhcpMessage *oldpacket); +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr); +int send_inform(struct dhcpMessage *oldpacket); + + +/*** files.h ***/ + +struct config_keyword { + const char *keyword; + int (* const handler)(const char *line, void *var); + void *var; + const char *def; +}; + +int read_config(const char *file); +void write_leases(void); +void read_leases(const char *file); +struct option_set *find_option(struct option_set *opt_list, char code); + + +#endif diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c new file mode 100644 index 0000000..e3a8168 --- /dev/null +++ b/networking/udhcp/dhcprelay.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* Port to Busybox Copyright (C) 2006 Jesse Dutton <jessedutton@gmail.com> + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + * + * DHCP Relay for 'DHCPv4 Configuration of IPSec Tunnel Mode' support + * Copyright (C) 2002 Mario Strasser <mast@gmx.net>, + * Zuercher Hochschule Winterthur, + * Netbeat AG + * Upstream has GPL v2 or later + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + +/* constants */ +#define SELECT_TIMEOUT 5 /* select timeout in sec. */ +#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ +#define MAX_INTERFACES 9 + + +/* This list holds information about clients. The xid_* functions manipulate this list. */ +static struct xid_item { + u_int32_t xid; + struct sockaddr_in ip; + int client; + time_t timestamp; + struct xid_item *next; +} dhcprelay_xid_list = {0, {0}, 0, 0, NULL}; + + +static struct xid_item * xid_add(u_int32_t xid, struct sockaddr_in *ip, int client) +{ + struct xid_item *item; + + /* create new xid entry */ + item = xmalloc(sizeof(struct xid_item)); + + /* add xid entry */ + item->ip = *ip; + item->xid = xid; + item->client = client; + item->timestamp = time(NULL); + item->next = dhcprelay_xid_list.next; + dhcprelay_xid_list.next = item; + + return item; +} + + +static void xid_expire(void) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + time_t current_time = time(NULL); + + while (item != NULL) { + if ((current_time-item->timestamp) > MAX_LIFETIME) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + +static struct xid_item * xid_find(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + while (item != NULL) { + if (item->xid == xid) { + return item; + } + item = item->next; + } + return NULL; +} + +static void xid_del(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + while (item != NULL) { + if (item->xid == xid) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + + +/** + * get_dhcp_packet_type - gets the message type of a dhcp packet + * p - pointer to the dhcp packet + * returns the message type on success, -1 otherwise + */ +static int get_dhcp_packet_type(struct dhcpMessage *p) +{ + u_char *op; + + /* it must be either a BOOTREQUEST or a BOOTREPLY */ + if (p->op != BOOTREQUEST && p->op != BOOTREPLY) + return -1; + /* get message type option */ + op = get_option(p, DHCP_MESSAGE_TYPE); + if (op != NULL) + return op[0]; + return -1; +} + +/** + * signal_handler - handles signals ;-) + * sig - sent signal + */ +static int dhcprelay_stopflag; +static void dhcprelay_signal_handler(int sig) +{ + dhcprelay_stopflag = 1; +} + +/** + * get_client_devices - parses the devices list + * dev_list - comma separated list of devices + * returns array + */ +static char ** get_client_devices(char *dev_list, int *client_number) +{ + char *s, *list, **client_dev; + int i, cn; + + /* copy list */ + list = xstrdup(dev_list); + if (list == NULL) return NULL; + + /* get number of items */ + for (s = dev_list, cn = 1; *s; s++) + if (*s == ',') + cn++; + + client_dev = xzalloc(cn * sizeof(*client_dev)); + + /* parse list */ + s = strtok(list, ","); + i = 0; + while (s != NULL) { + client_dev[i++] = xstrdup(s); + s = strtok(NULL, ","); + } + + /* free copy and exit */ + free(list); + *client_number = cn; + return client_dev; +} + + +/* Creates listen sockets (in fds) and returns the number allocated. */ +static int init_sockets(char **client, int num_clients, + char *server, int *fds, int *max_socket) +{ + int i; + + /* talk to real server on bootps */ + fds[0] = listen_socket(htonl(INADDR_ANY), 67, server); + if (fds[0] < 0) return -1; + *max_socket = fds[0]; + + /* array starts at 1 since server is 0 */ + num_clients++; + + for (i=1; i < num_clients; i++) { + /* listen for clients on bootps */ + fds[i] = listen_socket(htonl(INADDR_ANY), 67, client[i-1]); + if (fds[i] < 0) return -1; + if (fds[i] > *max_socket) *max_socket = fds[i]; + } + + return i; +} + + +/** + * pass_on() - forwards dhcp packets from client to server + * p - packet to send + * client - number of the client + */ +static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds, + struct sockaddr_in *client_addr, struct sockaddr_in *server_addr) +{ + int res, type; + struct xid_item *item; + + /* check packet_type */ + type = get_dhcp_packet_type(p); + if (type != DHCPDISCOVER && type != DHCPREQUEST + && type != DHCPDECLINE && type != DHCPRELEASE + && type != DHCPINFORM + ) { + return; + } + + /* create new xid entry */ + item = xid_add(p->xid, client_addr, client); + + /* forward request to LAN (server) */ + res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr, + sizeof(struct sockaddr_in)); + if (res != packet_len) { + bb_perror_msg("pass_on"); + return; + } +} + +/** + * pass_back() - forwards dhcp packets from server to client + * p - packet to send + */ +static void pass_back(struct dhcpMessage *p, int packet_len, int *fds) +{ + int res, type; + struct xid_item *item; + + /* check xid */ + item = xid_find(p->xid); + if (!item) { + return; + } + + /* check packet type */ + type = get_dhcp_packet_type(p); + if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) { + return; + } + + if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY)) + item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST); + if (item->client > MAX_INTERFACES) + return; + res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip), + sizeof(item->ip)); + if (res != packet_len) { + bb_perror_msg("pass_back"); + return; + } + + /* remove xid entry */ + xid_del(p->xid); +} + +static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients, + struct sockaddr_in *server_addr, uint32_t gw_ip) +{ + struct dhcpMessage dhcp_msg; + fd_set rfds; + size_t packlen, addr_size; + struct sockaddr_in client_addr; + struct timeval tv; + int i; + + while (!dhcprelay_stopflag) { + FD_ZERO(&rfds); + for (i = 0; i < num_sockets; i++) + FD_SET(fds[i], &rfds); + tv.tv_sec = SELECT_TIMEOUT; + tv.tv_usec = 0; + if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) { + /* server */ + if (FD_ISSET(fds[0], &rfds)) { + packlen = udhcp_get_packet(&dhcp_msg, fds[0]); + if (packlen > 0) { + pass_back(&dhcp_msg, packlen, fds); + } + } + for (i = 1; i < num_sockets; i++) { + /* clients */ + if (!FD_ISSET(fds[i], &rfds)) + continue; + addr_size = sizeof(struct sockaddr_in); + packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0, + (struct sockaddr *)(&client_addr), &addr_size); + if (packlen <= 0) + continue; + if (read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL) < 0) + dhcp_msg.giaddr = gw_ip; + pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr); + } + } + xid_expire(); + } +} + +int dhcprelay_main(int argc, char **argv) +{ + int i, num_sockets, max_socket, fds[MAX_INTERFACES]; + uint32_t gw_ip; + char **clients; + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(67); + if (argc == 4) { + if (!inet_aton(argv[3], &server_addr.sin_addr)) + bb_perror_msg_and_die("didn't grok server"); + } else if (argc == 3) { + server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + } else { + bb_show_usage(); + } + clients = get_client_devices(argv[1], &num_sockets); + if (!clients) return 0; + + signal(SIGTERM, dhcprelay_signal_handler); + signal(SIGQUIT, dhcprelay_signal_handler); + signal(SIGINT, dhcprelay_signal_handler); + + num_sockets = init_sockets(clients, num_sockets, argv[2], fds, &max_socket); + if (num_sockets == -1) + bb_perror_msg_and_die("init_sockets() failed"); + + if (read_interface(argv[2], NULL, &gw_ip, NULL) == -1) + return 1; + + dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip); + + if (ENABLE_FEATURE_CLEAN_UP) { + for (i = 0; i < num_sockets; i++) { + close(fds[i]); + free(clients[i]); + } + } + + return 0; +} diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c new file mode 100644 index 0000000..a0e81bb --- /dev/null +++ b/networking/udhcp/dumpleases.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#include <getopt.h> + +#include "common.h" +#include "dhcpd.h" + + +#define REMAINING 0 +#define ABSOLUTE 1 + +int dumpleases_main(int argc, char *argv[]) +{ + int fp; + int i, c, mode = REMAINING; + unsigned long expires; + const char *file = LEASES_FILE; + struct dhcpOfferedAddr lease; + struct in_addr addr; + + static const struct option options[] = { + {"absolute", 0, 0, 'a'}, + {"remaining", 0, 0, 'r'}, + {"file", 1, 0, 'f'}, + {0, 0, 0, 0} + }; + + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "arf:", options, &option_index); + if (c == -1) break; + + switch (c) { + case 'a': mode = ABSOLUTE; break; + case 'r': mode = REMAINING; break; + case 'f': + file = optarg; + break; + default: + bb_show_usage(); + } + } + + fp = xopen(file, O_RDONLY); + + printf("Mac Address IP-Address Expires %s\n", mode == REMAINING ? "in" : "at"); + /* "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */ + while (full_read(fp, &lease, sizeof(lease)) == sizeof(lease)) { + printf(":%02x"+1, lease.chaddr[0]); + for (i = 1; i < 6; i++) { + printf(":%02x", lease.chaddr[i]); + } + addr.s_addr = lease.yiaddr; + printf(" %-15s ", inet_ntoa(addr)); + expires = ntohl(lease.expires); + if (mode == REMAINING) { + if (!expires) + printf("expired\n"); + else { + unsigned d, h, m; + d = expires / (24*60*60); expires %= (24*60*60); + h = expires / (60*60); expires %= (60*60); + m = expires / 60; expires %= 60; + if (d) printf("%u days ", d); + printf("%02u:%02u:%02u\n", h, m, (unsigned)expires); + } + } else fputs(ctime(&expires), stdout); + } + /* close(fp); */ + + return 0; +} diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c new file mode 100644 index 0000000..5e399e1 --- /dev/null +++ b/networking/udhcp/files.c @@ -0,0 +1,395 @@ +/* vi: set sw=4 ts=4: */ +/* + * files.c -- DHCP server file manipulation * + * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001 + */ + +#include <netinet/ether.h> + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* + * Domain names may have 254 chars, and string options can be 254 + * chars long. However, 80 bytes will be enough for most, and won't + * hog up memory. If you have a special application, change it + */ +#define READ_CONFIG_BUF_SIZE 80 + +/* on these functions, make sure you datatype matches */ +static int read_ip(const char *line, void *arg) +{ + struct in_addr *addr = arg; + struct hostent *host; + int retval = 1; + + if (!inet_aton(line, addr)) { + host = gethostbyname(line); + if (host) + addr->s_addr = *((unsigned long *) host->h_addr_list[0]); + else retval = 0; + } + return retval; +} + +static int read_mac(const char *line, void *arg) +{ + uint8_t *mac_bytes = arg; + struct ether_addr *temp_ether_addr; + int retval = 1; + + temp_ether_addr = ether_aton(line); + + if (temp_ether_addr == NULL) + retval = 0; + else + memcpy(mac_bytes, temp_ether_addr, 6); + + return retval; +} + + +static int read_str(const char *line, void *arg) +{ + char **dest = arg; + + free(*dest); + *dest = strdup(line); + + return 1; +} + + +static int read_u32(const char *line, void *arg) +{ + *((uint32_t*)arg) = bb_strtou32(line, NULL, 10); + return errno == 0; +} + + +static int read_yn(const char *line, void *arg) +{ + char *dest = arg; + int retval = 1; + + if (!strcasecmp("yes", line)) + *dest = 1; + else if (!strcasecmp("no", line)) + *dest = 0; + else retval = 0; + + return retval; +} + + +/* find option 'code' in opt_list */ +struct option_set *find_option(struct option_set *opt_list, char code) +{ + while (opt_list && opt_list->data[OPT_CODE] < code) + opt_list = opt_list->next; + + if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list; + else return NULL; +} + + +/* add an option to the opt_list */ +static void attach_option(struct option_set **opt_list, + const struct dhcp_option *option, char *buffer, int length) +{ + struct option_set *existing, *new, **curr; + + /* add it to an existing option */ + existing = find_option(*opt_list, option->code); + if (existing) { + DEBUG("Attaching option %s to existing member of list", option->name); + if (option->flags & OPTION_LIST) { + if (existing->data[OPT_LEN] + length <= 255) { + existing->data = realloc(existing->data, + existing->data[OPT_LEN] + length + 2); + memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length); + existing->data[OPT_LEN] += length; + } /* else, ignore the data, we could put this in a second option in the future */ + } /* else, ignore the new data */ + } else { + DEBUG("Attaching option %s to list", option->name); + + /* make a new option */ + new = xmalloc(sizeof(struct option_set)); + new->data = xmalloc(length + 2); + new->data[OPT_CODE] = option->code; + new->data[OPT_LEN] = length; + memcpy(new->data + 2, buffer, length); + + curr = opt_list; + while (*curr && (*curr)->data[OPT_CODE] < option->code) + curr = &(*curr)->next; + + new->next = *curr; + *curr = new; + } +} + + +/* read a dhcp option and add it to opt_list */ +static int read_opt(const char *const_line, void *arg) +{ + struct option_set **opt_list = arg; + char *opt, *val, *endptr; + const struct dhcp_option *option; + int retval = 0, length; + char buffer[8]; + char *line; + uint16_t *result_u16 = (uint16_t *) buffer; + uint32_t *result_u32 = (uint32_t *) buffer; + + /* Cheat, the only const line we'll actually get is "" */ + line = (char *) const_line; + opt = strtok(line, " \t="); + if (!opt) return 0; + + option = dhcp_options; + while (1) { + if (!option->code) + return 0; + if (!strcasecmp(option->name, opt)) + break; + option++; + } + + do { + val = strtok(NULL, ", \t"); + if (!val) break; + length = option_lengths[option->flags & TYPE_MASK]; + retval = 0; + opt = buffer; /* new meaning for variable opt */ + switch (option->flags & TYPE_MASK) { + case OPTION_IP: + retval = read_ip(val, buffer); + break; + case OPTION_IP_PAIR: + retval = read_ip(val, buffer); + if (!(val = strtok(NULL, ", \t/-"))) retval = 0; + if (retval) retval = read_ip(val, buffer + 4); + break; + case OPTION_STRING: + length = strlen(val); + if (length > 0) { + if (length > 254) length = 254; + opt = val; + retval = 1; + } + break; + case OPTION_BOOLEAN: + retval = read_yn(val, buffer); + break; + case OPTION_U8: + buffer[0] = strtoul(val, &endptr, 0); + retval = (endptr[0] == '\0'); + break; + case OPTION_U16: + *result_u16 = htons(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S16: + *result_u16 = htons(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_U32: + *result_u32 = htonl(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S32: + *result_u32 = htonl(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + default: + break; + } + if (retval) + attach_option(opt_list, option, opt, length); + } while (retval && option->flags & OPTION_LIST); + return retval; +} + +static int read_staticlease(const char *const_line, void *arg) +{ + char *line; + char *mac_string; + char *ip_string; + uint8_t *mac_bytes; + uint32_t *ip; + + + /* Allocate memory for addresses */ + mac_bytes = xmalloc(sizeof(unsigned char) * 8); + ip = xmalloc(sizeof(uint32_t)); + + /* Read mac */ + line = (char *) const_line; + mac_string = strtok(line, " \t"); + read_mac(mac_string, mac_bytes); + + /* Read ip */ + ip_string = strtok(NULL, " \t"); + read_ip(ip_string, ip); + + addStaticLease(arg, mac_bytes, ip); + + if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg); + + return 1; +} + + +static const struct config_keyword keywords[] = { + /* keyword handler variable address default */ + {"start", read_ip, &(server_config.start), "192.168.0.20"}, + {"end", read_ip, &(server_config.end), "192.168.0.254"}, + {"interface", read_str, &(server_config.interface), "eth0"}, + {"option", read_opt, &(server_config.options), ""}, + {"opt", read_opt, &(server_config.options), ""}, + {"max_leases", read_u32, &(server_config.max_leases), "254"}, + {"remaining", read_yn, &(server_config.remaining), "yes"}, + {"auto_time", read_u32, &(server_config.auto_time), "7200"}, + {"decline_time",read_u32, &(server_config.decline_time),"3600"}, + {"conflict_time",read_u32,&(server_config.conflict_time),"3600"}, + {"offer_time", read_u32, &(server_config.offer_time), "60"}, + {"min_lease", read_u32, &(server_config.min_lease), "60"}, + {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE}, + {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"}, + {"notify_file", read_str, &(server_config.notify_file), ""}, + {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"}, + {"sname", read_str, &(server_config.sname), ""}, + {"boot_file", read_str, &(server_config.boot_file), ""}, + {"static_lease",read_staticlease, &(server_config.static_leases), ""}, + /*ADDME: static lease */ + {"", NULL, NULL, ""} +}; + + +int read_config(const char *file) +{ + FILE *in; + char buffer[READ_CONFIG_BUF_SIZE], *token, *line; + int i, lm = 0; + + for (i = 0; keywords[i].keyword[0]; i++) + if (keywords[i].def[0]) + keywords[i].handler(keywords[i].def, keywords[i].var); + + in = fopen(file, "r"); + if (!in) { + bb_error_msg("cannot open config file: %s", file); + return 0; + } + + while (fgets(buffer, READ_CONFIG_BUF_SIZE, in)) { + char debug_orig[READ_CONFIG_BUF_SIZE]; + char *p; + + lm++; + p = strchr(buffer, '\n'); + if (p) *p = '\0'; + if (ENABLE_FEATURE_UDHCP_DEBUG) strcpy(debug_orig, buffer); + p = strchr(buffer, '#'); + if (p) *p = '\0'; + + if (!(token = strtok(buffer, " \t"))) continue; + if (!(line = strtok(NULL, ""))) continue; + + /* eat leading whitespace */ + line = skip_whitespace(line); + /* eat trailing whitespace */ + i = strlen(line) - 1; + while (i >= 0 && isspace(line[i])) + line[i--] = '\0'; + + for (i = 0; keywords[i].keyword[0]; i++) + if (!strcasecmp(token, keywords[i].keyword)) + if (!keywords[i].handler(line, keywords[i].var)) { + bb_error_msg("cannot parse line %d of %s", lm, file); + if (ENABLE_FEATURE_UDHCP_DEBUG) + bb_error_msg("cannot parse '%s'", debug_orig); + /* reset back to the default value */ + keywords[i].handler(keywords[i].def, keywords[i].var); + } + } + fclose(in); + return 1; +} + + +void write_leases(void) +{ + int fp; + unsigned i; + time_t curr = time(0); + unsigned long tmp_time; + + fp = open(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fp < 0) { + bb_error_msg("cannot open %s for writing", server_config.lease_file); + return; + } + + for (i = 0; i < server_config.max_leases; i++) { + if (leases[i].yiaddr != 0) { + + /* screw with the time in the struct, for easier writing */ + tmp_time = leases[i].expires; + + if (server_config.remaining) { + if (lease_expired(&(leases[i]))) + leases[i].expires = 0; + else leases[i].expires -= curr; + } /* else stick with the time we got */ + leases[i].expires = htonl(leases[i].expires); + // FIXME: error check?? + full_write(fp, &leases[i], sizeof(leases[i])); + + /* then restore it when done */ + leases[i].expires = tmp_time; + } + } + close(fp); + + if (server_config.notify_file) { + char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file); + system(cmd); + free(cmd); + } +} + + +void read_leases(const char *file) +{ + int fp; + unsigned int i = 0; + struct dhcpOfferedAddr lease; + + fp = open(file, O_RDONLY); + if (fp < 0) { + bb_error_msg("cannot open %s for reading", file); + return; + } + + while (i < server_config.max_leases + && full_read(fp, &lease, sizeof(lease)) == sizeof(lease) + ) { + /* ADDME: is it a static lease */ + if (lease.yiaddr >= server_config.start && lease.yiaddr <= server_config.end) { + lease.expires = ntohl(lease.expires); + if (!server_config.remaining) lease.expires -= time(0); + if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) { + bb_error_msg("too many leases while loading %s", file); + break; + } + i++; + } + } + DEBUG("Read %d leases", i); + close(fp); +} diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c new file mode 100644 index 0000000..2f7847d --- /dev/null +++ b/networking/udhcp/leases.c @@ -0,0 +1,145 @@ +/* vi: set sw=4 ts=4: */ +/* + * leases.c -- tools to manage DHCP leases + * Russ Dill <Russ.Dill@asu.edu> July 2001 + */ + +#include "common.h" +#include "dhcpd.h" + + +uint8_t blank_chaddr[] = {[0 ... 15] = 0}; + +/* clear every lease out that chaddr OR yiaddr matches and is nonzero */ +void clear_lease(uint8_t *chaddr, uint32_t yiaddr) +{ + unsigned int i, j; + + for (j = 0; j < 16 && !chaddr[j]; j++); + + for (i = 0; i < server_config.max_leases; i++) + if ((j != 16 && !memcmp(leases[i].chaddr, chaddr, 16)) || + (yiaddr && leases[i].yiaddr == yiaddr)) { + memset(&(leases[i]), 0, sizeof(struct dhcpOfferedAddr)); + } +} + + +/* add a lease into the table, clearing out any old ones */ +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease) +{ + struct dhcpOfferedAddr *oldest; + + /* clean out any old ones */ + clear_lease(chaddr, yiaddr); + + oldest = oldest_expired_lease(); + + if (oldest) { + memcpy(oldest->chaddr, chaddr, 16); + oldest->yiaddr = yiaddr; + oldest->expires = time(0) + lease; + } + + return oldest; +} + + +/* true if a lease has expired */ +int lease_expired(struct dhcpOfferedAddr *lease) +{ + return (lease->expires < (unsigned long) time(0)); +} + + +/* Find the oldest expired lease, NULL if there are no expired leases */ +struct dhcpOfferedAddr *oldest_expired_lease(void) +{ + struct dhcpOfferedAddr *oldest = NULL; + unsigned long oldest_lease = time(0); + unsigned int i; + + + for (i = 0; i < server_config.max_leases; i++) + if (oldest_lease > leases[i].expires) { + oldest_lease = leases[i].expires; + oldest = &(leases[i]); + } + return oldest; + +} + + +/* Find the first lease that matches chaddr, NULL if no match */ +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (!memcmp(leases[i].chaddr, chaddr, 16)) return &(leases[i]); + + return NULL; +} + + +/* Find the first lease that matches yiaddr, NULL is no match */ +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (leases[i].yiaddr == yiaddr) return &(leases[i]); + + return NULL; +} + + +/* check is an IP is taken, if it is, add it to the lease table */ +static int check_ip(uint32_t addr) +{ + struct in_addr temp; + + if (arpping(addr, server_config.server, server_config.arp, server_config.interface) == 0) { + temp.s_addr = addr; + bb_info_msg("%s belongs to someone, reserving it for %ld seconds", + inet_ntoa(temp), server_config.conflict_time); + add_lease(blank_chaddr, addr, server_config.conflict_time); + return 1; + } else return 0; +} + + +/* find an assignable address, it check_expired is true, we check all the expired leases as well. + * Maybe this should try expired leases by age... */ +uint32_t find_address(int check_expired) +{ + uint32_t addr, ret; + struct dhcpOfferedAddr *lease = NULL; + + addr = ntohl(server_config.start); /* addr is in host order here */ + for (;addr <= ntohl(server_config.end); addr++) { + + /* ie, 192.168.55.0 */ + if (!(addr & 0xFF)) continue; + + /* ie, 192.168.55.255 */ + if ((addr & 0xFF) == 0xFF) continue; + + /* Only do if it isn't an assigned as a static lease */ + if (!reservedIp(server_config.static_leases, htonl(addr))) { + + /* lease is not taken */ + ret = htonl(addr); + lease = find_lease_by_yiaddr(ret); + + /* no lease or it expired and we are checking for expired leases */ + if ( (!lease || (check_expired && lease_expired(lease))) + && /* and it isn't on the network */ !check_ip(ret) + ) { + return ret; + break; + } + } + } + return 0; +} diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c new file mode 100644 index 0000000..4a46da5 --- /dev/null +++ b/networking/udhcp/options.c @@ -0,0 +1,174 @@ +/* vi: set sw=4 ts=4: */ +/* + * options.c -- DHCP server option packet tools + * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001 + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* supported options are easily added here */ +const struct dhcp_option dhcp_options[] = { + /* name[10] flags code */ + {"subnet", OPTION_IP | OPTION_REQ, 0x01}, + {"timezone", OPTION_S32, 0x02}, + {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, + {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, + {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, + {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, + {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, + {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, + {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, + {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, + {"bootsize", OPTION_U16, 0x0d}, + {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, + {"swapsvr", OPTION_IP, 0x10}, + {"rootpath", OPTION_STRING, 0x11}, + {"ipttl", OPTION_U8, 0x17}, + {"mtu", OPTION_U16, 0x1a}, + {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, + {"nisdomain", OPTION_STRING | OPTION_REQ, 0x28}, + {"nissrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x29}, + {"ntpsrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a}, + {"wins", OPTION_IP | OPTION_LIST, 0x2c}, + {"requestip", OPTION_IP, 0x32}, + {"lease", OPTION_U32, 0x33}, + {"dhcptype", OPTION_U8, 0x35}, + {"serverid", OPTION_IP, 0x36}, + {"message", OPTION_STRING, 0x38}, + {"vendorclass", OPTION_STRING, 0x3C}, + {"clientid", OPTION_STRING, 0x3D}, + {"tftp", OPTION_STRING, 0x42}, + {"bootfile", OPTION_STRING, 0x43}, + {"userclass", OPTION_STRING, 0x4D}, + /* MSIE's "Web Proxy Autodiscovery Protocol" support */ + {"wpad", OPTION_STRING, 0xfc}, + {"", 0x00, 0x00} +}; + +/* Lengths of the different option types */ +const unsigned char option_lengths[] = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, + [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, + [OPTION_U8] = 1, + [OPTION_U16] = 2, + [OPTION_S16] = 2, + [OPTION_U32] = 4, + [OPTION_S32] = 4 +}; + + +/* get an option with bounds checking (warning, not aligned). */ +uint8_t *get_option(struct dhcpMessage *packet, int code) +{ + int i, length; + uint8_t *optionptr; + int over = 0, done = 0, curr = OPTION_FIELD; + + optionptr = packet->options; + i = 0; + length = 308; + while (!done) { + if (i >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + if (optionptr[i + OPT_CODE] == code) { + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + return optionptr + i + 2; + } + switch (optionptr[i + OPT_CODE]) { + case DHCP_PADDING: + i++; + break; + case DHCP_OPTION_OVER: + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + over = optionptr[i + 3]; + i += optionptr[OPT_LEN] + 2; + break; + case DHCP_END: + if (curr == OPTION_FIELD && over & FILE_FIELD) { + optionptr = packet->file; + i = 0; + length = 128; + curr = FILE_FIELD; + } else if (curr == FILE_FIELD && over & SNAME_FIELD) { + optionptr = packet->sname; + i = 0; + length = 64; + curr = SNAME_FIELD; + } else done = 1; + break; + default: + i += optionptr[OPT_LEN + i] + 2; + } + } + return NULL; +} + + +/* return the position of the 'end' option (no bounds checking) */ +int end_option(uint8_t *optionptr) +{ + int i = 0; + + while (optionptr[i] != DHCP_END) { + if (optionptr[i] == DHCP_PADDING) i++; + else i += optionptr[i + OPT_LEN] + 2; + } + return i; +} + + +/* add an option string to the options (an option string contains an option code, + * length, then data) */ +int add_option_string(uint8_t *optionptr, uint8_t *string) +{ + int end = end_option(optionptr); + + /* end position + string length + option code/length + end option */ + if (end + string[OPT_LEN] + 2 + 1 >= 308) { + bb_error_msg("option 0x%02x did not fit into the packet", + string[OPT_CODE]); + return 0; + } + DEBUG("adding option 0x%02x", string[OPT_CODE]); + memcpy(optionptr + end, string, string[OPT_LEN] + 2); + optionptr[end + string[OPT_LEN] + 2] = DHCP_END; + return string[OPT_LEN] + 2; +} + + +/* add a one to four byte option to a packet */ +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) +{ + const struct dhcp_option *dh; + + for (dh = dhcp_options; dh->code; dh++) { + if (dh->code == code) { + uint8_t option[6], len; + + option[OPT_CODE] = code; + len = option_lengths[dh->flags & TYPE_MASK]; + option[OPT_LEN] = len; + if (BB_BIG_ENDIAN) data <<= 8 * (4 - len); + /* This memcpy is for broken processors which can't + * handle a simple unaligned 32-bit assignment */ + memcpy(&option[OPT_DATA], &data, 4); + return add_option_string(optionptr, option); + } + } + + bb_error_msg("cannot add option 0x%02x", code); + return 0; +} diff --git a/networking/udhcp/options.h b/networking/udhcp/options.h new file mode 100644 index 0000000..588504e --- /dev/null +++ b/networking/udhcp/options.h @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* options.h */ +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#define TYPE_MASK 0x0F + +enum { + OPTION_IP=1, + OPTION_IP_PAIR, + OPTION_STRING, + OPTION_BOOLEAN, + OPTION_U8, + OPTION_U16, + OPTION_S16, + OPTION_U32, + OPTION_S32 +}; + +#define OPTION_REQ 0x10 /* have the client request this option */ +#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ + +struct dhcp_option { + char name[12]; + char flags; + uint8_t code; +}; + +extern const struct dhcp_option dhcp_options[]; +extern const unsigned char option_lengths[]; + +uint8_t *get_option(struct dhcpMessage *packet, int code); +int end_option(uint8_t *optionptr); +int add_option_string(uint8_t *optionptr, uint8_t *string); +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data); + +#endif diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c new file mode 100644 index 0000000..dec9d0a --- /dev/null +++ b/networking/udhcp/packet.c @@ -0,0 +1,211 @@ +/* vi: set sw=4 ts=4: */ + +#include <netinet/in.h> +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <asm/types.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#endif + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +void udhcp_init_header(struct dhcpMessage *packet, char type) +{ + memset(packet, 0, sizeof(struct dhcpMessage)); + switch (type) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPRELEASE: + case DHCPINFORM: + packet->op = BOOTREQUEST; + break; + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; + } + packet->htype = ETH_10MB; + packet->hlen = ETH_10MB_LEN; + packet->cookie = htonl(DHCP_MAGIC); + packet->options[0] = DHCP_END; + add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); +} + + +/* read a packet from socket fd, return -1 on read error, -2 on packet error */ +int udhcp_get_packet(struct dhcpMessage *packet, int fd) +{ + static const char broken_vendors[][8] = { + "MSFT 98", + "" + }; + int bytes; + int i; + char unsigned *vendor; + + memset(packet, 0, sizeof(struct dhcpMessage)); + bytes = read(fd, packet, sizeof(struct dhcpMessage)); + if (bytes < 0) { + DEBUG("cannot read on listening socket, ignoring"); + return -1; + } + + if (ntohl(packet->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message, ignoring"); + return -2; + } + DEBUG("Received a packet"); + + if (packet->op == BOOTREQUEST && (vendor = get_option(packet, DHCP_VENDOR))) { + for (i = 0; broken_vendors[i][0]; i++) { + if (vendor[OPT_LEN - 2] == (uint8_t)strlen(broken_vendors[i]) + && !strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - 2]) + ) { + DEBUG("broken client (%s), forcing broadcast", + broken_vendors[i]); + packet->flags |= htons(BROADCAST_FLAG); + } + } + } + + return bytes; +} + + +uint16_t udhcp_checksum(void *addr, int count) +{ + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + int32_t sum = 0; + uint16_t *source = (uint16_t *) addr; + + while (count > 1) { + /* This is the inner loop */ + sum += *source++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) { + /* Make sure that the left-over byte is added correctly both + * with little and big endian hosts */ + uint16_t tmp = 0; + *(uint8_t *) (&tmp) = * (uint8_t *) source; + sum += tmp; + } + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + + +/* Construct a ip/udp header for a packet, and specify the source and dest hardware address */ +void BUG_sizeof_struct_udp_dhcp_packet_must_be_576(void); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex) +{ + int fd; + int result; + struct sockaddr_ll dest; + struct udp_dhcp_packet packet; + + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_IP); + dest.sll_ifindex = ifindex; + dest.sll_halen = 6; + memcpy(dest.sll_addr, dest_arp, 6); + if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_ip; + packet.ip.daddr = dest_ip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); /* cheat on the psuedo-header */ + packet.ip.tot_len = packet.udp.len; + memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); + packet.udp.check = udhcp_checksum(&packet, sizeof(struct udp_dhcp_packet)); + + packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = udhcp_checksum(&(packet.ip), sizeof(packet.ip)); + + if (sizeof(struct udp_dhcp_packet) != 576) + BUG_sizeof_struct_udp_dhcp_packet_must_be_576(); + + result = sendto(fd, &packet, sizeof(struct udp_dhcp_packet), 0, + (struct sockaddr *) &dest, sizeof(dest)); + if (result <= 0) { + bb_perror_msg("sendto"); + } + close(fd); + return result; +} + + +/* Let the kernel do all the work for packet generation */ +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) +{ + int fd, result; + struct sockaddr_in client; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(source_port); + client.sin_addr.s_addr = source_ip; + + if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(dest_port); + client.sin_addr.s_addr = dest_ip; + + if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + result = write(fd, payload, sizeof(struct dhcpMessage)); + close(fd); + return result; +} diff --git a/networking/udhcp/pidfile.c b/networking/udhcp/pidfile.c new file mode 100644 index 0000000..bcb2608 --- /dev/null +++ b/networking/udhcp/pidfile.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* pidfile.c + * + * Functions to assist in the writing and removing of pidfiles. + * + * Russ Dill <Russ.Dill@asu.edu> September 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" + + +static const char *saved_pidfile; + +static void pidfile_delete(void) +{ + if (saved_pidfile) unlink(saved_pidfile); +} + + +int pidfile_acquire(const char *pidfile) +{ + int pid_fd; + if (!pidfile) return -1; + + pid_fd = open(pidfile, O_CREAT|O_WRONLY|O_TRUNC, 0644); + if (pid_fd < 0) { + bb_perror_msg("cannot open pidfile %s", pidfile); + } else { + lockf(pid_fd, F_LOCK, 0); + if (!saved_pidfile) + atexit(pidfile_delete); + saved_pidfile = pidfile; + } + + return pid_fd; +} + + +void pidfile_write_release(int pid_fd) +{ + FILE *out; + + if (pid_fd < 0) return; + + out = fdopen(pid_fd, "w"); + if (out) { + fprintf(out, "%d\n", getpid()); + fclose(out); + } + lockf(pid_fd, F_UNLCK, 0); + close(pid_fd); +} diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c new file mode 100644 index 0000000..07f6836 --- /dev/null +++ b/networking/udhcp/script.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* script.c + * + * Functions to call the DHCP client notification scripts + * + * Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* get a rough idea of how long an option will be (rounding up...) */ +static const int max_option_length[] = { + [OPTION_IP] = sizeof("255.255.255.255 "), + [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, + [OPTION_STRING] = 1, + [OPTION_BOOLEAN] = sizeof("yes "), + [OPTION_U8] = sizeof("255 "), + [OPTION_U16] = sizeof("65535 "), + [OPTION_S16] = sizeof("-32768 "), + [OPTION_U32] = sizeof("4294967295 "), + [OPTION_S32] = sizeof("-2147483684 "), +}; + + +static inline int upper_length(int length, int opt_index) +{ + return max_option_length[opt_index] * + (length / option_lengths[opt_index]); +} + + +static int sprintip(char *dest, char *pre, uint8_t *ip) +{ + return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]); +} + + +/* really simple implementation, just count the bits */ +static int mton(struct in_addr *mask) +{ + int i; + unsigned long bits = ntohl(mask->s_addr); + /* too bad one can't check the carry bit, etc in c bit + * shifting */ + for (i = 0; i < 32 && !((bits >> i) & 1); i++); + return 32 - i; +} + + +/* Fill dest with the text of option 'option'. */ +static void fill_options(char *dest, uint8_t *option, + const struct dhcp_option *type_p) +{ + int type, optlen; + uint16_t val_u16; + int16_t val_s16; + uint32_t val_u32; + int32_t val_s32; + int len = option[OPT_LEN - 2]; + + dest += sprintf(dest, "%s=", type_p->name); + + type = type_p->flags & TYPE_MASK; + optlen = option_lengths[type]; + for (;;) { + switch (type) { + case OPTION_IP_PAIR: + dest += sprintip(dest, "", option); + *(dest++) = '/'; + option += 4; + optlen = 4; + case OPTION_IP: /* Works regardless of host byte order. */ + dest += sprintip(dest, "", option); + break; + case OPTION_BOOLEAN: + dest += sprintf(dest, *option ? "yes" : "no"); + break; + case OPTION_U8: + dest += sprintf(dest, "%u", *option); + break; + case OPTION_U16: + memcpy(&val_u16, option, 2); + dest += sprintf(dest, "%u", ntohs(val_u16)); + break; + case OPTION_S16: + memcpy(&val_s16, option, 2); + dest += sprintf(dest, "%d", ntohs(val_s16)); + break; + case OPTION_U32: + memcpy(&val_u32, option, 4); + dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32)); + break; + case OPTION_S32: + memcpy(&val_s32, option, 4); + dest += sprintf(dest, "%ld", (long) ntohl(val_s32)); + break; + case OPTION_STRING: + memcpy(dest, option, len); + dest[len] = '\0'; + return; /* Short circuit this case */ + } + option += optlen; + len -= optlen; + if (len <= 0) break; + dest += sprintf(dest, " "); + } +} + + +/* put all the parameters into an environment */ +static char **fill_envp(struct dhcpMessage *packet) +{ + int num_options = 0; + int i, j; + char **envp; + uint8_t *temp; + struct in_addr subnet; + char over = 0; + + if (packet == NULL) + num_options = 0; + else { + for (i = 0; dhcp_options[i].code; i++) + if (get_option(packet, dhcp_options[i].code)) { + num_options++; + if (dhcp_options[i].code == DHCP_SUBNET) + num_options++; /* for mton */ + } + if (packet->siaddr) num_options++; + if ((temp = get_option(packet, DHCP_OPTION_OVER))) + over = *temp; + if (!(over & FILE_FIELD) && packet->file[0]) num_options++; + if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++; + } + + envp = xzalloc(sizeof(char *) * (num_options + 5)); + j = 0; + envp[j++] = xasprintf("interface=%s", client_config.interface); + envp[j++] = xasprintf("PATH=%s", + getenv("PATH") ? : "/bin:/usr/bin:/sbin:/usr/sbin"); + envp[j++] = xasprintf("HOME=%s", getenv("HOME") ? : "/"); + + if (packet == NULL) return envp; + + envp[j] = xmalloc(sizeof("ip=255.255.255.255")); + sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr); + + for (i = 0; dhcp_options[i].code; i++) { + temp = get_option(packet, dhcp_options[i].code); + if (!temp) + continue; + envp[j] = xmalloc(upper_length(temp[OPT_LEN - 2], + dhcp_options[i].flags & TYPE_MASK) + strlen(dhcp_options[i].name) + 2); + fill_options(envp[j++], temp, &dhcp_options[i]); + + /* Fill in a subnet bits option for things like /24 */ + if (dhcp_options[i].code == DHCP_SUBNET) { + memcpy(&subnet, temp, 4); + envp[j++] = xasprintf("mask=%d", mton(&subnet)); + } + } + if (packet->siaddr) { + envp[j] = xmalloc(sizeof("siaddr=255.255.255.255")); + sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr); + } + if (!(over & FILE_FIELD) && packet->file[0]) { + /* watch out for invalid packets */ + packet->file[sizeof(packet->file) - 1] = '\0'; + envp[j++] = xasprintf("boot_file=%s", packet->file); + } + if (!(over & SNAME_FIELD) && packet->sname[0]) { + /* watch out for invalid packets */ + packet->sname[sizeof(packet->sname) - 1] = '\0'; + envp[j++] = xasprintf("sname=%s", packet->sname); + } + return envp; +} + + +/* Call a script with a par file and env vars */ +void udhcp_run_script(struct dhcpMessage *packet, const char *name) +{ + int pid; + char **envp, **curr; + + if (client_config.script == NULL) + return; + + DEBUG("vfork'ing and execle'ing %s", client_config.script); + + envp = fill_envp(packet); + /* call script */ + pid = vfork(); + if (pid) { + waitpid(pid, NULL, 0); + for (curr = envp; *curr; curr++) free(*curr); + free(envp); + return; + } else if (pid == 0) { + /* close fd's? */ + /* exec script */ + execle(client_config.script, client_config.script, + name, NULL, envp); + bb_perror_msg("script %s failed", client_config.script); + exit(1); + } +} diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c new file mode 100644 index 0000000..8889fda --- /dev/null +++ b/networking/udhcp/serverpacket.c @@ -0,0 +1,261 @@ +/* vi: set sw=4 ts=4: */ +/* serverpacket.c + * + * Construct and send DHCP server packets + * + * Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* send a packet to giaddr using the kernel ip stack */ +static int send_packet_to_relay(struct dhcpMessage *payload) +{ + DEBUG("Forwarding packet to relay"); + + return udhcp_kernel_packet(payload, server_config.server, SERVER_PORT, + payload->giaddr, SERVER_PORT); +} + + +/* send a packet to a specific arp address and ip address by creating our own ip packet */ +static int send_packet_to_client(struct dhcpMessage *payload, int force_broadcast) +{ + uint8_t *chaddr; + uint32_t ciaddr; + + if (force_broadcast) { + DEBUG("broadcasting packet to client (NAK)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else if (payload->ciaddr) { + DEBUG("unicasting packet to client ciaddr"); + ciaddr = payload->ciaddr; + chaddr = payload->chaddr; + } else if (ntohs(payload->flags) & BROADCAST_FLAG) { + DEBUG("broadcasting packet to client (requested)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else { + DEBUG("unicasting packet to client yiaddr"); + ciaddr = payload->yiaddr; + chaddr = payload->chaddr; + } + return udhcp_raw_packet(payload, server_config.server, SERVER_PORT, + ciaddr, CLIENT_PORT, chaddr, server_config.ifindex); +} + + +/* send a dhcp packet, if force broadcast is set, the packet will be broadcast to the client */ +static int send_packet(struct dhcpMessage *payload, int force_broadcast) +{ + int ret; + + if (payload->giaddr) + ret = send_packet_to_relay(payload); + else ret = send_packet_to_client(payload, force_broadcast); + return ret; +} + + +static void init_packet(struct dhcpMessage *packet, struct dhcpMessage *oldpacket, char type) +{ + udhcp_init_header(packet, type); + packet->xid = oldpacket->xid; + memcpy(packet->chaddr, oldpacket->chaddr, 16); + packet->flags = oldpacket->flags; + packet->giaddr = oldpacket->giaddr; + packet->ciaddr = oldpacket->ciaddr; + add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server); +} + + +/* add in the bootp options */ +static void add_bootp_options(struct dhcpMessage *packet) +{ + packet->siaddr = server_config.siaddr; + if (server_config.sname) + strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1); + if (server_config.boot_file) + strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1); +} + + +/* send a DHCP OFFER to a DHCP DISCOVER */ +int sendOffer(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct dhcpOfferedAddr *lease = NULL; + uint32_t req_align, lease_time_align = server_config.lease; + uint8_t *req, *lease_time; + struct option_set *curr; + struct in_addr addr; + + uint32_t static_lease_ip; + + init_packet(&packet, oldpacket, DHCPOFFER); + + static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr); + + /* ADDME: if static, short circuit */ + if (!static_lease_ip) { + /* the client is in our lease/offered table */ + lease = find_lease_by_chaddr(oldpacket->chaddr); + if (lease) { + if (!lease_expired(lease)) + lease_time_align = lease->expires - time(0); + packet.yiaddr = lease->yiaddr; + + /* Or the client has a requested ip */ + } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) + /* Don't look here (ugly hackish thing to do) */ + && memcpy(&req_align, req, 4) + /* and the ip is in the lease range */ + && ntohl(req_align) >= ntohl(server_config.start) + && ntohl(req_align) <= ntohl(server_config.end) + && !static_lease_ip /* Check that its not a static lease */ + /* and is not already taken/offered */ + && (!(lease = find_lease_by_yiaddr(req_align)) + /* or its taken, but expired */ /* ADDME: or maybe in here */ + || lease_expired(lease)) + ) { + packet.yiaddr = req_align; /* FIXME: oh my, is there a host using this IP? */ + /* otherwise, find a free IP */ + } else { + /* Is it a static lease? (No, because find_address skips static lease) */ + packet.yiaddr = find_address(0); + /* try for an expired lease */ + if (!packet.yiaddr) packet.yiaddr = find_address(1); + } + + if (!packet.yiaddr) { + bb_error_msg("no IP addresses to give - OFFER abandoned"); + return -1; + } + if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time)) { + bb_error_msg("lease pool is full - OFFER abandoned"); + return -1; + } + lease_time = get_option(oldpacket, DHCP_LEASE_TIME); + if (lease_time) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + } + + /* Make sure we aren't just using the lease time from the previous offer */ + if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + /* ADDME: end of short circuit */ + } else { + /* It is a static lease... use it */ + packet.yiaddr = static_lease_ip; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending OFFER of %s", inet_ntoa(addr)); + return send_packet(&packet, 0); +} + + +int sendNAK(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + + init_packet(&packet, oldpacket, DHCPNAK); + + DEBUG("Sending NAK"); + return send_packet(&packet, 1); +} + + +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) +{ + struct dhcpMessage packet; + struct option_set *curr; + uint8_t *lease_time; + uint32_t lease_time_align = server_config.lease; + struct in_addr addr; + + init_packet(&packet, oldpacket, DHCPACK); + packet.yiaddr = yiaddr; + + if ((lease_time = get_option(oldpacket, DHCP_LEASE_TIME))) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + else if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending ACK to %s", inet_ntoa(addr)); + + if (send_packet(&packet, 0) < 0) + return -1; + + add_lease(packet.chaddr, packet.yiaddr, lease_time_align); + + return 0; +} + + +int send_inform(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct option_set *curr; + + init_packet(&packet, oldpacket, DHCPACK); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + return send_packet(&packet, 0); +} diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c new file mode 100644 index 0000000..3615965 --- /dev/null +++ b/networking/udhcp/signalpipe.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* signalpipe.c + * + * Signal pipe infrastructure. A reliable way of delivering signals. + * + * Russ Dill <Russ.Dill@asu.edu> December 2003 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" + + +static int signal_pipe[2]; + +static void signal_handler(int sig) +{ + if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) + bb_perror_msg("cannot send signal"); +} + + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void udhcp_sp_setup(void) +{ + socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe); + fcntl(signal_pipe[0], F_SETFD, FD_CLOEXEC); + fcntl(signal_pipe[1], F_SETFD, FD_CLOEXEC); + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGTERM, signal_handler); +} + + +/* Quick little function to setup the rfds. Will return the + * max_fd for use with select. Limited in that you can only pass + * one extra fd */ +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) +{ + FD_ZERO(rfds); + FD_SET(signal_pipe[0], rfds); + if (extra_fd >= 0) { + fcntl(extra_fd, F_SETFD, FD_CLOEXEC); + FD_SET(extra_fd, rfds); + } + return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd; +} + + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int udhcp_sp_read(fd_set *rfds) +{ + int sig; + + if (!FD_ISSET(signal_pipe[0], rfds)) + return 0; + + if (read(signal_pipe[0], &sig, sizeof(sig)) < 0) + return -1; + + return sig; +} diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c new file mode 100644 index 0000000..2bae68f --- /dev/null +++ b/networking/udhcp/socket.c @@ -0,0 +1,130 @@ +/* vi: set sw=4 ts=4: */ +/* + * socket.c -- DHCP server client/server socket creation + * + * udhcp client/server + * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au> + * Chris Trew <ctrew@moreton.com.au> + * + * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <net/if.h> +#include <features.h> +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <asm/types.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#endif + +#include "common.h" + + +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) +{ + int fd; + struct ifreq ifr; + struct sockaddr_in *our_ip; + + memset(&ifr, 0, sizeof(struct ifreq)); + fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + bb_perror_msg("socket failed"); + return -1; + } + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); + if (addr) { + if (ioctl(fd, SIOCGIFADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFADDR failed, is the interface up and configured?"); + close(fd); + return -1; + } + our_ip = (struct sockaddr_in *) &ifr.ifr_addr; + *addr = our_ip->sin_addr.s_addr; + DEBUG("%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr)); + } + + if (ifindex) { + if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { + bb_perror_msg("SIOCGIFINDEX failed"); + close(fd); + return -1; + } + DEBUG("adapter index %d", ifr.ifr_ifindex); + *ifindex = ifr.ifr_ifindex; + } + + if (arp) { + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFHWADDR failed"); + close(fd); + return -1; + } + memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); + DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x", + arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); + } + + return 0; +} + + +int listen_socket(uint32_t ip, int port, char *inf) +{ + struct ifreq interface; + int fd; + struct sockaddr_in addr; + + DEBUG("Opening listen socket on 0x%08x:%d %s", ip, port, inf); + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + if (setsockopt_broadcast(fd) == -1) { + close(fd); + return -1; + } + + strncpy(interface.ifr_name, inf, IFNAMSIZ); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface)) < 0) { + close(fd); + return -1; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c new file mode 100644 index 0000000..aabfb81 --- /dev/null +++ b/networking/udhcp/static_leases.c @@ -0,0 +1,99 @@ +/* vi: set sw=4 ts=4: */ +/* + * static_leases.c -- Couple of functions to assist with storing and + * retrieving data for static leases + * + * Wade Berrier <wberrier@myrealbox.com> September 2004 + * + */ + +#include "common.h" +#include "dhcpd.h" + + +/* Takes the address of the pointer to the static_leases linked list, + * Address to a 6 byte mac address + * Address to a 4 byte ip address */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip) +{ + struct static_lease *cur; + struct static_lease *new_static_lease; + + /* Build new node */ + new_static_lease = xmalloc(sizeof(struct static_lease)); + new_static_lease->mac = mac; + new_static_lease->ip = ip; + new_static_lease->next = NULL; + + /* If it's the first node to be added... */ + if (*lease_struct == NULL) { + *lease_struct = new_static_lease; + } else { + cur = *lease_struct; + while (cur->next) { + cur = cur->next; + } + + cur->next = new_static_lease; + } + + return 1; +} + +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) +{ + uint32_t return_ip; + struct static_lease *cur = lease_struct; + uint8_t *mac = arg; + + return_ip = 0; + + while (cur) { + /* If the client has the correct mac */ + if (memcmp(cur->mac, mac, 6) == 0) { + return_ip = *(cur->ip); + } + + cur = cur->next; + } + + return return_ip; +} + +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip) +{ + struct static_lease *cur = lease_struct; + + uint32_t return_val = 0; + + while (cur) { + /* If the client has the correct ip */ + if (*cur->ip == ip) + return_val = 1; + + cur = cur->next; + } + + return return_val; +} + +#if ENABLE_FEATURE_UDHCP_DEBUG +/* Print out static leases just to check what's going on */ +/* Takes the address of the pointer to the static_leases linked list */ +void printStaticLeases(struct static_lease **arg) +{ + /* Get a pointer to the linked list */ + struct static_lease *cur = *arg; + + while (cur) { + /* printf("PrintStaticLeases: Lease mac Address: %x\n", cur->mac); */ + printf("PrintStaticLeases: Lease mac Value: %x\n", *(cur->mac)); + /* printf("PrintStaticLeases: Lease ip Address: %x\n", cur->ip); */ + printf("PrintStaticLeases: Lease ip Value: %x\n", *(cur->ip)); + + cur = cur->next; + } +} +#endif diff --git a/networking/vconfig.c b/networking/vconfig.c new file mode 100644 index 0000000..003c1a8 --- /dev/null +++ b/networking/vconfig.c @@ -0,0 +1,164 @@ +/* vi: set sw=4 ts=4: */ +/* + * vconfig implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III <mjn3@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ + +#include "busybox.h" +#include <net/if.h> + +/* Stuff from linux/if_vlan.h, kernel version 2.4.23 */ +enum vlan_ioctl_cmds { + ADD_VLAN_CMD, + DEL_VLAN_CMD, + SET_VLAN_INGRESS_PRIORITY_CMD, + SET_VLAN_EGRESS_PRIORITY_CMD, + GET_VLAN_INGRESS_PRIORITY_CMD, + GET_VLAN_EGRESS_PRIORITY_CMD, + SET_VLAN_NAME_TYPE_CMD, + SET_VLAN_FLAG_CMD +}; +enum vlan_name_types { + VLAN_NAME_TYPE_PLUS_VID, /* Name will look like: vlan0005 */ + VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like: eth1.0005 */ + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like: vlan5 */ + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like: eth0.5 */ + VLAN_NAME_TYPE_HIGHEST +}; + +struct vlan_ioctl_args { + int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */ + char device1[24]; + + union { + char device2[24]; + int VID; + unsigned int skb_priority; + unsigned int name_type; + unsigned int bind_type; + unsigned int flag; /* Matches vlan_dev_info flags */ + } u; + + short vlan_qos; +}; + +#define VLAN_GROUP_ARRAY_LEN 4096 +#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */ + +/* On entry, table points to the length of the current string plus + * nul terminator plus data length for the subsequent entry. The + * return value is the last data entry for the matching string. */ +static const char *xfind_str(const char *table, const char *str) +{ + while (strcasecmp(str, table+1) != 0) { + if (!*(table += table[0])) { + bb_show_usage(); + } + } + return table - 1; +} + +static const char cmds[] = { + 4, ADD_VLAN_CMD, 7, + 'a', 'd', 'd', 0, + 3, DEL_VLAN_CMD, 7, + 'r', 'e', 'm', 0, + 3, SET_VLAN_NAME_TYPE_CMD, 17, + 's', 'e', 't', '_', + 'n', 'a', 'm', 'e', '_', + 't', 'y', 'p', 'e', 0, + 5, SET_VLAN_FLAG_CMD, 12, + 's', 'e', 't', '_', + 'f', 'l', 'a', 'g', 0, + 5, SET_VLAN_EGRESS_PRIORITY_CMD, 18, + 's', 'e', 't', '_', + 'e', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, + 5, SET_VLAN_INGRESS_PRIORITY_CMD, 16, + 's', 'e', 't', '_', + 'i', 'n', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, +}; + +static const char name_types[] = { + VLAN_NAME_TYPE_PLUS_VID, 16, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, 22, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, + VLAN_NAME_TYPE_RAW_PLUS_VID, 15, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, 20, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, +}; + +static const char conf_file_name[] = "/proc/net/vlan/config"; + +int vconfig_main(int argc, char **argv) +{ + struct vlan_ioctl_args ifr; + const char *p; + int fd; + + if (argc < 3) { + bb_show_usage(); + } + + /* Don't bother closing the filedes. It will be closed on cleanup. */ + /* Will die if 802.1q is not present */ + xopen(conf_file_name, O_RDONLY); + + memset(&ifr, 0, sizeof(struct vlan_ioctl_args)); + + ++argv; + p = xfind_str(cmds+2, *argv); + ifr.cmd = *p; + if (argc != p[-1]) { + bb_show_usage(); + } + + if (ifr.cmd == SET_VLAN_NAME_TYPE_CMD) { /* set_name_type */ + ifr.u.name_type = *xfind_str(name_types+1, argv[1]); + } else { + if (strlen(argv[1]) >= IF_NAMESIZE) { + bb_error_msg_and_die("if_name >= %d chars", IF_NAMESIZE); + } + strcpy(ifr.device1, argv[1]); + p = argv[2]; + + /* I suppose one could try to combine some of the function calls below, + * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized + * (unsigned) int members of a unions. But because of the range checking, + * doing so wouldn't save that much space and would also make maintainence + * more of a pain. */ + if (ifr.cmd == SET_VLAN_FLAG_CMD) { /* set_flag */ + ifr.u.flag = xatoul_range(p, 0, 1); + /* DM: in order to set reorder header, qos must be set */ + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } else if (ifr.cmd == ADD_VLAN_CMD) { /* add */ + ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1); + } else if (ifr.cmd != DEL_VLAN_CMD) { /* set_{egress|ingress}_map */ + ifr.u.skb_priority = xatou(p); + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } + } + + fd = xsocket(AF_INET, SOCK_STREAM, 0); + if (ioctl(fd, SIOCSIFVLAN, &ifr) < 0) { + bb_perror_msg_and_die("ioctl error for %s", *argv); + } + + return 0; +} diff --git a/networking/wget.c b/networking/wget.c new file mode 100644 index 0000000..028e18c --- /dev/null +++ b/networking/wget.c @@ -0,0 +1,832 @@ +/* vi: set sw=4 ts=4: */ +/* + * wget - retrieve a file using HTTP or FTP + * + * Chip Rosenthal Covad Communications <chip@laserlink.net> + * + */ + +/* We want libc to give us xxx64 functions also */ +/* http://www.unix.org/version2/whatsnew/lfs20mar.html */ +#define _LARGEFILE64_SOURCE 1 + +#include "busybox.h" +#include <getopt.h> /* for struct option */ + +struct host_info { + // May be used if we ever will want to free() all xstrdup()s... + /* char *allocated; */ + char *host; + int port; + char *path; + int is_ftp; + char *user; +}; + +static void parse_url(char *url, struct host_info *h); +static FILE *open_socket(struct sockaddr_in *s_in); +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc); +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf); + +/* Globals (can be accessed from signal handlers */ +static off_t content_len; /* Content-length of the file */ +static off_t beg_range; /* Range at which continue begins */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static off_t transferred; /* Number of bytes transferred so far */ +#endif +static int chunked; /* chunked transfer encoding */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static void progressmeter(int flag); +static char *curfile; /* Name of current file being transferred */ +static struct timeval start; /* Time a transfer started */ +enum { + STALLTIME = 5 /* Seconds when xfer considered "stalled" */ +}; +#else +static void progressmeter(int flag) {} +#endif + +/* Read NMEMB elements of SIZE bytes into PTR from STREAM. Returns the + * number of elements read, and a short count if an eof or non-interrupt + * error is encountered. */ +static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t ret = 0; + + do { + clearerr(stream); + ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream); + } while (ret < nmemb && ferror(stream) && errno == EINTR); + + return ret; +} + +/* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM. + * Returns S, or NULL if an eof or non-interrupt error is encountered. */ +static char *safe_fgets(char *s, int size, FILE *stream) +{ + char *ret; + + do { + clearerr(stream); + ret = fgets(s, size, stream); + } while (ret == NULL && ferror(stream) && errno == EINTR); + + return ret; +} + +#if ENABLE_FEATURE_WGET_AUTHENTICATION +/* + * Base64-encode character string and return the string. + */ +static char *base64enc(unsigned char *p, char *buf, int len) +{ + bb_uuencode(p, buf, len, bb_uuenc_tbl_base64); + return buf; +} +#endif + +int wget_main(int argc, char **argv) +{ + char buf[512]; + struct host_info server, target; + struct sockaddr_in s_in; + int n, status; + int port; + int try = 5; + unsigned opt; + char *s; + char *proxy = 0; + char *dir_prefix = NULL; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + char *extra_headers = NULL; + llist_t *headers_llist = NULL; +#endif + + /* server.allocated = target.allocated = NULL; */ + + FILE *sfp = NULL; /* socket to web/ftp server */ + FILE *dfp = NULL; /* socket to ftp server (data) */ + char *fname_out = NULL; /* where to direct output (-O) */ + int got_clen = 0; /* got content-length: from server */ + int output_fd = -1; + int use_proxy = 1; /* Use proxies if env vars are set */ + const char *proxy_flag = "on"; /* Use proxies if env vars are set */ + const char *user_agent = "Wget";/* Content of the "User-Agent" header field */ + + /* + * Crack command line. + */ + enum { + WGET_OPT_CONTINUE = 0x1, + WGET_OPT_QUIET = 0x2, + WGET_OPT_OUTNAME = 0x4, + WGET_OPT_PREFIX = 0x8, + WGET_OPT_PROXY = 0x10, + WGET_OPT_USER_AGENT = 0x20, + WGET_OPT_PASSIVE = 0x40, + WGET_OPT_HEADER = 0x80, + }; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + static const struct option wget_long_options[] = { + // name, has_arg, flag, val + { "continue", no_argument, NULL, 'c' }, + { "quiet", no_argument, NULL, 'q' }, + { "output-document", required_argument, NULL, 'O' }, + { "directory-prefix", required_argument, NULL, 'P' }, + { "proxy", required_argument, NULL, 'Y' }, + { "user-agent", required_argument, NULL, 'U' }, + { "passive-ftp", no_argument, NULL, 0xff }, + { "header", required_argument, NULL, 0xfe }, + { 0, 0, 0, 0 } + }; + applet_long_options = wget_long_options; +#endif + opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); + opt = getopt32(argc, argv, "cqO:P:Y:U:", + &fname_out, &dir_prefix, + &proxy_flag, &user_agent + USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist) + ); + if (strcmp(proxy_flag, "off") == 0) { + /* Use the proxy if necessary. */ + use_proxy = 0; + } +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (headers_llist) { + int size = 1; + char *cp; + llist_t *ll = headers_llist = rev_llist(headers_llist); + while (ll) { + size += strlen(ll->data) + 2; + ll = ll->link; + } + extra_headers = cp = xmalloc(size); + while (headers_llist) { + cp += sprintf(cp, "%s\r\n", headers_llist->data); + headers_llist = headers_llist->link; + } + } +#endif + + parse_url(argv[optind], &target); + server.host = target.host; + server.port = target.port; + + /* + * Use the proxy if necessary. + */ + if (use_proxy) { + proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy"); + if (proxy && *proxy) { + parse_url(proxy, &server); + } else { + use_proxy = 0; + } + } + + /* Guess an output filename */ + if (!fname_out) { + // Dirty hack. Needed because bb_get_last_path_component + // will destroy trailing / by storing '\0' in last byte! + if (*target.path && target.path[strlen(target.path)-1] != '/') { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + bb_get_last_path_component(target.path); + } + if (!fname_out || !fname_out[0]) { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + "index.html"; + } + if (dir_prefix != NULL) + fname_out = concat_path_file(dir_prefix, fname_out); +#if ENABLE_FEATURE_WGET_STATUSBAR + } else { + curfile = bb_get_last_path_component(fname_out); +#endif + } + /* Impossible? + if ((opt & WGET_OPT_CONTINUE) && !fname_out) + bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */ + + /* + * Determine where to start transfer. + */ + if (fname_out[0] == '-' && !fname_out[1]) { + output_fd = 1; + opt &= ~WGET_OPT_CONTINUE; + } + if (opt & WGET_OPT_CONTINUE) { + output_fd = open(fname_out, O_WRONLY); + if (output_fd >= 0) { + beg_range = xlseek(output_fd, 0, SEEK_END); + } + /* File doesn't exist. We do not create file here yet. + We are not sure it exists on remove side */ + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + if (!(opt & WGET_OPT_QUIET)) { + fprintf(stderr, "Connecting to %s[%s]:%d\n", + server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port)); + } + + if (use_proxy || !target.is_ftp) { + /* + * HTTP session + */ + do { + got_clen = chunked = 0; + + if (!--try) + bb_error_msg_and_die("too many redirections"); + + /* + * Open socket to http server + */ + if (sfp) fclose(sfp); + sfp = open_socket(&s_in); + + /* + * Send HTTP request. + */ + if (use_proxy) { + const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n"; +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (strchr(target.host, ':')) + format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n"; +#endif + fprintf(sfp, format, + target.is_ftp ? "f" : "ht", target.host, + ntohs(target.port), target.path); + } else { + fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); + } + + fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", target.host, + user_agent); + +#if ENABLE_FEATURE_WGET_AUTHENTICATION + if (target.user) { + fprintf(sfp, "Authorization: Basic %s\r\n", + base64enc((unsigned char*)target.user, buf, sizeof(buf))); + } + if (use_proxy && server.user) { + fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", + base64enc((unsigned char*)server.user, buf, sizeof(buf))); + } +#endif + + if (beg_range) + fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (extra_headers) + fputs(extra_headers, sfp); +#endif + fprintf(sfp, "Connection: close\r\n\r\n"); + + /* + * Retrieve HTTP response line and check for "200" status code. + */ + read_response: + if (fgets(buf, sizeof(buf), sfp) == NULL) + bb_error_msg_and_die("no response from server"); + + s = buf; + while (*s != '\0' && !isspace(*s)) ++s; + s = skip_whitespace(s); + // FIXME: no error check + // xatou wouldn't work: "200 OK" + status = atoi(s); + switch (status) { + case 0: + case 100: + while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) + /* eat all remaining headers */; + goto read_response; + case 200: + break; + case 300: /* redirection */ + case 301: + case 302: + case 303: + break; + case 206: + if (beg_range) + break; + /*FALLTHRU*/ + default: + /* Show first line only and kill any ESC tricks */ + buf[strcspn(buf, "\n\r\x1b")] = '\0'; + bb_error_msg_and_die("server returned error: %s", buf); + } + + /* + * Retrieve HTTP headers. + */ + while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { + if (strcasecmp(buf, "content-length") == 0) { + content_len = BB_STRTOOFF(s, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("content-length %s is garbage", s); + } + got_clen = 1; + continue; + } + if (strcasecmp(buf, "transfer-encoding") == 0) { + if (strcasecmp(s, "chunked") != 0) + bb_error_msg_and_die("server wants to do %s transfer encoding", s); + chunked = got_clen = 1; + } + if (strcasecmp(buf, "location") == 0) { + if (s[0] == '/') + /* free(target.allocated); */ + target.path = /* target.allocated = */ xstrdup(s+1); + else { + parse_url(s, &target); + if (use_proxy == 0) { + server.host = target.host; + server.port = target.port; + } + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + break; + } + } + } + } while(status >= 300); + + dfp = sfp; + + } else { + + /* + * FTP session + */ + if (!target.user) + target.user = xstrdup("anonymous:busybox@"); + + sfp = open_socket(&s_in); + if (ftpcmd(NULL, NULL, sfp, buf) != 220) + bb_error_msg_and_die("%s", buf+4); + + /* + * Splitting username:password pair, + * trying to log in + */ + s = strchr(target.user, ':'); + if (s) + *(s++) = '\0'; + switch (ftpcmd("USER ", target.user, sfp, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", s, sfp, buf) == 230) + break; + /* FALLTHRU (failed login) */ + default: + bb_error_msg_and_die("ftp login: %s", buf+4); + } + + ftpcmd("TYPE I", NULL, sfp, buf); + + /* + * Querying file size + */ + if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { + content_len = BB_STRTOOFF(buf+4, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("SIZE value is garbage"); + } + got_clen = 1; + } + + /* + * Entering passive mode + */ + if (ftpcmd("PASV", NULL, sfp, buf) != 227) { + pasv_error: + bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); + } + // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] + // Server's IP is N1.N2.N3.N4 (we ignore it) + // Server's port for data connection is P1*256+P2 + s = strrchr(buf, ')'); + if (s) s[0] = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port = xatou_range(s+1, 0, 255); + *s = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port += xatou_range(s+1, 0, 255) * 256; + s_in.sin_port = htons(port); + dfp = open_socket(&s_in); + + if (beg_range) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, sfp, buf) == 350) + content_len -= beg_range; + } + + if (ftpcmd("RETR ", target.path, sfp, buf) > 150) + bb_error_msg_and_die("bad response to RETR: %s", buf); + } + + + /* + * Retrieve file + */ + if (chunked) { + fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check?? */ + } + + /* Do it before progressmeter (want to have nice error message) */ + if (output_fd < 0) + output_fd = xopen(fname_out, + O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(-1); + + do { + while (content_len > 0 || !got_clen) { + unsigned rdsz = sizeof(buf); + if (content_len < sizeof(buf) && (chunked || got_clen)) + rdsz = (unsigned)content_len; + n = safe_fread(buf, 1, rdsz, dfp); + if (n <= 0) + break; + if (full_write(output_fd, buf, n) != n) { + bb_perror_msg_and_die(bb_msg_write_error); + } +#if ENABLE_FEATURE_WGET_STATUSBAR + transferred += n; +#endif + if (got_clen) { + content_len -= n; + } + } + + if (chunked) { + safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ + safe_fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check? */ + if (content_len == 0) { + chunked = 0; /* all done! */ + } + } + + if (n == 0 && ferror(dfp)) { + bb_perror_msg_and_die(bb_msg_read_error); + } + } while (chunked); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(1); + + if ((use_proxy == 0) && target.is_ftp) { + fclose(dfp); + if (ftpcmd(NULL, NULL, sfp, buf) != 226) + bb_error_msg_and_die("ftp error: %s", buf+4); + ftpcmd("QUIT", NULL, sfp, buf); + } + exit(EXIT_SUCCESS); +} + + +static void parse_url(char *src_url, struct host_info *h) +{ + char *url, *p, *cp, *sp, *up, *pp; + + /* h->allocated = */ url = xstrdup(src_url); + + if (strncmp(url, "http://", 7) == 0) { + h->port = bb_lookup_port("http", "tcp", 80); + h->host = url + 7; + h->is_ftp = 0; + } else if (strncmp(url, "ftp://", 6) == 0) { + h->port = bb_lookup_port("ftp", "tcp", 21); + h->host = url + 6; + h->is_ftp = 1; + } else + bb_error_msg_and_die("not an http or ftp url: %s", url); + + // FYI: + // "Real" wget 'http://busybox.net?var=a/b' sends this request: + // 'GET /?var=a/b HTTP 1.0' + // and saves 'index.html?var=a%2Fb' (we save 'b') + // wget 'http://busybox.net?login=john@doe': + // request: 'GET /?login=john@doe HTTP/1.0' + // saves: 'index.html?login=john@doe' (we save '?login=john@doe') + // wget 'http://busybox.net#test/test': + // request: 'GET / HTTP/1.0' + // saves: 'index.html' (we save 'test') + // + // We also don't add unique .N suffix if file exists... + sp = strchr(h->host, '/'); + p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; + p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; + if (!sp) { + h->path = ""; + } else if (*sp == '/') { + *sp++ = '\0'; + h->path = sp; + } else { // '#' or '?' + // http://busybox.net?login=john@doe is a valid URL + // memmove converts to: + // http:/busybox.nett?login=john@doe... + memmove(h->host-1, h->host, sp - h->host); + h->host--; + sp[-1] = '\0'; + h->path = sp; + } + + up = strrchr(h->host, '@'); + if (up != NULL) { + h->user = h->host; + *up++ = '\0'; + h->host = up; + } else + h->user = NULL; + + pp = h->host; + +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (h->host[0] == '[') { + char *ep; + + ep = h->host + 1; + while (*ep == ':' || isxdigit(*ep)) + ep++; + if (*ep == ']') { + h->host++; + *ep = '\0'; + pp = ep + 1; + } + } +#endif + + cp = strchr(pp, ':'); + if (cp != NULL) { + *cp++ = '\0'; + h->port = htons(xatou16(cp)); + } +} + + +static FILE *open_socket(struct sockaddr_in *s_in) +{ + FILE *fp; + + /* glibc 2.4 seems to try seeking on it - ??! */ + /* hopefully it understands what ESPIPE means... */ + fp = fdopen(xconnect_tcp_v4(s_in), "r+"); + if (fp == NULL) + bb_perror_msg_and_die("fdopen"); + + return fp; +} + + +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) +{ + char *s, *hdrval; + int c; + + *istrunc = 0; + + /* retrieve header line */ + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + + /* see if we are at the end of the headers */ + for (s = buf; *s == '\r'; ++s) + ; + if (s[0] == '\n') + return NULL; + + /* convert the header name to lower case */ + for (s = buf; isalnum(*s) || *s == '-'; ++s) + *s = tolower(*s); + + /* verify we are at the end of the header name */ + if (*s != ':') + bb_error_msg_and_die("bad header line: %s", buf); + + /* locate the start of the header value */ + for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) + ; + hdrval = s; + + /* locate the end of header */ + while (*s != '\0' && *s != '\r' && *s != '\n') + ++s; + + /* end of header found */ + if (*s != '\0') { + *s = '\0'; + return hdrval; + } + + /* Rats! The buffer isn't big enough to hold the entire header value. */ + while (c = getc(fp), c != EOF && c != '\n') + ; + *istrunc = 1; + return hdrval; +} + +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf) +{ + int result; + if (s1) { + if (!s2) s2 = ""; + fprintf(fp, "%s%s\r\n", s1, s2); + fflush(fp); + } + + do { + char *buf_ptr; + + if (fgets(buf, 510, fp) == NULL) { + bb_perror_msg_and_die("error getting response"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + buf[3] = '\0'; + result = xatoi_u(buf); + buf[3] = ' '; + return result; +} + +#if ENABLE_FEATURE_WGET_STATUSBAR +/* Stuff below is from BSD rcp util.c, as added to openshh. + * Original copyright notice is retained at the end of this file. + */ +static int +getttywidth(void) +{ + int width; + get_terminal_width_height(0, &width, NULL); + return width; +} + +static void +updateprogressmeter(int ignore) +{ + int save_errno = errno; + + progressmeter(0); + errno = save_errno; +} + +static void alarmtimer(int iwait) +{ + struct itimerval itv; + + itv.it_value.tv_sec = iwait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); +} + + +static void +progressmeter(int flag) +{ + static struct timeval lastupdate; + static off_t lastsize, totalsize; + + struct timeval now, td, tvwait; + off_t abbrevsize; + int elapsed, ratio, barlength, i; + char buf[256]; + + if (flag == -1) { /* first call to progressmeter */ + gettimeofday(&start, (struct timezone *) 0); + lastupdate = start; + lastsize = 0; + totalsize = content_len + beg_range; /* as content_len changes.. */ + } + + gettimeofday(&now, (struct timezone *) 0); + ratio = 100; + if (totalsize != 0 && !chunked) { + /* long long helps to have working ETA even if !LFS */ + ratio = (int) (100 * (unsigned long long)(transferred+beg_range) / totalsize); + ratio = MIN(ratio, 100); + } + + fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); + + barlength = getttywidth() - 51; + if (barlength > 0 && barlength < sizeof(buf)) { + i = barlength * ratio / 100; + memset(buf, '*', i); + memset(buf + i, ' ', barlength - i); + buf[barlength] = '\0'; + fprintf(stderr, "|%s|", buf); + } + i = 0; + abbrevsize = transferred + beg_range; + while (abbrevsize >= 100000) { + i++; + abbrevsize >>= 10; + } + /* see http://en.wikipedia.org/wiki/Tera */ + fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' '); + + timersub(&now, &lastupdate, &tvwait); + if (transferred > lastsize) { + lastupdate = now; + lastsize = transferred; + if (tvwait.tv_sec >= STALLTIME) + timeradd(&start, &tvwait, &start); + tvwait.tv_sec = 0; + } + timersub(&now, &start, &td); + elapsed = td.tv_sec; + + if (tvwait.tv_sec >= STALLTIME) { + fprintf(stderr, " - stalled -"); + } else { + off_t to_download = totalsize - beg_range; + if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) { + fprintf(stderr, "--:--:-- ETA"); + } else { + /* to_download / (transferred/elapsed) - elapsed: */ + int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); + /* (long long helps to have working ETA even if !LFS) */ + i = eta % 3600; + fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); + } + } + + if (flag == -1) { /* first call to progressmeter */ + struct sigaction sa; + sa.sa_handler = updateprogressmeter; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + alarmtimer(1); + } else if (flag == 1) { /* last call to progressmeter */ + alarmtimer(0); + transferred = 0; + putc('\n', stderr); + } +} +#endif + +/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, + * much of which was blatantly stolen from openssh. */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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. + * + * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change + * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $ + */ diff --git a/networking/zcip.c b/networking/zcip.c new file mode 100644 index 0000000..27e281c --- /dev/null +++ b/networking/zcip.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * RFC3927 ZeroConf IPv4 Link-Local addressing + * (see <http://www.zeroconf.org/>) + * + * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com) + * Copyright (C) 2004 by David Brownell + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * ZCIP just manages the 169.254.*.* addresses. That network is not + * routed at the IP level, though various proxies or bridges can + * certainly be used. Its naming is built over multicast DNS. + */ + +//#define DEBUG + +// TODO: +// - more real-world usage/testing, especially daemon mode +// - kernel packet filters to reduce scheduling noise +// - avoid silent script failures, especially under load... +// - link status monitoring (restart on link-up; stop on link-down) + +#include "busybox.h" +#include <syslog.h> +#include <poll.h> +#include <sys/wait.h> +#include <netinet/ether.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> + +#include <linux/if_packet.h> +#include <linux/sockios.h> + + +struct arp_packet { + struct ether_header hdr; + struct ether_arp arp; +} ATTRIBUTE_PACKED; + +enum { +/* 169.254.0.0 */ + LINKLOCAL_ADDR = 0xa9fe0000, + +/* protocol timeout parameters, specified in seconds */ + PROBE_WAIT = 1, + PROBE_MIN = 1, + PROBE_MAX = 2, + PROBE_NUM = 3, + MAX_CONFLICTS = 10, + RATE_LIMIT_INTERVAL = 60, + ANNOUNCE_WAIT = 2, + ANNOUNCE_NUM = 2, + ANNOUNCE_INTERVAL = 2, + DEFEND_INTERVAL = 10 +}; + +/* States during the configuration process. */ +enum { + PROBE = 0, + RATE_LIMIT_PROBE, + ANNOUNCE, + MONITOR, + DEFEND +}; + +#define VDBG(fmt,args...) \ + do { } while (0) + +static unsigned opts; +#define FOREGROUND (opts & 1) +#define QUIT (opts & 2) + +/** + * Pick a random link local IP address on 169.254/16, except that + * the first and last 256 addresses are reserved. + */ +static void pick(struct in_addr *ip) +{ + unsigned tmp; + + /* use cheaper math than lrand48() mod N */ + do { + tmp = (lrand48() >> 16) & IN_CLASSB_HOST; + } while (tmp > (IN_CLASSB_HOST - 0x0200)); + ip->s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp); +} + +/* TODO: we need a flag to direct bb_[p]error_msg output to stderr. */ + +/** + * Broadcast an ARP packet. + */ +static void arp(int fd, struct sockaddr *saddr, int op, + const struct ether_addr *source_addr, struct in_addr source_ip, + const struct ether_addr *target_addr, struct in_addr target_ip) +{ + struct arp_packet p; + memset(&p, 0, sizeof(p)); + + // ether header + p.hdr.ether_type = htons(ETHERTYPE_ARP); + memcpy(p.hdr.ether_shost, source_addr, ETH_ALEN); + memset(p.hdr.ether_dhost, 0xff, ETH_ALEN); + + // arp request + p.arp.arp_hrd = htons(ARPHRD_ETHER); + p.arp.arp_pro = htons(ETHERTYPE_IP); + p.arp.arp_hln = ETH_ALEN; + p.arp.arp_pln = 4; + p.arp.arp_op = htons(op); + memcpy(&p.arp.arp_sha, source_addr, ETH_ALEN); + memcpy(&p.arp.arp_spa, &source_ip, sizeof (p.arp.arp_spa)); + memcpy(&p.arp.arp_tha, target_addr, ETH_ALEN); + memcpy(&p.arp.arp_tpa, &target_ip, sizeof (p.arp.arp_tpa)); + + // send it + if (sendto(fd, &p, sizeof (p), 0, saddr, sizeof (*saddr)) < 0) { + bb_perror_msg("sendto"); + //return -errno; + } + // Currently all callers ignore errors, that's why returns are + // commented out... + //return 0; +} + +/** + * Run a script. + */ +static int run(char *script, char *arg, char *intf, struct in_addr *ip) +{ + int pid, status; + char *why; + + if(1) { //always true: if (script != NULL) + VDBG("%s run %s %s\n", intf, script, arg); + if (ip != NULL) { + char *addr = inet_ntoa(*ip); + setenv("ip", addr, 1); + bb_info_msg("%s %s %s", arg, intf, addr); + } + + pid = vfork(); + if (pid < 0) { // error + why = "vfork"; + goto bad; + } else if (pid == 0) { // child + execl(script, script, arg, NULL); + bb_perror_msg("execl"); + _exit(EXIT_FAILURE); + } + + if (waitpid(pid, &status, 0) <= 0) { + why = "waitpid"; + goto bad; + } + if (WEXITSTATUS(status) != 0) { + bb_error_msg("script %s failed, exit=%d", + script, WEXITSTATUS(status)); + return -errno; + } + } + return 0; +bad: + status = -errno; + bb_perror_msg("%s %s, %s", arg, intf, why); + return status; +} + + +/** + * Return milliseconds of random delay, up to "secs" seconds. + */ +static unsigned ATTRIBUTE_ALWAYS_INLINE ms_rdelay(unsigned secs) +{ + return lrand48() % (secs * 1000); +} + +/** + * main program + */ + +/* Used to be auto variables on main() stack, but + * most of them were zero-inited. Moving them to bss + * is more space-efficient. + */ +static const struct in_addr null_ip; // = { 0 }; +static const struct ether_addr null_addr; // = { {0, 0, 0, 0, 0, 0} }; + +static struct sockaddr saddr; // memset(0); +static struct in_addr ip; // = { 0 }; +static struct ifreq ifr; //memset(0); + +static char *intf; // = NULL; +static char *script; // = NULL; +static suseconds_t timeout; // = 0; // milliseconds +static unsigned conflicts; // = 0; +static unsigned nprobes; // = 0; +static unsigned nclaims; // = 0; +static int ready; // = 0; +static int verbose; // = 0; +static int state = PROBE; + +int zcip_main(int argc, char *argv[]) +{ + struct ether_addr eth_addr; + char *why; + int fd; + + // parse commandline: prog [options] ifname script + char *r_opt; + opt_complementary = "vv:vf"; // -v accumulates and implies -f + opts = getopt32(argc, argv, "fqr:v", &r_opt, &verbose); + if (!FOREGROUND) { + /* Do it early, before all bb_xx_msg calls */ + logmode = LOGMODE_SYSLOG; + openlog(applet_name, 0, LOG_DAEMON); + } + if (opts & 4) { // -r n.n.n.n + if (inet_aton(r_opt, &ip) == 0 + || (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR) { + bb_error_msg_and_die("invalid link address"); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + bb_show_usage(); + intf = argv[0]; + script = argv[1]; + setenv("interface", intf, 1); + + // initialize the interface (modprobe, ifup, etc) + if (run(script, "init", intf, NULL) < 0) + return EXIT_FAILURE; + + // initialize saddr + //memset(&saddr, 0, sizeof (saddr)); + safe_strncpy(saddr.sa_data, intf, sizeof (saddr.sa_data)); + + // open an ARP socket + fd = xsocket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + // bind to the interface's ARP socket + xbind(fd, &saddr, sizeof (saddr)); + + // get the interface's ethernet address + //memset(&ifr, 0, sizeof (ifr)); + strncpy(ifr.ifr_name, intf, sizeof (ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + bb_perror_msg_and_die("get ethernet address"); + } + memcpy(ð_addr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + // start with some stable ip address, either a function of + // the hardware address or else the last address we used. + // NOTE: the sequence of addresses we try changes only + // depending on when we detect conflicts. + // (SVID 3 bogon: who says that "short" is always 16 bits?) + seed48( (unsigned short*)&ifr.ifr_hwaddr.sa_data ); + if (ip.s_addr == 0) + pick(&ip); + + // FIXME cases to handle: + // - zcip already running! + // - link already has local address... just defend/update + + // daemonize now; don't delay system startup + if (!FOREGROUND) { + setsid(); + xdaemon(0, 0); + bb_info_msg("start, interface %s", intf); + } + + // run the dynamic address negotiation protocol, + // restarting after address conflicts: + // - start with some address we want to try + // - short random delay + // - arp probes to see if another host else uses it + // - arp announcements that we're claiming it + // - use it + // - defend it, within limits + while (1) { + struct pollfd fds[1]; + struct timeval tv1; + struct arp_packet p; + + int source_ip_conflict = 0; + int target_ip_conflict = 0; + + fds[0].fd = fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + // poll, being ready to adjust current timeout + if (!timeout) { + timeout = ms_rdelay(PROBE_WAIT); + // FIXME setsockopt(fd, SO_ATTACH_FILTER, ...) to + // make the kernel filter out all packets except + // ones we'd care about. + } + // set tv1 to the point in time when we timeout + gettimeofday(&tv1, NULL); + tv1.tv_usec += (timeout % 1000) * 1000; + while (tv1.tv_usec > 1000000) { + tv1.tv_usec -= 1000000; + tv1.tv_sec++; + } + tv1.tv_sec += timeout / 1000; + + VDBG("...wait %ld %s nprobes=%d, nclaims=%d\n", + timeout, intf, nprobes, nclaims); + switch (poll(fds, 1, timeout)) { + + // timeout + case 0: + VDBG("state = %d\n", state); + switch (state) { + case PROBE: + // timeouts in the PROBE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nprobes < PROBE_NUM) { + nprobes++; + VDBG("probe/%d %s@%s\n", + nprobes, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, null_ip, + &null_addr, ip); + timeout = PROBE_MIN * 1000; + timeout += ms_rdelay(PROBE_MAX + - PROBE_MIN); + } + else { + // Switch to announce state. + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + break; + case RATE_LIMIT_PROBE: + // timeouts in the RATE_LIMIT_PROBE state mean no conflicting ARP packets + // have been received, so we can move immediately to the announce state + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + break; + case ANNOUNCE: + // timeouts in the ANNOUNCE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nclaims < ANNOUNCE_NUM) { + nclaims++; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + else { + // Switch to monitor state. + state = MONITOR; + // link is ok to use earlier + // FIXME update filters + run(script, "config", intf, &ip); + ready = 1; + conflicts = 0; + timeout = -1; // Never timeout in the monitor state. + + // NOTE: all other exit paths + // should deconfig ... + if (QUIT) + return EXIT_SUCCESS; + } + break; + case DEFEND: + // We won! No ARP replies, so just go back to monitor. + state = MONITOR; + timeout = -1; + conflicts = 0; + break; + default: + // Invalid, should never happen. Restart the whole protocol. + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch (state) + break; // case 0 (timeout) + // packets arriving + case 1: + // We need to adjust the timeout in case we didn't receive + // a conflicting packet. + if (timeout > 0) { + struct timeval tv2; + + gettimeofday(&tv2, NULL); + if (timercmp(&tv1, &tv2, <)) { + // Current time is greater than the expected timeout time. + // Should never happen. + VDBG("missed an expected timeout\n"); + timeout = 0; + } else { + VDBG("adjusting timeout\n"); + timersub(&tv1, &tv2, &tv1); + timeout = 1000 * tv1.tv_sec + + tv1.tv_usec / 1000; + } + } + + if ((fds[0].revents & POLLIN) == 0) { + if (fds[0].revents & POLLERR) { + // FIXME: links routinely go down; + // this shouldn't necessarily exit. + bb_error_msg("%s: poll error", intf); + if (ready) { + run(script, "deconfig", + intf, &ip); + } + return EXIT_FAILURE; + } + continue; + } + + // read ARP packet + if (recv(fd, &p, sizeof (p), 0) < 0) { + why = "recv"; + goto bad; + } + if (p.hdr.ether_type != htons(ETHERTYPE_ARP)) + continue; + +#ifdef DEBUG + { + struct ether_addr * sha = (struct ether_addr *) p.arp.arp_sha; + struct ether_addr * tha = (struct ether_addr *) p.arp.arp_tha; + struct in_addr * spa = (struct in_addr *) p.arp.arp_spa; + struct in_addr * tpa = (struct in_addr *) p.arp.arp_tpa; + VDBG("%s recv arp type=%d, op=%d,\n", + intf, ntohs(p.hdr.ether_type), + ntohs(p.arp.arp_op)); + VDBG("\tsource=%s %s\n", + ether_ntoa(sha), + inet_ntoa(*spa)); + VDBG("\ttarget=%s %s\n", + ether_ntoa(tha), + inet_ntoa(*tpa)); + } +#endif + if (p.arp.arp_op != htons(ARPOP_REQUEST) + && p.arp.arp_op != htons(ARPOP_REPLY)) + continue; + + if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + memcmp(ð_addr, &p.arp.arp_sha, ETH_ALEN) != 0) { + source_ip_conflict = 1; + } + if (memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + p.arp.arp_op == htons(ARPOP_REQUEST) && + memcmp(ð_addr, &p.arp.arp_tha, ETH_ALEN) != 0) { + target_ip_conflict = 1; + } + + VDBG("state = %d, source ip conflict = %d, target ip conflict = %d\n", + state, source_ip_conflict, target_ip_conflict); + switch (state) { + case PROBE: + case ANNOUNCE: + // When probing or announcing, check for source IP conflicts + // and other hosts doing ARP probes (target IP conflicts). + if (source_ip_conflict || target_ip_conflict) { + conflicts++; + if (conflicts >= MAX_CONFLICTS) { + VDBG("%s ratelimit\n", intf); + timeout = RATE_LIMIT_INTERVAL * 1000; + state = RATE_LIMIT_PROBE; + } + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + case MONITOR: + // If a conflict, we try to defend with a single ARP probe. + if (source_ip_conflict) { + VDBG("monitor conflict -- defending\n"); + state = DEFEND; + timeout = DEFEND_INTERVAL * 1000; + arp(fd, &saddr, + ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + } + break; + case DEFEND: + // Well, we tried. Start over (on conflict). + if (source_ip_conflict) { + state = PROBE; + VDBG("defend conflict -- starting over\n"); + ready = 0; + run(script, "deconfig", intf, &ip); + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + default: + // Invalid, should never happen. Restart the whole protocol. + VDBG("invalid state -- starting over\n"); + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch state + + break; // case 1 (packets arriving) + default: + why = "poll"; + goto bad; + } // switch poll + } +bad: + bb_perror_msg("%s, %s", intf, why); + return EXIT_FAILURE; +} |