diff options
author | Bjørn Mork <bjorn@mork.no> | 2015-05-15 10:20:47 +0200 |
---|---|---|
committer | Bjørn Mork <bjorn@mork.no> | 2015-05-15 10:20:47 +0200 |
commit | 73b16af8feec390afbabd9356d6e5e83c0390838 (patch) | |
tree | 3730020ba2f9caeb9d7815a975af51830b51ce11 /networking |
busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2busybox-1.13.3
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'networking')
87 files changed, 31977 insertions, 0 deletions
diff --git a/networking/Config.in b/networking/Config.in new file mode 100644 index 0000000..95f8942 --- /dev/null +++ b/networking/Config.in @@ -0,0 +1,921 @@ +# +# 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 FEATURE_PREFER_IPV4_ADDRESS + bool "Preferentially use IPv4 addresses from DNS queries" + default y + depends on FEATURE_IPV6 + help + Use IPv4 address of network host if it has one. + + If this option is off, the first returned address will be used. + This may cause problems when your DNS server is IPv6-capable and + is returning IPv6 host addresses too. If IPv6 address + precedes IPv4 one in DNS reply, busybox network applets + (e.g. wget) will use IPv6 address. On an IPv6-incapable host + or network applets will fail to connect to the host + using IPv6 address. + +config VERBOSE_RESOLUTION_ERRORS + bool "Verbose resolution errors" + default n + help + Enable if you are not satisfied with simplistic + "can't resolve 'hostname.com'" and want to know more. + This may increase size of your executable a bit. + +config ARP + bool "arp" + default n + help + Manipulate the system ARP cache. + +config ARPING + bool "arping" + default n + help + Ping hosts by ARP packets. + +config BRCTL + bool "brctl" + default n + help + Manage ethernet bridges. + Supports addbr/delbr and addif/delif. + +config FEATURE_BRCTL_FANCY + bool "Fancy options" + default n + depends on BRCTL + help + Add support for extended option like: + setageing, setfd, sethello, setmaxage, + setpathcost, setportprio, setbridgeprio, + stp + This adds about 600 bytes. + +config FEATURE_BRCTL_SHOW + bool "Support show, showmac and showstp" + default n + depends on BRCTL && FEATURE_BRCTL_FANCY + help + Add support for option which prints the current config: + showmacs, showstp, show + +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_RANGES + bool "Support 'Ranges:' header" + default n + depends on HTTPD + help + Makes httpd emit "Accept-Ranges: bytes" header and understand + "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted + downloads, seeking in multimedia players etc. + +config FEATURE_HTTPD_USE_SENDFILE + bool "Use sendfile system call" + default n + depends on HTTPD + help + When enabled, httpd will use the kernel sendfile() function + instead of read/write loop. + +config FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + bool "Support reloading of global config file on HUP signal" + default n + depends on HTTPD + help + This option enables processing of SIGHUP to reload cached + configuration settings. + +config FEATURE_HTTPD_SETUID + bool "Enable -u <user> option" + default n + depends on HTTPD + 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 "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 additional line in your httpd + config file: + *.php:/path/to/your/php + +config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + bool "Set 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 -e option (useful for CGIs written as shell scripts)" + default y + depends on HTTPD + help + This option allows html encoding of arbitrary strings for display + by the browser. Output goes to stdout. + For example, httpd -e "<Hello World>" produces + "<Hello World>". + +config FEATURE_HTTPD_ERROR_PAGES + bool "Support for custom error pages" + default n + depends on HTTPD + help + This option allows you to define custom error pages in + the configuration file instead of the default HTTP status + error pages. For instance, if you add the line: + E404:/path/e404.html + in the config file, the server will respond the specified + '/path/e404.html' file instead of the terse '404 NOT FOUND' + message. + +config FEATURE_HTTPD_PROXY + bool "Support for reverse proxy" + default n + depends on HTTPD + help + This option allows you to define URLs that will be forwarded + to another HTTP server. To setup add the following line to the + configuration file + P:/url/:http://hostname[:port]/new/path/ + Then a request to /url/myfile will be forwarded to + http://hostname[:port]/new/path/myfile. + +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 IFENSLAVE + bool "ifenslave" + default n + help + Userspace application to bind several interfaces + to a logical interface (use with kernel bonding driver). + +config IFUPDOWN + bool "ifupdown" + default n + 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", "route" and "run-parts" or the "ip" command, either + via busybox or via standalone utilities. + +config IFUPDOWN_IFSTATE_PATH + string "Absolute path to ifstate file" + default "/var/run/ifstate" + depends on IFUPDOWN + help + ifupdown keeps state information in a file called ifstate. + Typically it is located in /var/run/ifstate, however + some distributions tend to put it in other places + (debian, for example, uses /etc/network/run/ifstate). + This config option defines location of ifstate. + +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 "Support for IPv4" + default y + depends on IFUPDOWN + help + If you want ifup/ifdown to talk IPv4, leave this on. + +config FEATURE_IFUPDOWN_IPV6 + bool "Support for IPv6" + default n + depends on IFUPDOWN && FEATURE_IPV6 + help + If you need support for IPv6, turn this option on. + +### UNUSED +###config FEATURE_IFUPDOWN_IPX +### bool "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 FEATURE_IFUPDOWN_EXTERNAL_DHCP + bool "Support for external dhcp clients" + default n + depends on IFUPDOWN + help + This enables support for the external dhcp clients. Clients are + tried in the following order: dhcpcd, dhclient, pump and udhcpc. + Otherwise, if udhcpc applet is enabled, it is used. + Otherwise, ifup/ifdown will have no support for DHCP. + +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 + select 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 + ip rule -> iprule + + Say N unless you desparately need the short form of the ip + object commands. + +config FEATURE_IP_RARE_PROTOCOLS + bool "Support displaying rarely used link types" + default n + depends on IP + help + If you are not going to use links of type "frad", "econet", + "bif" etc, you probably don't need to enable this. + Ethernet, wireless, infrared, ppp/slip, ip tunnelling + link types are supported without this option selected. + +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: IFNAMSIZ = 16 + File fields are separated by space or tab. + File format: + # Comment + new_interface_name XX:XX:XX:XX:XX:XX + +config FEATURE_NAMEIF_EXTENDED + bool "Extended nameif" + default n + depends on NAMEIF + help + This extends the nameif syntax to support the bus_info and driver + checks. The syntax is compatible to the normal nameif. + File format: + new_interface_name driver=asix bus=usb-0000:00:08.2-3 + new_interface_name bus=usb-0000:00:08.2-3 00:80:C8:38:91:B5 + new_interface_name mac=00:80:C8:38:91:B5 + new_interface_name 00:80:C8:38:91:B5 + +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 (-l)" + 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 FEATURE_NETSTAT_WIDE + bool "Enable wide netstat output" + default n + depends on NETSTAT + help + Add support for wide columns. Useful when displaying IPv6 addresses + (-W option). + +config FEATURE_NETSTAT_PRG + bool "Enable PID/Program name output" + default n + depends on NETSTAT + help + Add support for -p flag to print out PID and program name. + +700 bytes of code. + +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 PING6 + bool "ping6" + default n + depends on FEATURE_IPV6 && PING + help + This will give you a ping that can talk IPv6. + +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 PSCAN + bool "pscan" + default n + help + Simple network port scanner. + +config ROUTE + bool "route" + default n + help + Route displays or manipulates the kernel's IP routing tables. + +config SLATTACH + bool "slattach" + default n + help + slattach is a small utility to attach network interfaces to serial + lines. + +#config TC +# bool "tc" +# default n +# help +# show / manipulate traffic control settings +# +#config FEATURE_TC_INGRESS +# def_bool n +# depends on TC + +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 TFTPD + bool "tftpd" + default n + help + This enables the Trivial File Transfer Protocol server program. + It expects that stdin is a datagram socket and a packet + is already pending on it. It will exit after one transfer. + In other words: it should be run from inetd in nowait mode, + or from udpsvd. Example: "udpsvd -E 0 69 tftpd DIR" + +config FEATURE_TFTP_GET + bool "Enable \"get\" command" + default y + depends on TFTP || TFTPD + help + Add support for the GET command within the TFTP client. This allows + a client to retrieve a file from a TFTP server. + Also enable upload support in tftpd, if tftpd is selected. + +config FEATURE_TFTP_PUT + bool "Enable \"put\" command" + default y + depends on TFTP || TFTPD + help + Add support for the PUT command within the TFTP client. This allows + a client to transfer a file to a TFTP server. + Also enable download support in tftpd, if tftpd is selected. + +config FEATURE_TFTP_BLOCKSIZE + bool "Enable \"blksize\" protocol option" + default n + depends on TFTP || TFTPD + help + Allow tftp to specify block size, and tftpd to understand + "blksize" option. + +config TFTP_DEBUG + 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_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. + +config TCPSVD + bool "tcpsvd" + default n + help + tcpsvd listens on a TCP port and runs a program for each new + connection. + +config UDPSVD + bool "udpsvd" + default n + help + udpsvd listens on an UDP port and runs a program for each new + connection. + +endmenu diff --git a/networking/Kbuild b/networking/Kbuild new file mode 100644 index 0000000..63d0745 --- /dev/null +++ b/networking/Kbuild @@ -0,0 +1,44 @@ +# 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_ARP) += arp.o interface.o +lib-$(CONFIG_ARPING) += arping.o +lib-$(CONFIG_BRCTL) += brctl.o +lib-$(CONFIG_DNSD) += dnsd.o +lib-$(CONFIG_ETHER_WAKE) += ether-wake.o +lib-$(CONFIG_FAKEIDENTD) += isrv_identd.o isrv.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_IFENSLAVE) += ifenslave.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_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) += ping.o +lib-$(CONFIG_PSCAN) += pscan.o +lib-$(CONFIG_ROUTE) += route.o +lib-$(CONFIG_SLATTACH) += slattach.o +lib-$(CONFIG_TC) += tc.o +lib-$(CONFIG_TELNET) += telnet.o +lib-$(CONFIG_TELNETD) += telnetd.o +lib-$(CONFIG_TFTP) += tftp.o +lib-$(CONFIG_TFTPD) += tftp.o +lib-$(CONFIG_TRACEROUTE) += traceroute.o +lib-$(CONFIG_VCONFIG) += vconfig.o +lib-$(CONFIG_WGET) += wget.o +lib-$(CONFIG_ZCIP) += zcip.o + +lib-$(CONFIG_TCPSVD) += tcpudp.o tcpudp_perhost.o +lib-$(CONFIG_UDPSVD) += tcpudp.o tcpudp_perhost.o diff --git a/networking/arp.c b/networking/arp.c new file mode 100644 index 0000000..e2c5bbb --- /dev/null +++ b/networking/arp.c @@ -0,0 +1,497 @@ +/* vi: set sw=4 ts=4: */ +/* + * arp.c - Manipulate the system ARP cache + * + * 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. + * + * Author: Fred N. van Kempen, <waltje at uwalt.nl.mugnet.org> + * Busybox port: Paul van Gool <pvangool at mimotech.com> + * + * modified for getopt32 by Arne Bernin <arne [at] alamut.de> + */ + +#include "libbb.h" +#include "inet_common.h" + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/ether.h> +#include <netpacket/packet.h> + +#define DEBUG 0 + +#define DFLT_AF "inet" +#define DFLT_HW "ether" + +#define ARP_OPT_A (0x1) +#define ARP_OPT_p (0x2) +#define ARP_OPT_H (0x4) +#define ARP_OPT_t (0x8) +#define ARP_OPT_i (0x10) +#define ARP_OPT_a (0x20) +#define ARP_OPT_d (0x40) +#define ARP_OPT_n (0x80) /* do not resolve addresses */ +#define ARP_OPT_D (0x100) /* HW-address is devicename */ +#define ARP_OPT_s (0x200) +#define ARP_OPT_v (0x400 * DEBUG) /* debugging output flag */ + + +static const struct aftype *ap; /* current address family */ +static const struct hwtype *hw; /* current hardware type */ +static int sockfd; /* active socket descriptor */ +static smallint hw_set; /* flag if hw-type was set (-H) */ +static const char *device = ""; /* current device */ + +static const char options[] ALIGN1 = + "pub\0" + "priv\0" + "temp\0" + "trail\0" + "dontpub\0" + "auto\0" + "dev\0" + "netmask\0"; + +/* Delete an entry from the ARP cache. */ +/* Called only from main, once */ +static int arp_del(char **args) +{ + char *host; + struct arpreq req; + struct sockaddr sa; + int flags = 0; + int err; + + memset(&req, 0, sizeof(req)); + + /* Resolve the host name. */ + host = *args; + if (ap->input(host, &sa) < 0) { + bb_herror_msg_and_die("%s", host); + } + + /* If a host has more than one address, use the correct one! */ + memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr)); + + if (hw_set) + req.arp_ha.sa_family = hw->type; + + req.arp_flags = ATF_PERM; + args++; + while (*args != NULL) { + switch (index_in_strings(options, *args)) { + case 0: /* "pub" */ + flags |= 1; + args++; + break; + case 1: /* "priv" */ + flags |= 2; + args++; + break; + case 2: /* "temp" */ + req.arp_flags &= ~ATF_PERM; + args++; + break; + case 3: /* "trail" */ + req.arp_flags |= ATF_USETRAILERS; + args++; + break; + case 4: /* "dontpub" */ +#ifdef HAVE_ATF_DONTPUB + req.arp_flags |= ATF_DONTPUB; +#else + bb_error_msg("feature ATF_DONTPUB is not supported"); +#endif + args++; + break; + case 5: /* "auto" */ +#ifdef HAVE_ATF_MAGIC + req.arp_flags |= ATF_MAGIC; +#else + bb_error_msg("feature ATF_MAGIC is not supported"); +#endif + args++; + break; + case 6: /* "dev" */ + if (*++args == NULL) + bb_show_usage(); + device = *args; + args++; + break; + case 7: /* "netmask" */ + if (*++args == NULL) + bb_show_usage(); + if (strcmp(*args, "255.255.255.255") != 0) { + host = *args; + if (ap->input(host, &sa) < 0) { + bb_herror_msg_and_die("%s", host); + } + memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr)); + req.arp_flags |= ATF_NETMASK; + } + args++; + break; + default: + bb_show_usage(); + break; + } + } + if (flags == 0) + flags = 3; + + strncpy(req.arp_dev, device, sizeof(req.arp_dev)); + + err = -1; + + /* Call the kernel. */ + if (flags & 2) { + if (option_mask32 & ARP_OPT_v) + bb_error_msg("SIOCDARP(nopub)"); + err = ioctl(sockfd, SIOCDARP, &req); + if (err < 0) { + if (errno == ENXIO) { + if (flags & 1) + goto nopub; + printf("No ARP entry for %s\n", host); + return -1; + } + bb_perror_msg_and_die("SIOCDARP(priv)"); + } + } + if ((flags & 1) && err) { + nopub: + req.arp_flags |= ATF_PUBL; + if (option_mask32 & ARP_OPT_v) + bb_error_msg("SIOCDARP(pub)"); + if (ioctl(sockfd, SIOCDARP, &req) < 0) { + if (errno == ENXIO) { + printf("No ARP entry for %s\n", host); + return -1; + } + bb_perror_msg_and_die("SIOCDARP(pub)"); + } + } + return 0; +} + +/* Get the hardware address to a specified interface name */ +static void arp_getdevhw(char *ifname, struct sockaddr *sa, + const struct hwtype *hwt) +{ + struct ifreq ifr; + const struct hwtype *xhw; + + strcpy(ifr.ifr_name, ifname); + ioctl_or_perror_and_die(sockfd, SIOCGIFHWADDR, &ifr, + "cant get HW-Address for '%s'", ifname); + if (hwt && (ifr.ifr_hwaddr.sa_family != hw->type)) { + bb_error_msg_and_die("protocol type mismatch"); + } + memcpy(sa, &(ifr.ifr_hwaddr), sizeof(struct sockaddr)); + + if (option_mask32 & ARP_OPT_v) { + xhw = get_hwntype(ifr.ifr_hwaddr.sa_family); + if (!xhw || !xhw->print) { + xhw = get_hwntype(-1); + } + bb_error_msg("device '%s' has HW address %s '%s'", + ifname, xhw->name, + xhw->print((unsigned char *) &ifr.ifr_hwaddr.sa_data)); + } +} + +/* Set an entry in the ARP cache. */ +/* Called only from main, once */ +static int arp_set(char **args) +{ + char *host; + struct arpreq req; + struct sockaddr sa; + int flags; + + memset(&req, 0, sizeof(req)); + + host = *args++; + if (ap->input(host, &sa) < 0) { + bb_herror_msg_and_die("%s", host); + } + /* If a host has more than one address, use the correct one! */ + memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr)); + + /* Fetch the hardware address. */ + if (*args == NULL) { + bb_error_msg_and_die("need hardware address"); + } + if (option_mask32 & ARP_OPT_D) { + arp_getdevhw(*args++, &req.arp_ha, hw_set ? hw : NULL); + } else { + if (hw->input(*args++, &req.arp_ha) < 0) { + bb_error_msg_and_die("invalid hardware address"); + } + } + + /* Check out any modifiers. */ + flags = ATF_PERM | ATF_COM; + while (*args != NULL) { + switch (index_in_strings(options, *args)) { + case 0: /* "pub" */ + flags |= ATF_PUBL; + args++; + break; + case 1: /* "priv" */ + flags &= ~ATF_PUBL; + args++; + break; + case 2: /* "temp" */ + flags &= ~ATF_PERM; + args++; + break; + case 3: /* "trail" */ + flags |= ATF_USETRAILERS; + args++; + break; + case 4: /* "dontpub" */ +#ifdef HAVE_ATF_DONTPUB + flags |= ATF_DONTPUB; +#else + bb_error_msg("feature ATF_DONTPUB is not supported"); +#endif + args++; + break; + case 5: /* "auto" */ +#ifdef HAVE_ATF_MAGIC + flags |= ATF_MAGIC; +#else + bb_error_msg("feature ATF_MAGIC is not supported"); +#endif + args++; + break; + case 6: /* "dev" */ + if (*++args == NULL) + bb_show_usage(); + device = *args; + args++; + break; + case 7: /* "netmask" */ + if (*++args == NULL) + bb_show_usage(); + if (strcmp(*args, "255.255.255.255") != 0) { + host = *args; + if (ap->input(host, &sa) < 0) { + bb_herror_msg_and_die("%s", host); + } + memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr)); + flags |= ATF_NETMASK; + } + args++; + break; + default: + bb_show_usage(); + break; + } + } + + /* Fill in the remainder of the request. */ + req.arp_flags = flags; + + strncpy(req.arp_dev, device, sizeof(req.arp_dev)); + + /* Call the kernel. */ + if (option_mask32 & ARP_OPT_v) + bb_error_msg("SIOCSARP()"); + xioctl(sockfd, SIOCSARP, &req); + return 0; +} + + +/* Print the contents of an ARP request block. */ +static void +arp_disp(const char *name, char *ip, int type, int arp_flags, + char *hwa, char *mask, char *dev) +{ + static const int arp_masks[] = { + ATF_PERM, ATF_PUBL, +#ifdef HAVE_ATF_MAGIC + ATF_MAGIC, +#endif +#ifdef HAVE_ATF_DONTPUB + ATF_DONTPUB, +#endif + ATF_USETRAILERS, + }; + static const char arp_labels[] ALIGN1 = "PERM\0""PUP\0" +#ifdef HAVE_ATF_MAGIC + "AUTO\0" +#endif +#ifdef HAVE_ATF_DONTPUB + "DONTPUB\0" +#endif + "TRAIL\0" + ; + + const struct hwtype *xhw; + + xhw = get_hwntype(type); + if (xhw == NULL) + xhw = get_hwtype(DFLT_HW); + + printf("%s (%s) at ", name, ip); + + if (!(arp_flags & ATF_COM)) { + if (arp_flags & ATF_PUBL) + printf("* "); + else + printf("<incomplete> "); + } else { + printf("%s [%s] ", hwa, xhw->name); + } + + if (arp_flags & ATF_NETMASK) + printf("netmask %s ", mask); + + print_flags_separated(arp_masks, arp_labels, arp_flags, " "); + printf(" on %s\n", dev); +} + +/* Display the contents of the ARP cache in the kernel. */ +/* Called only from main, once */ +static int arp_show(char *name) +{ + const char *host; + const char *hostname; + FILE *fp; + struct sockaddr sa; + int type, flags; + int num; + unsigned entries = 0, shown = 0; + char ip[128]; + char hwa[128]; + char mask[128]; + char line[128]; + char dev[128]; + + host = NULL; + if (name != NULL) { + /* Resolve the host name. */ + if (ap->input(name, &sa) < 0) { + bb_herror_msg_and_die("%s", name); + } + host = xstrdup(ap->sprint(&sa, 1)); + } + fp = xfopen_for_read("/proc/net/arp"); + /* Bypass header -- read one line */ + fgets(line, sizeof(line), fp); + + /* Read the ARP cache entries. */ + while (fgets(line, sizeof(line), fp)) { + + mask[0] = '-'; mask[1] = '\0'; + dev[0] = '-'; dev[1] = '\0'; + /* All these strings can't overflow + * because fgets above reads limited amount of data */ + num = sscanf(line, "%s 0x%x 0x%x %s %s %s\n", + ip, &type, &flags, hwa, mask, dev); + if (num < 4) + break; + + entries++; + /* if the user specified hw-type differs, skip it */ + if (hw_set && (type != hw->type)) + continue; + + /* if the user specified address differs, skip it */ + if (host && strcmp(ip, host) != 0) + continue; + + /* if the user specified device differs, skip it */ + if (device[0] && strcmp(dev, device) != 0) + continue; + + shown++; + /* This IS ugly but it works -be */ + hostname = "?"; + if (!(option_mask32 & ARP_OPT_n)) { + if (ap->input(ip, &sa) < 0) + hostname = ip; + else + hostname = ap->sprint(&sa, (option_mask32 & ARP_OPT_n) | 0x8000); + if (strcmp(hostname, ip) == 0) + hostname = "?"; + } + + arp_disp(hostname, ip, type, flags, hwa, mask, dev); + } + if (option_mask32 & ARP_OPT_v) + printf("Entries: %d\tSkipped: %d\tFound: %d\n", + entries, entries - shown, shown); + + if (!shown) { + if (hw_set || host || device[0]) + printf("No match found in %d entries\n", entries); + } + if (ENABLE_FEATURE_CLEAN_UP) { + free((char*)host); + fclose(fp); + } + return 0; +} + +int arp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int arp_main(int argc UNUSED_PARAM, char **argv) +{ + const char *hw_type = "ether"; + const char *protocol; + + /* Initialize variables... */ + ap = get_aftype(DFLT_AF); + if (!ap) + bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family"); + + getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol, + &hw_type, &hw_type, &device); + argv += optind; + if (option_mask32 & ARP_OPT_A || option_mask32 & ARP_OPT_p) { + ap = get_aftype(protocol); + if (ap == NULL) + bb_error_msg_and_die("%s: unknown %s", protocol, "address family"); + } + if (option_mask32 & ARP_OPT_A || option_mask32 & ARP_OPT_p) { + hw = get_hwtype(hw_type); + if (hw == NULL) + bb_error_msg_and_die("%s: unknown %s", hw_type, "hardware type"); + hw_set = 1; + } + //if (option_mask32 & ARP_OPT_i)... -i + + if (ap->af != AF_INET) { + bb_error_msg_and_die("%s: kernel only supports 'inet'", ap->name); + } + + /* If no hw type specified get default */ + if (!hw) { + hw = get_hwtype(DFLT_HW); + if (!hw) + bb_error_msg_and_die("%s: %s not supported", DFLT_HW, "hardware type"); + } + + if (hw->alen <= 0) { + bb_error_msg_and_die("%s: %s without ARP support", + hw->name, "hardware type"); + } + sockfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + /* Now see what we have to do here... */ + if (option_mask32 & (ARP_OPT_d|ARP_OPT_s)) { + if (argv[0] == NULL) + bb_error_msg_and_die("need host name"); + if (option_mask32 & ARP_OPT_s) + return arp_set(argv); + return arp_del(argv); + } + //if (option_mask32 & ARP_OPT_a) - default + return arp_show(argv[0]); +} diff --git a/networking/arping.c b/networking/arping.c new file mode 100644 index 0000000..021dc86 --- /dev/null +++ b/networking/arping.c @@ -0,0 +1,401 @@ +/* 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 <arpa/inet.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <netpacket/packet.h> + +#include "libbb.h" + +/* We don't expect to see 1000+ seconds delay, unsigned is enough */ +#define MONOTONIC_US() ((unsigned)monotonic_us()) + +enum { + DAD = 1, + UNSOLICITED = 2, + ADVERT = 4, + QUIET = 8, + QUIT_ON_REPLY = 16, + BCAST_ONLY = 32, + UNICASTING = 64 +}; + +struct globals { + struct in_addr src; + struct in_addr dst; + struct sockaddr_ll me; + struct sockaddr_ll he; + int sock_fd; + + int count; // = -1; + unsigned last; + unsigned timeout_us; + unsigned start; + + unsigned sent; + unsigned brd_sent; + unsigned received; + unsigned brd_recv; + unsigned req_recv; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define src (G.src ) +#define dst (G.dst ) +#define me (G.me ) +#define he (G.he ) +#define sock_fd (G.sock_fd ) +#define count (G.count ) +#define last (G.last ) +#define timeout_us (G.timeout_us) +#define start (G.start ) +#define sent (G.sent ) +#define brd_sent (G.brd_sent ) +#define received (G.received ) +#define brd_recv (G.brd_recv ) +#define req_recv (G.req_recv ) +#define INIT_G() do { \ + count = -1; \ +} while (0) + +// If GNUisms are not available... +//static void *mempcpy(void *_dst, const void *_src, int n) +//{ +// memcpy(_dst, _src, n); +// return (char*)_dst + n; +//} + +static int send_pack(struct in_addr *src_addr, + struct in_addr *dst_addr, struct sockaddr_ll *ME, + struct sockaddr_ll *HE) +{ + int err; + unsigned char buf[256]; + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + + 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 = option_mask32 & ADVERT ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST); + + p = mempcpy(p, &ME->sll_addr, ah->ar_hln); + p = mempcpy(p, src_addr, 4); + + if (option_mask32 & ADVERT) + p = mempcpy(p, &ME->sll_addr, ah->ar_hln); + else + p = mempcpy(p, &HE->sll_addr, ah->ar_hln); + + p = mempcpy(p, dst_addr, 4); + + err = sendto(sock_fd, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE)); + if (err == p - buf) { + last = MONOTONIC_US(); + sent++; + if (!(option_mask32 & UNICASTING)) + brd_sent++; + } + return err; +} + +static void finish(void) NORETURN; +static void finish(void) +{ + if (!(option_mask32 & QUIET)) { + printf("Sent %u probe(s) (%u broadcast(s))\n" + "Received %u repl%s" + " (%u request(s), %u broadcast(s))\n", + sent, brd_sent, + received, (received == 1) ? "ies" : "y", + req_recv, brd_recv); + } + if (option_mask32 & DAD) + exit(!!received); + if (option_mask32 & UNSOLICITED) + exit(EXIT_SUCCESS); + exit(!received); +} + +static void catcher(void) +{ + unsigned now; + + now = MONOTONIC_US(); + if (start == 0) + start = now; + + if (count == 0 || (timeout_us && (now - start) > timeout_us)) + finish(); + + /* count < 0 means "infinite count" */ + if (count > 0) + count--; + + if (last == 0 || (now - last) > 500000) { + send_pack(&src, &dst, &me, &he); + if (count == 0 && (option_mask32 & UNSOLICITED)) + finish(); + } + alarm(1); +} + +static bool 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 false; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY)) + return false; + + /* 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 false; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons(ETH_P_IP) + || (ah->ar_pln != 4) + || (ah->ar_hln != me.sll_halen) + || (len < (int)(sizeof(*ah) + 2 * (4 + ah->ar_hln)))) + return false; + + memcpy(&src_ip, p + ah->ar_hln, 4); + memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4); + + if (dst.s_addr != src_ip.s_addr) + return false; + if (!(option_mask32 & DAD)) { + if ((src.s_addr != dst_ip.s_addr) + || (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln))) + return false; + } 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 ((memcmp(p, &me.sll_addr, me.sll_halen) == 0) + || (src.s_addr && src.s_addr != dst_ip.s_addr)) + return false; + } + if (!(option_mask32 & QUIET)) { + int s_printed = 0; + + printf("%scast re%s from %s [%s]", + FROM->sll_pkttype == PACKET_HOST ? "Uni" : "Broad", + ah->ar_op == htons(ARPOP_REPLY) ? "ply" : "quest", + 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) { + unsigned diff = MONOTONIC_US() - last; + printf(" %u.%03ums\n", diff / 1000, diff % 1000); + } 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 (option_mask32 & QUIT_ON_REPLY) + finish(); + if (!(option_mask32 & BCAST_ONLY)) { + memcpy(he.sll_addr, p, me.sll_halen); + option_mask32 |= UNICASTING; + } + return true; +} + +int arping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int arping_main(int argc UNUSED_PARAM, char **argv) +{ + const char *device = "eth0"; + char *source = NULL; + char *target; + unsigned char *packet; + char *err_str; + + INIT_G(); + + sock_fd = xsocket(AF_PACKET, SOCK_DGRAM, 0); + + // Drop suid root privileges + // Need to remove SUID_NEVER from applets.h for this to work + //xsetuid(getuid()); + + err_str = xasprintf("interface %s %%s", device); + { + unsigned opt; + char *str_timeout; + + /* Dad also sets quit_on_reply. + * Advert also sets unsolicited. + */ + opt_complementary = "=1:Df:AU:c+"; + opt = getopt32(argv, "DUAqfbc:w:I:s:", + &count, &str_timeout, &device, &source); + if (opt & 0x80) /* -w: timeout */ + timeout_us = xatou_range(str_timeout, 0, INT_MAX/2000000) * 1000000 + 500000; + //if (opt & 0x200) /* -s: source */ + option_mask32 &= 0x3f; /* set respective flags */ + } + + target = argv[optind]; + + xfunc_error_retval = 2; + + { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name) - 1); + /* We use ifr.ifr_name in error msg so that problem + * with truncated name will be visible */ + ioctl_or_perror_and_die(sock_fd, SIOCGIFINDEX, &ifr, err_str, "not found"); + me.sll_ifindex = ifr.ifr_ifindex; + + xioctl(sock_fd, SIOCGIFFLAGS, (char *) &ifr); + + if (!(ifr.ifr_flags & IFF_UP)) { + bb_error_msg_and_die(err_str, "is down"); + } + if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) { + bb_error_msg(err_str, "is not ARPable"); + return (option_mask32 & DAD ? 0 : 2); + } + } + + /* if (!inet_aton(target, &dst)) - not needed */ { + len_and_sockaddr *lsa; + lsa = xhost_and_af2sockaddr(target, 0, AF_INET); + memcpy(&dst, &lsa->u.sin.sin_addr.s_addr, 4); + if (ENABLE_FEATURE_CLEAN_UP) + free(lsa); + } + + if (source && !inet_aton(source, &src)) { + bb_error_msg_and_die("invalid source address %s", source); + } + + if ((option_mask32 & (DAD|UNSOLICITED)) == UNSOLICITED && src.s_addr == 0) + src = dst; + + if (!(option_mask32 & DAD) || src.s_addr) { + struct sockaddr_in saddr; + int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + setsockopt_bindtodevice(probe_fd, device); + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if (src.s_addr) { + /* Check that this is indeed our IP */ + saddr.sin_addr = src; + xbind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)); + } else { /* !(option_mask32 & DAD) case */ + /* Find IP address on this iface */ + socklen_t alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = dst; + + if (setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, &const_int_1, sizeof(const_int_1)) == -1) + bb_perror_msg("setsockopt(SO_DONTROUTE)"); + xconnect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)); + if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == -1) { + bb_perror_msg_and_die("getsockname"); + } + if (saddr.sin_family != AF_INET) + bb_error_msg_and_die("no IP address configured"); + src = saddr.sin_addr; + } + close(probe_fd); + } + + me.sll_family = AF_PACKET; + //me.sll_ifindex = ifindex; - done before + me.sll_protocol = htons(ETH_P_ARP); + xbind(sock_fd, (struct sockaddr *) &me, sizeof(me)); + + { + socklen_t alen = sizeof(me); + + if (getsockname(sock_fd, (struct sockaddr *) &me, &alen) == -1) { + bb_perror_msg_and_die("getsockname"); + } + } + if (me.sll_halen == 0) { + bb_error_msg(err_str, "is not ARPable (no ll address)"); + return (option_mask32 & DAD ? 0 : 2); + } + he = me; + memset(he.sll_addr, -1, he.sll_halen); + + if (!(option_mask32 & QUIET)) { + /* inet_ntoa uses static storage, can't use in same printf */ + printf("ARPING to %s", inet_ntoa(dst)); + printf(" from %s via %s\n", inet_ntoa(src), device); + } + + signal_SA_RESTART_empty_mask(SIGINT, (void (*)(int))finish); + signal_SA_RESTART_empty_mask(SIGALRM, (void (*)(int))catcher); + + catcher(); + + packet = xmalloc(4096); + while (1) { + sigset_t sset, osset; + struct sockaddr_ll from; + socklen_t alen = sizeof(from); + int cc; + + cc = recvfrom(sock_fd, packet, 4096, 0, (struct sockaddr *) &from, &alen); + if (cc < 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); + } +} diff --git a/networking/brctl.c b/networking/brctl.c new file mode 100644 index 0000000..8475179 --- /dev/null +++ b/networking/brctl.c @@ -0,0 +1,272 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small implementation of brctl for busybox. + * + * Copyright (C) 2008 by Bernhard Reutner-Fischer + * + * Some helper functions from bridge-utils are + * Copyright (C) 2000 Lennert Buytenhek + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +/* This applet currently uses only the ioctl interface and no sysfs at all. + * At the time of this writing this was considered a feature. + */ +#include "libbb.h" +#include <linux/sockios.h> +#include <net/if.h> + +/* Maximum number of ports supported per bridge interface. */ +#ifndef MAX_PORTS +#define MAX_PORTS 32 +#endif + +/* Use internal number parsing and not the "exact" conversion. */ +/* #define BRCTL_USE_INTERNAL 0 */ /* use exact conversion */ +#define BRCTL_USE_INTERNAL 1 + +#if ENABLE_FEATURE_BRCTL_FANCY +#include <linux/if_bridge.h> + +/* FIXME: These 4 funcs are not really clean and could be improved */ +static ALWAYS_INLINE void strtotimeval(struct timeval *tv, + const char *time_str) +{ + double secs; +#if BRCTL_USE_INTERNAL + secs = /*bb_*/strtod(time_str, NULL); + if (!secs) +#else + if (sscanf(time_str, "%lf", &secs) != 1) +#endif + bb_error_msg_and_die (bb_msg_invalid_arg, time_str, "timespec"); + tv->tv_sec = secs; + tv->tv_usec = 1000000 * (secs - tv->tv_sec); +} + +static ALWAYS_INLINE unsigned long __tv_to_jiffies(const struct timeval *tv) +{ + unsigned long long jif; + + jif = 1000000ULL * tv->tv_sec + tv->tv_usec; + + return jif/10000; +} +# if 0 +static void __jiffies_to_tv(struct timeval *tv, unsigned long jiffies) +{ + unsigned long long tvusec; + + tvusec = 10000ULL*jiffies; + tv->tv_sec = tvusec/1000000; + tv->tv_usec = tvusec - 1000000 * tv->tv_sec; +} +# endif +static unsigned long str_to_jiffies(const char *time_str) +{ + struct timeval tv; + strtotimeval(&tv, time_str); + return __tv_to_jiffies(&tv); +} + +static void arm_ioctl(unsigned long *args, + unsigned long arg0, unsigned long arg1, unsigned long arg2) +{ + args[0] = arg0; + args[1] = arg1; + args[2] = arg2; + args[3] = 0; +} +#endif + + +int brctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int brctl_main(int argc UNUSED_PARAM, char **argv) +{ + static const char keywords[] ALIGN1 = + "addbr\0" "delbr\0" "addif\0" "delif\0" + USE_FEATURE_BRCTL_FANCY( + "stp\0" + "setageing\0" "setfd\0" "sethello\0" "setmaxage\0" + "setpathcost\0" "setportprio\0" "setbridgeprio\0" + ) + USE_FEATURE_BRCTL_SHOW("showmacs\0" "show\0"); + + enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif + USE_FEATURE_BRCTL_FANCY(, + ARG_stp, + ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage, + ARG_setpathcost, ARG_setportprio, ARG_setbridgeprio + ) + USE_FEATURE_BRCTL_SHOW(, ARG_showmacs, ARG_show) + }; + + int fd; + smallint key; + struct ifreq ifr; + char *br, *brif; + + argv++; + while (*argv) { +#if ENABLE_FEATURE_BRCTL_FANCY + int ifidx[MAX_PORTS]; + unsigned long args[4]; + ifr.ifr_data = (char *) &args; +#endif + + key = index_in_strings(keywords, *argv); + if (key == -1) /* no match found in keywords array, bail out. */ + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name); + argv++; + fd = xsocket(AF_INET, SOCK_STREAM, 0); + +#if ENABLE_FEATURE_BRCTL_SHOW + if (key == ARG_show) { /* show */ + char brname[IFNAMSIZ]; + int bridx[MAX_PORTS]; + int i, num; + arm_ioctl(args, BRCTL_GET_BRIDGES, + (unsigned long) bridx, MAX_PORTS); + num = xioctl(fd, SIOCGIFBR, args); + printf("bridge name\tbridge id\t\tSTP enabled\tinterfaces\n"); + for (i = 0; i < num; i++) { + char ifname[IFNAMSIZ]; + int j, tabs; + struct __bridge_info bi; + unsigned char *x; + + if (!if_indextoname(bridx[i], brname)) + bb_perror_msg_and_die("can't get bridge name for index %d", i); + strncpy(ifr.ifr_name, brname, IFNAMSIZ); + + arm_ioctl(args, BRCTL_GET_BRIDGE_INFO, + (unsigned long) &bi, 0); + xioctl(fd, SIOCDEVPRIVATE, &ifr); + printf("%s\t\t", brname); + + /* print bridge id */ + x = (unsigned char *) &bi.bridge_id; + for (j = 0; j < 8; j++) { + printf("%.2x", x[j]); + if (j == 1) + bb_putchar('.'); + } + printf(bi.stp_enabled ? "\tyes" : "\tno"); + + /* print interface list */ + arm_ioctl(args, BRCTL_GET_PORT_LIST, + (unsigned long) ifidx, MAX_PORTS); + xioctl(fd, SIOCDEVPRIVATE, &ifr); + tabs = 0; + for (j = 0; j < MAX_PORTS; j++) { + if (!ifidx[j]) + continue; + if (!if_indextoname(ifidx[j], ifname)) + bb_perror_msg_and_die("can't get interface name for index %d", j); + if (tabs) + printf("\t\t\t\t\t"); + else + tabs = 1; + printf("\t\t%s\n", ifname); + } + if (!tabs) /* bridge has no interfaces */ + bb_putchar('\n'); + } + goto done; + } +#endif + + if (!*argv) /* all but 'show' need at least one argument */ + bb_show_usage(); + + br = *argv++; + + if (key == ARG_addbr || key == ARG_delbr) { /* addbr or delbr */ + ioctl_or_perror_and_die(fd, + key == ARG_addbr ? SIOCBRADDBR : SIOCBRDELBR, + br, "bridge %s", br); + goto done; + } + + if (!*argv) /* all but 'addif/delif' need at least two arguments */ + bb_show_usage(); + + strncpy(ifr.ifr_name, br, IFNAMSIZ); + if (key == ARG_addif || key == ARG_delif) { /* addif or delif */ + brif = *argv; + ifr.ifr_ifindex = if_nametoindex(brif); + if (!ifr.ifr_ifindex) { + bb_perror_msg_and_die("iface %s", brif); + } + ioctl_or_perror_and_die(fd, + key == ARG_addif ? SIOCBRADDIF : SIOCBRDELIF, + &ifr, "bridge %s", br); + goto done_next_argv; + } +#if ENABLE_FEATURE_BRCTL_FANCY + if (key == ARG_stp) { /* stp */ + /* FIXME: parsing yes/y/on/1 versus no/n/off/0 is too involved */ + arm_ioctl(args, BRCTL_SET_BRIDGE_STP_STATE, + (unsigned)(**argv - '0'), 0); + goto fire; + } + if ((unsigned)(key - ARG_setageing) < 4) { /* time related ops */ + static const uint8_t ops[] ALIGN1 = { + BRCTL_SET_AGEING_TIME, /* ARG_setageing */ + BRCTL_SET_BRIDGE_FORWARD_DELAY, /* ARG_setfd */ + BRCTL_SET_BRIDGE_HELLO_TIME, /* ARG_sethello */ + BRCTL_SET_BRIDGE_MAX_AGE /* ARG_setmaxage */ + }; + arm_ioctl(args, ops[key - ARG_setageing], str_to_jiffies(*argv), 0); + goto fire; + } + if (key == ARG_setpathcost + || key == ARG_setportprio + || key == ARG_setbridgeprio + ) { + static const uint8_t ops[] ALIGN1 = { + BRCTL_SET_PATH_COST, /* ARG_setpathcost */ + BRCTL_SET_PORT_PRIORITY, /* ARG_setportprio */ + BRCTL_SET_BRIDGE_PRIORITY /* ARG_setbridgeprio */ + }; + int port = -1; + unsigned arg1, arg2; + + if (key != ARG_setbridgeprio) { + /* get portnum */ + unsigned i; + + port = if_nametoindex(*argv++); + if (!port) + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, "port"); + memset(ifidx, 0, sizeof ifidx); + arm_ioctl(args, BRCTL_GET_PORT_LIST, (unsigned long)ifidx, + MAX_PORTS); + xioctl(fd, SIOCDEVPRIVATE, &ifr); + for (i = 0; i < MAX_PORTS; i++) { + if (ifidx[i] == port) { + port = i; + break; + } + } + } + arg1 = port; + arg2 = xatoi_u(*argv); + if (key == ARG_setbridgeprio) { + arg1 = arg2; + arg2 = 0; + } + arm_ioctl(args, ops[key - ARG_setpathcost], arg1, arg2); + } + fire: + /* Execute the previously set command */ + xioctl(fd, SIOCDEVPRIVATE, &ifr); +#endif + done_next_argv: + argv++; + done: + close(fd); + } + + return EXIT_SUCCESS; +} diff --git a/networking/dnsd.c b/networking/dnsd.c new file mode 100644 index 0000000..e8dcb40 --- /dev/null +++ b/networking/dnsd.c @@ -0,0 +1,380 @@ +/* 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 "libbb.h" +#include <syslog.h> + +//#define DEBUG 1 +#define DEBUG 0 + +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, + + DEFAULT_TTL = 30, // increase this when not testing? + + REQ_A = 1, + REQ_PTR = 12 +}; + +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; +static uint32_t ttl = DEFAULT_TTL; + +static const char *fileconf = "/etc/dnsd.conf"; + +// Must match getopt32 call +#define OPT_daemon (option_mask32 & 0x10) +#define OPT_verbose (option_mask32 & 0x20) + + +/* + * 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++; + } +} + +/* + * Read hostname/IP records from file + */ +static void dnsentryinit(void) +{ + char *token[2]; + parser_t *parser; + struct dns_entry *m, *prev; + + prev = dnsentry = NULL; + parser = config_open(fileconf); + while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) { + unsigned a, b, c, d; + /* + * Assumes all host names are lower case only + * Hostnames with more than one label are not handled correctly. + * Presently the dot is copied into name without + * converting to a length/string substring for that label. + */ +// if (!token[1] || sscanf(token[1], ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4) + if (sscanf(token[1], ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4) + continue; + + m = xzalloc(sizeof(*m)); + /*m->next = NULL;*/ + sprintf(m->ip, ".%u.%u.%u.%u"+1, a, b, c, d); + sprintf(m->rip, ".%u.%u.%u.%u", d, c, b, a); + undot((uint8_t*)m->rip); + convname(m->name, (uint8_t*)token[0]); + + if (OPT_verbose) + fprintf(stderr, "\tname:%s, ip:%s\n", &(m->name[1]), m->ip); + + if (prev == NULL) + dnsentry = m; + else + prev->next = m; + prev = m; + } + config_close(parser); +} + +/* + * 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 { +#if DEBUG + char *p,*q; + q = (char *)&(qs[1]); + p = &(d->name[1]); + fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d", + __FUNCTION__, (int)strlen(p), (int)(d->name[0]), + p, q, (int)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]) || + (d->name[0] == 1 && d->name[1] == '*')) { + strcpy((char *)as, d->ip); +#if DEBUG + fprintf(stderr, " OK as:%s\n", as); +#endif + return 0; + } + } else if (type == REQ_PTR) { /* search by IP-address */ + if ((d->name[0] != 1 || d->name[1] != '*') && + !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 + */ +static int process_packet(uint8_t *buf) +{ + uint8_t answstr[MAX_NAME_LEN + 1]; + struct dns_head *head; + struct dns_prop *qprop; + uint8_t *from, *answb; + uint16_t outr_rlen; + uint16_t outr_flags; + uint16_t flags; + int lookup_result, type, packet_len; + int querystr_len; + + answstr[0] = '\0'; + + head = (struct dns_head *)buf; + if (head->nquer == 0) { + bb_error_msg("no queries"); + return -1; + } + + if (head->flags & 0x8000) { + bb_error_msg("ignoring response packet"); + return -1; + } + + from = (void *)&head[1]; // start of query string +//FIXME: strlen of untrusted data??! + querystr_len = strlen((char *)from) + 1 + sizeof(struct dns_prop); + answb = from + querystr_len; // where to append answer block + + outr_rlen = 0; + 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 + bb_info_msg("%s", (char *)from); + lookup_result = table_lookup(type, answstr, 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; // NB! its "struct { unsigned __long__ s_addr; }" + uint32_t v32; + if (!inet_aton((char*)answstr, &a)) { //dotted dec to long conv + outr_flags = 1; /* Frmt err */ + goto empty_packet; + } + v32 = a.s_addr; /* in case long != int */ + memcpy(answstr, &v32, 4); + outr_rlen = 4; // uint32_t IP + } else + outr_rlen = strlen((char *)answstr) + 1; // a host name + outr_flags |= 0x0400; /* authority-bit */ + // we have an answer + head->nansw = htons(1); + + // copy query block to answer block + memcpy(answb, from, querystr_len); + answb += querystr_len; + + // and append answer rr +// FIXME: unaligned accesses?? + *(uint32_t *) answb = htonl(ttl); + answb += 4; + *(uint16_t *) answb = htons(outr_rlen); + answb += 2; + memcpy(answb, answstr, outr_rlen); + answb += 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 = 0; + head->nquer = htons(1); + + packet_len = answb - buf; + return packet_len; +} + +/* + * Exit on signal + */ +static void interrupt(int sig) +{ + /* unlink("/var/run/dnsd.lock"); */ + bb_error_msg("interrupt, exiting\n"); + kill_myself_with_sig(sig); +} + +int dnsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int dnsd_main(int argc UNUSED_PARAM, char **argv) +{ + const char *listen_interface = "0.0.0.0"; + char *sttl, *sport; + len_and_sockaddr *lsa, *from, *to; + unsigned lsa_size; + int udps; + uint16_t port = 53; + /* Paranoid sizing: querystring x2 + ttl + outr_rlen + answstr */ + /* I'd rather see process_packet() fixed instead... */ + uint8_t buf[MAX_PACK_LEN * 2 + 4 + 2 + (MAX_NAME_LEN+1)]; + + getopt32(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 + ttl = xatou_range(sttl, 1, 0xffffffff); + if (option_mask32 & 0x8) // -p + port = xatou_range(sport, 1, 0xffff); + + 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) { + bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); + openlog(applet_name, LOG_PID, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + dnsentryinit(); + + signal(SIGINT, interrupt); + bb_signals(0 + /* why? + (1 << SIGPIPE) */ + + (1 << SIGHUP) +#ifdef SIGTSTP + + (1 << SIGTSTP) +#endif +#ifdef SIGURG + + (1 << SIGURG) +#endif + , SIG_IGN); + + lsa = xdotted2sockaddr(listen_interface, port); + udps = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0); + xbind(udps, &lsa->u.sa, lsa->len); + socket_want_pktinfo(udps); /* needed for recv_from_to to work */ + lsa_size = LSA_LEN_SIZE + lsa->len; + from = xzalloc(lsa_size); + to = xzalloc(lsa_size); + + bb_info_msg("Accepting UDP packets on %s", + xmalloc_sockaddr2dotted(&lsa->u.sa)); + + while (1) { + int r; + /* Try to get *DEST* address (to which of our addresses + * this query was directed), and reply from the same address. + * Or else we can exhibit usual UDP ugliness: + * [ip1.multihomed.ip2] <= query to ip1 <= peer + * [ip1.multihomed.ip2] => reply from ip2 => peer (confused) */ + memcpy(to, lsa, lsa_size); + r = recv_from_to(udps, buf, MAX_PACK_LEN + 1, 0, &from->u.sa, &to->u.sa, lsa->len); + if (r < 12 || r > MAX_PACK_LEN) { + bb_error_msg("invalid packet size"); + continue; + } + if (OPT_verbose) + bb_info_msg("Got UDP packet"); + buf[r] = '\0'; /* paranoia */ + r = process_packet(buf); + if (r <= 0) + continue; + send_to_from(udps, buf, r, 0, &from->u.sa, &to->u.sa, lsa->len); + } + return 0; +} diff --git a/networking/ether-wake.c b/networking/ether-wake.c new file mode 100644 index 0000000..a37b6eb --- /dev/null +++ b/networking/ether-wake.c @@ -0,0 +1,276 @@ +/* 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 "libbb.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) bb_putchar('\n'); + } + printf("\n\n"); +} +#else +# define bb_debug_msg(fmt, args...) ((void)0) +# define bb_debug_dump_packet(outpack, pktsize) ((void)0) +#endif + +/* Convert the host ID string to a MAC address. + * The string may be a: + * Host name + * IP address string + * MAC address string +*/ +static 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)); +#endif + } else + bb_show_usage(); +} + +static int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast) +{ + int i; + unsigned char *station_addr = eaddr->ether_addr_octet; + + memset(pkt, 0xff, 6); + if (!broadcast) + memcpy(pkt, station_addr, 6); + pkt += 6; + + memcpy(pkt, station_addr, 6); /* 6 */ + pkt += 6; + + *pkt++ = 0x08; /* 12 */ /* Or 0x0806 for ARP, 0x8035 for RARP */ + *pkt++ = 0x42; /* 13 */ + + memset(pkt, 0xff, 6); /* 14 */ + + for (i = 0; i < 16; ++i) { + pkt += 6; + memcpy(pkt, station_addr, 6); /* 20,26,32,... */ + } + + return 20 + 16*6; /* length of packet */ +} + +static int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd) +{ + unsigned 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 */ +// FIXME: why < 4?? should it be < 6? + if (byte_cnt < 4) + byte_cnt = sscanf(ethoptarg, "%u.%u.%u.%u", + &passwd[0], &passwd[1], &passwd[2], &passwd[3]); + if (byte_cnt < 4) { + bb_error_msg("cannot read Wake-On-LAN pass"); + return 0; + } +// TODO: check invalid numbers >255?? + 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; +} + +int ether_wake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ether_wake_main(int argc UNUSED_PARAM, char **argv) +{ + const char *ifname = "eth0"; + char *pass; + unsigned 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 */ + opt_complementary = "=1"; + flags = getopt32(argv, "bi:p:", &ifname, &pass); + if (flags & 4) /* -p */ + wol_passwd_sz = get_wol_pw(pass, wol_passwd); + flags &= 1; /* we further interested only in -b [bcast] flag */ + + /* create the raw socket */ + s = make_socket(); + + /* now that we have a raw socket we can drop root */ + /* xsetuid(getuid()); - but save on code size... */ + + /* 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)); + ioctl_or_perror_and_die(s, SIOCGIFHWADDR, &if_hwaddr, "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)); + xioctl(s, SIOCGIFINDEX, &ifr); + 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 + xsendto(s, outpack, pktsize, (struct sockaddr *)&whereto, sizeof(whereto)); + if (ENABLE_FEATURE_CLEAN_UP) + close(s); + return EXIT_SUCCESS; +} diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c new file mode 100644 index 0000000..d39b73e --- /dev/null +++ b/networking/ftpgetput.c @@ -0,0 +1,325 @@ +/* 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 + * + * 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 "libbb.h" + +struct globals { + const char *user; + const char *password; + struct len_and_sockaddr *lsa; + FILE *control_stream; + int verbose_flag; + int do_continue; + char buf[1]; /* actually [BUFSZ] */ +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +enum { BUFSZ = COMMON_BUFSIZE - offsetof(struct globals, buf) }; +struct BUG_G_too_big { + char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1]; +}; +#define user (G.user ) +#define password (G.password ) +#define lsa (G.lsa ) +#define control_stream (G.control_stream) +#define verbose_flag (G.verbose_flag ) +#define do_continue (G.do_continue ) +#define buf (G.buf ) +#define INIT_G() do { } while (0) + + +static void ftp_die(const char *msg) NORETURN; +static void ftp_die(const char *msg) +{ + char *cp = buf; /* buf holds peer's response */ + + /* Guard against garbage from remote server */ + while (*cp >= ' ' && *cp < '\x7f') + cp++; + *cp = '\0'; + bb_error_msg_and_die("unexpected server response%s%s: %s", + (msg ? " to " : ""), (msg ? msg : ""), buf); +} + +static int ftpcmd(const char *s1, const char *s2) +{ + unsigned n; + + if (verbose_flag) { + bb_error_msg("cmd %s %s", s1, s2); + } + + if (s1) { + fprintf(control_stream, (s2 ? "%s %s\r\n" : "%s %s\r\n"+3), + s1, s2); + fflush(control_stream); + } + + do { + strcpy(buf, "EOF"); + if (fgets(buf, BUFSZ - 2, control_stream) == NULL) { + ftp_die(NULL); + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + buf[3] = '\0'; + n = xatou(buf); + buf[3] = ' '; + return n; +} + +static void ftp_login(void) +{ + /* Connect to the command socket */ + control_stream = fdopen(xconnect_stream(lsa), "r+"); + if (control_stream == NULL) { + /* fdopen failed - extremely unlikely */ + bb_perror_nomsg_and_die(); + } + + if (ftpcmd(NULL, NULL) != 220) { + ftp_die(NULL); + } + + /* Login to the server */ + switch (ftpcmd("USER", user)) { + case 230: + break; + case 331: + if (ftpcmd("PASS", password) != 230) { + ftp_die("PASS"); + } + break; + default: + ftp_die("USER"); + } + + ftpcmd("TYPE I", NULL); +} + +static int xconnect_ftpdata(void) +{ + char *buf_ptr; + unsigned port_num; + +/* +TODO: PASV command will not work for IPv6. RFC2428 describes +IPv6-capable "extended PASV" - EPSV. + +"EPSV [protocol]" asks server to bind to and listen on a data port +in specified protocol. Protocol is 1 for IPv4, 2 for IPv6. +If not specified, defaults to "same as used for control connection". +If server understood you, it should answer "229 <some text>(|||port|)" +where "|" are literal pipe chars and "port" is ASCII decimal port#. + +There is also an IPv6-capable replacement for PORT (EPRT), +but we don't need that. + +NB: PASV may still work for some servers even over IPv6. +For example, vsftp happily answers +"227 Entering Passive Mode (0,0,0,0,n,n)" and proceeds as usual. + +TODO2: need to stop ignoring IP address in PASV response. +*/ + + if (ftpcmd("PASV", NULL) != 227) { + ftp_die("PASV"); + } + + /* Response is "NNN 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 */ + buf_ptr = strrchr(buf, ')'); + if (buf_ptr) *buf_ptr = '\0'; + + 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; + + set_nport(lsa, htons(port_num)); + return xconnect_stream(lsa); +} + +static int pump_data_and_QUIT(int from, int to) +{ + /* copy the file */ + if (bb_copyfd_eof(from, to) == -1) { + /* error msg is already printed by bb_copyfd_eof */ + return EXIT_FAILURE; + } + + /* close data connection */ + close(from); /* don't know which one is that, so we close both */ + close(to); + + /* does server confirm that transfer is finished? */ + if (ftpcmd(NULL, NULL) != 226) { + ftp_die(NULL); + } + ftpcmd("QUIT", NULL); + + return EXIT_SUCCESS; +} + +#if !ENABLE_FTPGET +int ftp_receive(const char *local_path, char *server_path); +#else +static +int ftp_receive(const char *local_path, char *server_path) +{ + int fd_data; + int fd_local = -1; + off_t beg_range = 0; + + /* connect to the data socket */ + fd_data = xconnect_ftpdata(); + + if (ftpcmd("SIZE", server_path) != 213) { + do_continue = 0; + } + + if (LONE_DASH(local_path)) { + fd_local = STDOUT_FILENO; + do_continue = 0; + } + + if (do_continue) { + struct stat sbuf; + /* lstat would be wrong here! */ + if (stat(local_path, &sbuf) < 0) { + bb_perror_msg_and_die("stat"); + } + 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) != 350) { + do_continue = 0; + } + } + + if (ftpcmd("RETR", server_path) > 150) { + ftp_die("RETR"); + } + + /* create local file _after_ we know that remote file exists */ + if (fd_local == -1) { + fd_local = xopen(local_path, + do_continue ? (O_APPEND | O_WRONLY) + : (O_CREAT | O_TRUNC | O_WRONLY) + ); + } + + return pump_data_and_QUIT(fd_data, fd_local); +} +#endif + +#if !ENABLE_FTPPUT +int ftp_send(const char *server_path, char *local_path); +#else +static +int ftp_send(const char *server_path, char *local_path) +{ + int fd_data; + int fd_local; + int response; + + /* connect to the data socket */ + fd_data = xconnect_ftpdata(); + + /* get the local file */ + fd_local = STDIN_FILENO; + if (NOT_LONE_DASH(local_path)) + fd_local = xopen(local_path, O_RDONLY); + + response = ftpcmd("STOR", server_path); + switch (response) { + case 125: + case 150: + break; + default: + ftp_die("STOR"); + } + + return pump_data_and_QUIT(fd_local, fd_data); +} +#endif + +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS +static const char ftpgetput_longopts[] ALIGN1 = + "continue\0" Required_argument "c" + "verbose\0" No_argument "v" + "username\0" Required_argument "u" + "password\0" Required_argument "p" + "port\0" Required_argument "P" + ; +#endif + +int ftpgetput_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ftpgetput_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned opt; + const char *port = "ftp"; + /* socket to ftp server */ + +#if ENABLE_FTPPUT && !ENABLE_FTPGET +# define ftp_action ftp_send +#elif ENABLE_FTPGET && !ENABLE_FTPPUT +# define ftp_action ftp_receive +#else + int (*ftp_action)(const char *, char *) = ftp_send; + + /* Check to see if the command is ftpget or ftput */ + if (applet_name[3] == 'g') { + ftp_action = ftp_receive; + } +#endif + + INIT_G(); + /* Set default values */ + user = "anonymous"; + password = "busybox@"; + + /* + * Decipher the command line + */ +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS + applet_long_options = ftpgetput_longopts; +#endif + opt_complementary = "=3:vv:cc"; /* must have 3 params; -v and -c count */ + opt = getopt32(argv, "cvu:p:P:", &user, &password, &port, + &verbose_flag, &do_continue); + argv += optind; + + /* 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... */ + lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21)); + if (verbose_flag) { + printf("Connecting to %s (%s)\n", argv[0], + xmalloc_sockaddr2dotted(&lsa->u.sa)); + } + + ftp_login(); + return ftp_action(argv[1], argv[2]); +} diff --git a/networking/hostname.c b/networking/hostname.c new file mode 100644 index 0000000..48e70db --- /dev/null +++ b/networking/hostname.c @@ -0,0 +1,93 @@ +/* vi: set sw=4 ts=4: */ +/* + * 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 "libbb.h" + +static void do_sethostname(char *s, int isfile) +{ + if (!s) + return; + if (isfile) { + parser_t *parser = config_open2(s, xfopen_for_read); + while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) { + do_sethostname(s, 0); + } + if (ENABLE_FEATURE_CLEAN_UP) + config_close(parser); + } else if (sethostname(s, strlen(s)) < 0) { + if (errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + bb_perror_msg_and_die("sethostname"); + } +} + +int hostname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int hostname_main(int argc, char **argv) +{ + enum { + OPT_d = 0x1, + OPT_f = 0x2, + OPT_i = 0x4, + OPT_s = 0x8, + OPT_F = 0x10, + OPT_dfis = 0xf, + }; + + char *buf; + char *hostname_str; + + if (argc < 1) + bb_show_usage(); + + getopt32(argv, "dfisF:", &hostname_str); + argv += optind; + buf = safe_gethostname(); + + /* Output in desired format */ + if (option_mask32 & OPT_dfis) { + struct hostent *hp; + char *p; + hp = xgethostbyname(buf); + p = strchr(hp->h_name, '.'); + if (option_mask32 & OPT_f) { + puts(hp->h_name); + } else if (option_mask32 & OPT_s) { + if (p) + *p = '\0'; + puts(hp->h_name); + } else if (option_mask32 & OPT_d) { + if (p) + puts(p + 1); + } else if (option_mask32 & OPT_i) { + while (hp->h_addr_list[0]) { + printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++))); + } + bb_putchar('\n'); + } + } + /* Set the hostname */ + else if (option_mask32 & OPT_F) { + do_sethostname(hostname_str, 1); + } else if (argv[0]) { + do_sethostname(argv[0], 0); + } + /* Or if all else fails, + * just print the current hostname */ + else { + puts(buf); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(buf); + return EXIT_SUCCESS; +} diff --git a/networking/httpd.c b/networking/httpd.c new file mode 100644 index 0000000..db8eb1e --- /dev/null +++ b/networking/httpd.c @@ -0,0 +1,2432 @@ +/* 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 starts by "/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 applet 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: + * + * H:/serverroot # define the server root. It will override -h + * 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 + * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page + * I:index.html # Show index.html when a directory is requested + * + * P:/url:[http://]hostname[:port]/new/path + * # When /urlXXXXXX is requested, reverse proxy + * # it to http://hostname[:port]/new/pathXXXXXX + * + * /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 - only first char matters. + * Deny/Allow IP logic: + * - Default is to allow all (Allow all (A:*) is a no-op). + * - Deny rules take precedence over allow rules. + * - "Deny all" rule (D:*) is applied last. + * + * 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. + * + * Custom error pages can contain an absolute path or be relative to + * 'home_httpd'. Error pages are to be static files (no CGI or script). Error + * page can only be defined in the root configuration file and are not taken + * into account in local (directories) config files. + * + * 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 "libbb.h" +#if ENABLE_FEATURE_HTTPD_USE_SENDFILE +#include <sys/sendfile.h> +#endif + +//#define DEBUG 1 +#define DEBUG 0 + +#define IOBUF_SIZE 8192 /* IO buffer */ + +/* amount of buffering in a pipe */ +#ifndef PIPE_BUF +# define PIPE_BUF 4096 +#endif +#if PIPE_BUF >= IOBUF_SIZE +# error "PIPE_BUF >= IOBUF_SIZE" +#endif + +#define HEADER_READ_TIMEOUT 60 + +static const char default_path_httpd_conf[] ALIGN1 = "/etc"; +static const char httpd_conf[] ALIGN1 = "httpd.conf"; +static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n"; + +typedef struct has_next_ptr { + struct has_next_ptr *next; +} has_next_ptr; + +/* Must have "next" as a first member */ +typedef struct Htaccess { + struct Htaccess *next; + char *after_colon; + char before_colon[1]; /* really bigger, must be last */ +} Htaccess; + +/* Must have "next" as a first member */ +typedef struct Htaccess_IP { + struct Htaccess_IP *next; + unsigned ip; + unsigned mask; + int allow_deny; +} Htaccess_IP; + +/* Must have "next" as a first member */ +typedef struct Htaccess_Proxy { + struct Htaccess_Proxy *next; + char *url_from; + char *host_port; + char *url_to; +} Htaccess_Proxy; + +enum { + HTTP_OK = 200, + HTTP_PARTIAL_CONTENT = 206, + 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, + HTTP_CONTINUE = 100, +#if 0 /* future use */ + 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 +}; + +static const uint16_t http_response_type[] ALIGN2 = { + HTTP_OK, +#if ENABLE_FEATURE_HTTPD_RANGES + HTTP_PARTIAL_CONTENT, +#endif + HTTP_MOVED_TEMPORARILY, + HTTP_REQUEST_TIMEOUT, + HTTP_NOT_IMPLEMENTED, +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + HTTP_UNAUTHORIZED, +#endif + HTTP_NOT_FOUND, + HTTP_BAD_REQUEST, + HTTP_FORBIDDEN, + HTTP_INTERNAL_SERVER_ERROR, +#if 0 /* not implemented */ + HTTP_CREATED, + HTTP_ACCEPTED, + HTTP_NO_CONTENT, + HTTP_MULTIPLE_CHOICES, + HTTP_MOVED_PERMANENTLY, + HTTP_NOT_MODIFIED, + HTTP_BAD_GATEWAY, + HTTP_SERVICE_UNAVAILABLE, +#endif +}; + +static const struct { + const char *name; + const char *info; +} http_response[ARRAY_SIZE(http_response_type)] = { + { "OK", NULL }, +#if ENABLE_FEATURE_HTTPD_RANGES + { "Partial Content", NULL }, +#endif + { "Found", NULL }, + { "Request Timeout", "No request appeared within 60 seconds" }, + { "Not Implemented", "The requested method is not recognized" }, +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + { "Unauthorized", "" }, +#endif + { "Not Found", "The requested URL was not found" }, + { "Bad Request", "Unsupported method" }, + { "Forbidden", "" }, + { "Internal Server Error", "Internal Server Error" }, +#if 0 /* not implemented */ + { "Created" }, + { "Accepted" }, + { "No Content" }, + { "Multiple Choices" }, + { "Moved Permanently" }, + { "Not Modified" }, + { "Bad Gateway", "" }, + { "Service Unavailable", "" }, +#endif +}; + + +struct globals { + int verbose; /* must be int (used by getopt32) */ + smallint flg_deny_all; + + unsigned rmt_ip; /* used for IP-based allow/deny rules */ + time_t last_mod; + char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ + const char *bind_addr_or_port; + + const char *g_query; + const char *configFile; + const char *home_httpd; + const char *index_page; + + const char *found_mime_type; + const char *found_moved_temporarily; + Htaccess_IP *ip_a_d; /* config allow/deny lines */ + + USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) + USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) + USE_FEATURE_HTTPD_CGI(char *referer;) + USE_FEATURE_HTTPD_CGI(char *user_agent;) + USE_FEATURE_HTTPD_CGI(char *http_accept;) + USE_FEATURE_HTTPD_CGI(char *http_accept_language;) + + off_t file_size; /* -1 - unknown */ +#if ENABLE_FEATURE_HTTPD_RANGES + off_t range_start; + off_t range_end; + off_t range_len; +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *g_auth; /* config user:password lines */ +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *mime_a; /* config mime types */ +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + Htaccess *script_i; /* config script interpreters */ +#endif + char *iobuf; /* [IOBUF_SIZE] */ +#define hdr_buf bb_common_bufsiz1 + char *hdr_ptr; + int hdr_cnt; +#if ENABLE_FEATURE_HTTPD_ERROR_PAGES + const char *http_error_page[ARRAY_SIZE(http_response_type)]; +#endif +#if ENABLE_FEATURE_HTTPD_PROXY + Htaccess_Proxy *proxy; +#endif +}; +#define G (*ptr_to_globals) +#define verbose (G.verbose ) +#define flg_deny_all (G.flg_deny_all ) +#define rmt_ip (G.rmt_ip ) +#define bind_addr_or_port (G.bind_addr_or_port) +#define g_query (G.g_query ) +#define configFile (G.configFile ) +#define home_httpd (G.home_httpd ) +#define index_page (G.index_page ) +#define found_mime_type (G.found_mime_type ) +#define found_moved_temporarily (G.found_moved_temporarily) +#define last_mod (G.last_mod ) +#define ip_a_d (G.ip_a_d ) +#define g_realm (G.g_realm ) +#define remoteuser (G.remoteuser ) +#define referer (G.referer ) +#define user_agent (G.user_agent ) +#define http_accept (G.http_accept ) +#define http_accept_language (G.http_accept_language) +#define file_size (G.file_size ) +#if ENABLE_FEATURE_HTTPD_RANGES +#define range_start (G.range_start ) +#define range_end (G.range_end ) +#define range_len (G.range_len ) +#endif +#define rmt_ip_str (G.rmt_ip_str ) +#define g_auth (G.g_auth ) +#define mime_a (G.mime_a ) +#define script_i (G.script_i ) +#define iobuf (G.iobuf ) +#define hdr_ptr (G.hdr_ptr ) +#define hdr_cnt (G.hdr_cnt ) +#define http_error_page (G.http_error_page ) +#define proxy (G.proxy ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ + bind_addr_or_port = "80"; \ + index_page = "index.html"; \ + file_size = -1; \ +} while (0) + +#if !ENABLE_FEATURE_HTTPD_RANGES +enum { + range_start = 0, + range_end = MAXINT(off_t) - 1, + range_len = MAXINT(off_t), +}; +#endif + + +#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) + +/* Prototypes */ +enum { + SEND_HEADERS = (1 << 0), + SEND_BODY = (1 << 1), + SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY, +}; +static void send_file_and_exit(const char *url, int what) NORETURN; + +static void free_llist(has_next_ptr **pptr) +{ + has_next_ptr *cur = *pptr; + while (cur) { + has_next_ptr *t = cur; + cur = cur->next; + free(t); + } + *pptr = NULL; +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ + || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ + || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR +static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr) +{ + free_llist((has_next_ptr**)pptr); +} +#endif + +static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr) +{ + free_llist((has_next_ptr**)pptr); +} + +/* Returns presumed mask width in bits or < 0 on error. + * Updates strp, stores IP at provided pointer */ +static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc) +{ + const char *p = *strp; + int auto_mask = 8; + unsigned ip = 0; + int j; + + if (*p == '/') + return -auto_mask; + + for (j = 0; j < 4; j++) { + unsigned octet; + + if ((*p < '0' || *p > '9') && *p != '/' && *p) + 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) + auto_mask += 8; + ip = (ip << 8) | octet; + } + if (*p) { + if (*p != endc) + return -auto_mask; + p++; + if (*p == '\0') + return -auto_mask; + } + *ipp = ip; + *strp = p; + return auto_mask; +} + +/* Returns 0 on success. Stores IP and mask at provided pointers */ +static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp) +{ + int i; + unsigned mask; + char *p; + + i = scan_ip(&str, ipp, '/'); + if (i < 0) + return i; + + if (*str) { + /* there is /xxx after dotted-IP address */ + i = bb_strtou(str, &p, 10); + if (*p == '.') { + /* 'xxx' itself is dotted-IP mask, parse it */ + /* (return 0 (success) only if it has N.N.N.N form) */ + return scan_ip(&str, maskp, '\0') - 32; + } + if (*p) + return -1; + } + + if (i > 32) + return -1; + + if (sizeof(unsigned) == 4 && i == 32) { + /* mask >>= 32 below may not work */ + mask = 0; + } else { + mask = 0xffffffff; + mask >>= i; + } + /* i == 0 -> *maskp = 0x00000000 + * i == 1 -> *maskp = 0x80000000 + * i == 4 -> *maskp = 0xf0000000 + * i == 31 -> *maskp = 0xfffffffe + * i == 32 -> *maskp = 0xffffffff */ + *maskp = (uint32_t)(~mask); + return 0; +} + +/* + * 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 + * Ennn:error.html # error page for status nnn + * P:/url:[http://]hostname[:port]/new/path # reverse proxy + * + * 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. + * Error pages are only parsed on the main config file. + * + * path Path where to look for httpd.conf (without filename). + * flag Type of the parse request. + */ +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 +#define FIND_FROM_HTTPD_ROOT 3 +static void parse_conf(const char *path, int flag) +{ + FILE *f; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *prev; +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ + || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ + || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + Htaccess *cur; +#endif + const char *filename = configFile; + char buf[160]; + char *p, *p0; + char *after_colon; + Htaccess_IP *pip; + + /* discard old rules */ + free_Htaccess_IP_list(&ip_a_d); + 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_Htaccess_list(&g_auth); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + free_Htaccess_list(&mime_a); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + free_Htaccess_list(&script_i); +#endif + } +#endif + + if (flag == SUBDIR_PARSE || filename == NULL) { + filename = alloca(strlen(path) + sizeof(httpd_conf) + 2); + sprintf((char *)filename, "%s/%s", path, httpd_conf); + } + + while ((f = fopen_for_read(filename)) == NULL) { + if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ + return; + } + if (configFile && flag == FIRST_PARSE) /* if -c option given */ + bb_simple_perror_msg_and_die(filename); + flag = FIND_FROM_HTTPD_ROOT; + filename = httpd_conf; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + prev = g_auth; +#endif + /* This could stand some work */ + while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { + after_colon = NULL; + for (p = p0; *p0 != '\0' && *p0 != '#'; p0++) { + if (!isspace(*p0)) { + *p++ = *p0; + if (*p0 == ':' && after_colon == NULL) + after_colon = p; + } + } + *p = '\0'; + + /* test for empty or strange line */ + if (after_colon == NULL || *after_colon == '\0') + continue; + p0 = buf; + if (*p0 == 'd' || *p0 == 'a') + *p0 -= 0x20; /* a/d -> A/D */ + if (*after_colon == '*') { + if (*p0 == 'D') { + /* memorize "deny all" */ + flg_deny_all = 1; + } + /* skip assumed "A:*", it is a default anyway */ + continue; + } + + if (*p0 == 'A' || *p0 == 'D') { + /* storing current config IP line */ + pip = xzalloc(sizeof(Htaccess_IP)); + if (scan_ip_mask(after_colon, &(pip->ip), &(pip->mask))) { + /* IP{/mask} syntax error detected, protect all */ + *p0 = 'D'; + pip->mask = 0; + } + pip->allow_deny = *p0; + if (*p0 == 'D') { + /* Deny:from_IP - prepend */ + pip->next = ip_a_d; + ip_a_d = pip; + } else { + /* A:from_IP - append (thus D precedes A) */ + Htaccess_IP *prev_IP = ip_a_d; + if (prev_IP == NULL) { + ip_a_d = pip; + } else { + while (prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + continue; + } + +#if ENABLE_FEATURE_HTTPD_ERROR_PAGES + if (flag == FIRST_PARSE && *p0 == 'E') { + unsigned i; + int status = atoi(++p0); /* error status code */ + if (status < HTTP_CONTINUE) { + bb_error_msg("config error '%s' in '%s'", buf, filename); + continue; + } + /* then error page; find matching status */ + for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { + if (http_response_type[i] == status) { + /* We chdir to home_httpd, thus no need to + * concat_path_file(home_httpd, after_colon) + * here */ + http_error_page[i] = xstrdup(after_colon); + break; + } + } + continue; + } +#endif + +#if ENABLE_FEATURE_HTTPD_PROXY + if (flag == FIRST_PARSE && *p0 == 'P') { + /* P:/url:[http://]hostname[:port]/new/path */ + char *url_from, *host_port, *url_to; + Htaccess_Proxy *proxy_entry; + + url_from = after_colon; + host_port = strchr(after_colon, ':'); + if (host_port == NULL) { + bb_error_msg("config error '%s' in '%s'", buf, filename); + continue; + } + *host_port++ = '\0'; + if (strncmp(host_port, "http://", 7) == 0) + host_port += 7; + if (*host_port == '\0') { + bb_error_msg("config error '%s' in '%s'", buf, filename); + continue; + } + url_to = strchr(host_port, '/'); + if (url_to == NULL) { + bb_error_msg("config error '%s' in '%s'", buf, filename); + continue; + } + *url_to = '\0'; + proxy_entry = xzalloc(sizeof(Htaccess_Proxy)); + proxy_entry->url_from = xstrdup(url_from); + proxy_entry->host_port = xstrdup(host_port); + *url_to = '/'; + proxy_entry->url_to = xstrdup(url_to); + proxy_entry->next = proxy; + proxy = proxy_entry; + continue; + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (*p0 == '/') { + /* make full path from httpd root / current_path / config_line_path */ + const char *tp = (flag == SUBDIR_PARSE ? path : ""); + p0 = xmalloc(strlen(tp) + (after_colon - buf) + 2 + strlen(after_colon)); + after_colon[-1] = '\0'; + sprintf(p0, "/%s%s", tp, buf); + + /* looks like bb_simplify_path... */ + tp = p = p0; + do { + if (*p == '/') { + if (*tp == '/') { /* skip duplicate (or initial) slash */ + continue; + } + if (*tp == '.') { + if (tp[1] == '/' || tp[1] == '\0') { /* remove extra '.' */ + continue; + } + if ((tp[1] == '.') && (tp[2] == '/' || tp[2] == '\0')) { + ++tp; + if (p > p0) { + while (*--p != '/') /* omit previous dir */ + continue; + } + continue; + } + } + } + *++p = *tp; + } while (*++tp); + + if ((p == p0) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = ':'; + strcpy(p + 1, after_colon); + } +#endif + if (*p0 == 'I') { + index_page = xstrdup(after_colon); + continue; + } + + /* Do not allow jumping around using H in subdir's configs */ + if (flag == FIRST_PARSE && *p0 == 'H') { + home_httpd = xstrdup(after_colon); + xchdir(home_httpd); + continue; + } + +#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 = xzalloc(sizeof(Htaccess) + strlen(p0)); + strcpy(cur->before_colon, p0); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (*p0 == '/') /* was malloced - see above */ + free(p0); +#endif + cur->after_colon = strchr(cur->before_colon, ':'); + *cur->after_colon++ = '\0'; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (cur->before_colon[0] == '.') { + /* .mime line: prepend to mime_a list */ + cur->next = mime_a; + mime_a = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (cur->before_colon[0] == '*' && cur->before_colon[1] == '.') { + /* script interpreter line: prepend to script_i list */ + cur->next = script_i; + script_i = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +//TODO: we do not test for leading "/"?? +//also, do we leak cur if BASIC_AUTH is off? + if (prev == NULL) { + /* first line */ + g_auth = prev = cur; + } else { + /* sort path, if current length eq or bigger then move up */ + Htaccess *prev_hti = g_auth; + size_t l = strlen(cur->before_colon); + 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 */ + g_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 /* BASIC_AUTH */ +#endif /* BASIC_AUTH || MIME_TYPES || SCRIPT_INTERPR */ + } /* while (fgets) */ + fclose(f); +} + +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR +/* + * 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(). + * + * Returns a pointer to the encoded string (malloced). + */ +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 = xmalloc(len * 6 + 1); + char *p = out; + char ch; + + 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 */ + +/* + * 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 xstrdup() 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. + * + * string The first string to decode. + * option_d 1 if called for httpd -d + * + * Returns a pointer to the decoded string (same as input). + */ +static unsigned hex_to_bin(unsigned char c) +{ + unsigned v; + + v = c - '0'; + if (v <= 9) + return v; + /* c | 0x20: letters to lower case, non-letters + * to (potentially different) non-letters */ + v = (unsigned)(c | 0x20) - 'a'; + if (v <= 5) + return v + 10; + return ~0; +} +/* For testing: +void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); } +int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f'); +t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; } +*/ +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 v; + + if (option_d && c == '+') { + *string++ = ' '; + continue; + } + if (c != '%') { + *string++ = c; + continue; + } + v = hex_to_bin(ptr[0]); + if (v > 15) { + bad_hex: + if (!option_d) + return NULL; + *string++ = '%'; + continue; + } + v = (v * 16) | hex_to_bin(ptr[1]); + if (v > 255) + goto bad_hex; + if (!option_d && (v == '/' || v == '\0')) { + /* caller takes it as indication of invalid + * (dangerous wrt exploits) chars */ + return orig + 1; + } + *string++ = v; + ptr += 2; + } + *string = '\0'; + return orig; +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +/* + * Decode a base64 data stream as per rfc1521. + * Note that the rfc states that non 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: a pointer to a base64 encoded string. + * Decoded data is stored in-place. + */ +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 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 + +/* + * Create a listen server socket on the designated port. + */ +static int openServer(void) +{ + unsigned n = bb_strtou(bind_addr_or_port, NULL, 10); + if (!errno && n && n <= 0xffff) + n = create_and_bind_stream_or_die(NULL, n); + else + n = create_and_bind_stream_or_die(bind_addr_or_port, 80); + xlisten(n, 9); + return n; +} + +/* + * Log the connection closure and exit. + */ +static void log_and_exit(void) NORETURN; +static void log_and_exit(void) +{ + /* Paranoia. IE said to be buggy. It may send some extra data + * or be confused by us just exiting without SHUT_WR. Oh well. */ + shutdown(1, SHUT_WR); + /* Why?? + (this also messes up stdin when user runs httpd -i from terminal) + ndelay_on(0); + while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0) + continue; + */ + + if (verbose > 2) + bb_error_msg("closed"); + _exit(xfunc_error_retval); +} + +/* + * 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. + * responseNum - the result code to send. + */ +static void send_headers(int responseNum) +{ + static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; + + const char *responseString = ""; + const char *infoString = NULL; + const char *mime_type; +#if ENABLE_FEATURE_HTTPD_ERROR_PAGES + const char *error_page = NULL; +#endif + unsigned i; + time_t timer = time(0); + char tmp_str[80]; + int len; + + for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { + if (http_response_type[i] == responseNum) { + responseString = http_response[i].name; + infoString = http_response[i].info; +#if ENABLE_FEATURE_HTTPD_ERROR_PAGES + error_page = http_error_page[i]; +#endif + break; + } + } + /* error message is HTML */ + mime_type = responseNum == HTTP_OK ? + found_mime_type : "text/html"; + + if (verbose) + bb_error_msg("response:%u", responseNum); + + /* emit the current date */ + strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer)); + len = sprintf(iobuf, + "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, mime_type, tmp_str); + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(iobuf + len, + "WWW-Authenticate: Basic realm=\"%s\"\r\n", + g_realm); + } +#endif + if (responseNum == HTTP_MOVED_TEMPORARILY) { + len += sprintf(iobuf + len, "Location: %s/%s%s\r\n", + found_moved_temporarily, + (g_query ? "?" : ""), + (g_query ? g_query : "")); + } + +#if ENABLE_FEATURE_HTTPD_ERROR_PAGES + if (error_page && access(error_page, R_OK) == 0) { + strcat(iobuf, "\r\n"); + len += 2; + + if (DEBUG) + fprintf(stderr, "headers: '%s'\n", iobuf); + full_write(STDOUT_FILENO, iobuf, len); + if (DEBUG) + fprintf(stderr, "writing error page: '%s'\n", error_page); + return send_file_and_exit(error_page, SEND_BODY); + } +#endif + + if (file_size != -1) { /* file */ + strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod)); +#if ENABLE_FEATURE_HTTPD_RANGES + if (responseNum == HTTP_PARTIAL_CONTENT) { + len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\r\n", + range_start, + range_end, + file_size); + file_size = range_end - range_start + 1; + } +#endif + len += sprintf(iobuf + len, +#if ENABLE_FEATURE_HTTPD_RANGES + "Accept-Ranges: bytes\r\n" +#endif + "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", + tmp_str, + "Content-length:", + file_size + ); + } + iobuf[len++] = '\r'; + iobuf[len++] = '\n'; + if (infoString) { + len += sprintf(iobuf + len, + "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n" + "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n", + responseNum, responseString, + responseNum, responseString, infoString); + } + if (DEBUG) + fprintf(stderr, "headers: '%s'\n", iobuf); + if (full_write(STDOUT_FILENO, iobuf, len) != len) { + if (verbose > 1) + bb_perror_msg("error"); + log_and_exit(); + } +} + +static void send_headers_and_exit(int responseNum) NORETURN; +static void send_headers_and_exit(int responseNum) +{ + send_headers(responseNum); + log_and_exit(); +} + +/* + * Read from the socket until '\n' or EOF. '\r' chars are removed. + * '\n' is replaced with NUL. + * Return number of characters read or 0 if nothing is read + * ('\r' and '\n' are not counted). + * Data is returned in iobuf. + */ +static int get_line(void) +{ + int count = 0; + char c; + + while (1) { + if (hdr_cnt <= 0) { + hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); + if (hdr_cnt <= 0) + break; + hdr_ptr = hdr_buf; + } + iobuf[count] = c = *hdr_ptr++; + hdr_cnt--; + + if (c == '\r') + continue; + if (c == '\n') { + iobuf[count] = '\0'; + return count; + } + if (count < (IOBUF_SIZE - 1)) /* check overflow */ + count++; + } + return count; +} + +#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY + +/* gcc 4.2.1 fares better with NOINLINE */ +static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN; +static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) +{ + enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ + struct pollfd pfd[3]; + int out_cnt; /* we buffer a bit of initial CGI output */ + int count; + + /* iobuf is used for CGI -> network data, + * hdr_buf is for network -> CGI data (POSTDATA) */ + + /* If CGI dies, we still want to correctly finish reading its output + * and send it to the peer. So please no SIGPIPEs! */ + signal(SIGPIPE, SIG_IGN); + + // We inconsistently handle a case when more POSTDATA from network + // is coming than we expected. We may give *some part* of that + // extra data to CGI. + + //if (hdr_cnt > post_len) { + // /* We got more POSTDATA from network than we expected */ + // hdr_cnt = post_len; + //} + post_len -= hdr_cnt; + /* post_len - number of POST bytes not yet read from network */ + + /* NB: breaking out of this loop jumps to log_and_exit() */ + out_cnt = 0; + while (1) { + memset(pfd, 0, sizeof(pfd)); + + pfd[FROM_CGI].fd = fromCgi_rd; + pfd[FROM_CGI].events = POLLIN; + + if (toCgi_wr) { + pfd[TO_CGI].fd = toCgi_wr; + if (hdr_cnt > 0) { + pfd[TO_CGI].events = POLLOUT; + } else if (post_len > 0) { + pfd[0].events = POLLIN; + } else { + /* post_len <= 0 && hdr_cnt <= 0: + * no more POST data to CGI, + * let CGI see EOF on CGI's stdin */ + close(toCgi_wr); + toCgi_wr = 0; + } + } + + /* Now wait on the set of sockets */ + count = safe_poll(pfd, 3, -1); + if (count <= 0) { +#if 0 + if (safe_waitpid(pid, &status, WNOHANG) <= 0) { + /* Weird. CGI didn't exit and no fd's + * are ready, yet poll returned?! */ + continue; + } + if (DEBUG && WIFEXITED(status)) + bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status)); + if (DEBUG && WIFSIGNALED(status)) + bb_error_msg("CGI killed, signal=%d", WTERMSIG(status)); +#endif + break; + } + + if (pfd[TO_CGI].revents) { + /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */ + /* Have data from peer and can write to CGI */ + count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt); + /* Doesn't happen, we dont use nonblocking IO here + *if (count < 0 && errno == EAGAIN) { + * ... + *} else */ + if (count > 0) { + hdr_ptr += count; + hdr_cnt -= count; + } else { + /* EOF/broken pipe to CGI, stop piping POST data */ + hdr_cnt = post_len = 0; + } + } + + if (pfd[0].revents) { + /* post_len > 0 && hdr_cnt == 0 here */ + /* We expect data, prev data portion is eaten by CGI + * and there *is* data to read from the peer + * (POSTDATA) */ + //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len; + //count = safe_read(STDIN_FILENO, hdr_buf, count); + count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); + if (count > 0) { + hdr_cnt = count; + hdr_ptr = hdr_buf; + post_len -= count; + } else { + /* no more POST data can be read */ + post_len = 0; + } + } + + if (pfd[FROM_CGI].revents) { + /* There is something to read from CGI */ + char *rbuf = iobuf; + + /* Are we still buffering CGI output? */ + if (out_cnt >= 0) { + /* HTTP_200[] has single "\r\n" at the end. + * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, + * CGI scripts MUST send their own header terminated by + * empty line, then data. That's why we have only one + * <cr><lf> pair here. We will output "200 OK" line + * if needed, but CGI still has to provide blank line + * between header and body */ + + /* Must use safe_read, not full_read, because + * CGI may output a few first bytes and then wait + * for POSTDATA without closing stdout. + * With full_read we may wait here forever. */ + count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8); + if (count <= 0) { + /* eof (or error) and there was no "HTTP", + * so write it, then write received data */ + if (out_cnt) { + full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); + full_write(STDOUT_FILENO, rbuf, out_cnt); + } + break; /* CGI stdout is closed, exiting */ + } + out_cnt += count; + count = 0; + /* "Status" header format is: "Status: 302 Redirected\r\n" */ + if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { + /* send "HTTP/1.0 " */ + if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) + break; + rbuf += 8; /* skip "Status: " */ + count = out_cnt - 8; + out_cnt = -1; /* buffering off */ + } else if (out_cnt >= 4) { + /* Did CGI add "HTTP"? */ + if (memcmp(rbuf, HTTP_200, 4) != 0) { + /* there is no "HTTP", do it ourself */ + if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) + break; + } + /* Commented out: + if (!strstr(rbuf, "ontent-")) { + full_write(s, "Content-type: text/plain\r\n\r\n", 28); + } + * Counter-example of valid CGI without Content-type: + * echo -en "HTTP/1.0 302 Found\r\n" + * echo -en "Location: http://www.busybox.net\r\n" + * echo -en "\r\n" + */ + count = out_cnt; + out_cnt = -1; /* buffering off */ + } + } else { + count = safe_read(fromCgi_rd, rbuf, PIPE_BUF); + if (count <= 0) + break; /* eof (or error) */ + } + if (full_write(STDOUT_FILENO, rbuf, count) != count) + break; + if (DEBUG) + fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); + } /* if (pfd[FROM_CGI].revents) */ + } /* while (1) */ + log_and_exit(); +} +#endif + +#if ENABLE_FEATURE_HTTPD_CGI + +static void setenv1(const char *name, const char *value) +{ + setenv(name, value ? value : "", 1); +} + +/* + * Spawn CGI script, forward CGI's stdin/out <=> network + * + * 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 post_len Length of the POST body. + * const char *cookie For set HTTP_COOKIE. + * const char *content_type For set CONTENT_TYPE. + */ +static void send_cgi_and_exit( + const char *url, + const char *request, + int post_len, + const char *cookie, + const char *content_type) NORETURN; +static void send_cgi_and_exit( + const char *url, + const char *request, + int post_len, + const char *cookie, + const char *content_type) +{ + struct fd_pair fromCgi; /* CGI -> httpd pipe */ + struct fd_pair toCgi; /* httpd -> CGI pipe */ + char *script; + int pid; + + /* Make a copy. NB: caller guarantees: + * url[0] == '/', url[1] != '/' */ + url = xstrdup(url); + + /* + * We are mucking with environment _first_ and then vfork/exec, + * this allows us to use vfork safely. Parent doesn't care about + * these environment changes anyway. + */ + + /* Check for [dirs/]script.cgi/PATH_INFO */ + script = (char*)url; + while ((script = strchr(script + 1, '/')) != NULL) { + struct stat sb; + + *script = '\0'; + if (!is_directory(url + 1, 1, &sb)) { + /* not directory, found script.cgi/PATH_INFO */ + *script = '/'; + break; + } + *script = '/'; /* is directory, find next '/' */ + } + setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */ + setenv1("REQUEST_METHOD", request); + if (g_query) { + putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query)); + } else { + setenv1("REQUEST_URI", url); + } + if (script != NULL) + *script = '\0'; /* cut off /PATH_INFO */ + + /* SCRIPT_FILENAME is required by PHP in CGI mode */ + if (home_httpd[0] == '/') { + char *fullpath = concat_path_file(home_httpd, url); + setenv1("SCRIPT_FILENAME", fullpath); + } + /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ + setenv1("SCRIPT_NAME", url); + /* 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 seem to do some decoding) */ + setenv1("QUERY_STRING", g_query); + putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER); + putenv((char*)"SERVER_PROTOCOL=HTTP/1.0"); + putenv((char*)"GATEWAY_INTERFACE=CGI/1.1"); + /* Having _separate_ variables for IP and port defeats + * the purpose of having socket abstraction. Which "port" + * are you using on Unix domain socket? + * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense. + * Oh well... */ + { + char *p = rmt_ip_str ? rmt_ip_str : (char*)""; + char *cp = strrchr(p, ':'); + if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']')) + cp = NULL; + if (cp) *cp = '\0'; /* delete :PORT */ + setenv1("REMOTE_ADDR", p); + if (cp) { + *cp = ':'; +#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + setenv1("REMOTE_PORT", cp + 1); +#endif + } + } + setenv1("HTTP_USER_AGENT", user_agent); + if (http_accept) + setenv1("HTTP_ACCEPT", http_accept); + if (http_accept_language) + setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language); + if (post_len) + putenv(xasprintf("CONTENT_LENGTH=%d", post_len)); + if (cookie) + setenv1("HTTP_COOKIE", cookie); + if (content_type) + setenv1("CONTENT_TYPE", content_type); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (remoteuser) { + setenv1("REMOTE_USER", remoteuser); + putenv((char*)"AUTH_TYPE=Basic"); + } +#endif + if (referer) + setenv1("HTTP_REFERER", referer); + + xpiped_pair(fromCgi); + xpiped_pair(toCgi); + + pid = vfork(); + if (pid < 0) { + /* TODO: log perror? */ + log_and_exit(); + } + + if (!pid) { + /* Child process */ + char *argv[3]; + + xfunc_error_retval = 242; + + /* NB: close _first_, then move fds! */ + close(toCgi.wr); + close(fromCgi.rd); + xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */ + xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */ + /* User seeing stderr output can be a security problem. + * If CGI really wants that, it can always do dup itself. */ + /* dup2(1, 2); */ + + /* Chdiring to script's dir */ + script = strrchr(url, '/'); + if (script != url) { /* paranoia */ + *script = '\0'; + if (chdir(url + 1) != 0) { + bb_perror_msg("chdir %s", url + 1); + goto error_execing_cgi; + } + // not needed: *script = '/'; + } + script++; + + /* set argv[0] to name without path */ + argv[0] = script; + argv[1] = NULL; + +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + { + char *suffix = strrchr(script, '.'); + + if (suffix) { + Htaccess *cur; + for (cur = script_i; cur; cur = cur->next) { + if (strcmp(cur->before_colon + 1, suffix) == 0) { + /* found interpreter name */ + argv[0] = cur->after_colon; + argv[1] = script; + argv[2] = NULL; + break; + } + } + } + } +#endif + /* restore default signal dispositions for CGI process */ + bb_signals(0 + | (1 << SIGCHLD) + | (1 << SIGPIPE) + | (1 << SIGHUP) + , SIG_DFL); + + /* _NOT_ execvp. We do not search PATH. argv[0] is a filename + * without any dir components and will only match a file + * in the current directory */ + execv(argv[0], argv); + if (verbose) + bb_perror_msg("exec %s", argv[0]); + error_execing_cgi: + /* send to stdout + * (we are CGI here, our stdout is pumped to the net) */ + send_headers_and_exit(HTTP_NOT_FOUND); + } /* end child */ + + /* Parent process */ + + /* Restore variables possibly changed by child */ + xfunc_error_retval = 0; + + /* Pump data */ + close(fromCgi.wr); + close(toCgi.rd); + cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len); +} + +#endif /* FEATURE_HTTPD_CGI */ + +/* + * Send a file response to a HTTP request, and exit + * + * Parameters: + * const char *url The requested URL (with leading /). + * what What to send (headers/body/both). + */ +static void send_file_and_exit(const char *url, int what) +{ + static const char *const suffixTable[] = { + /* Warning: shorter 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 + NULL + }; + + char *suffix; + int f; + const char *const *table; + const char *try_suffix; + ssize_t count; +#if ENABLE_FEATURE_HTTPD_USE_SENDFILE + off_t offset; +#endif + + /* If you want to know about EPIPE below + * (happens if you abort downloads from local httpd): */ + signal(SIGPIPE, SIG_IGN); + + suffix = strrchr(url, '.'); + + /* If not found, set default as "application/octet-stream"; */ + found_mime_type = "application/octet-stream"; + if (suffix) { +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; +#endif + for (table = suffixTable; *table; table += 2) { + try_suffix = strstr(table[0], suffix); + if (try_suffix) { + try_suffix += strlen(suffix); + if (*try_suffix == '\0' || *try_suffix == '.') { + found_mime_type = table[1]; + break; + } + } + } +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + for (cur = mime_a; cur; cur = cur->next) { + if (strcmp(cur->before_colon, suffix) == 0) { + found_mime_type = cur->after_colon; + break; + } + } +#endif + } + + if (DEBUG) + bb_error_msg("sending file '%s' content-type: %s", + url, found_mime_type); + + f = open(url, O_RDONLY); + if (f < 0) { + if (DEBUG) + bb_perror_msg("can't open '%s'", url); + /* Error pages are sent by using send_file_and_exit(SEND_BODY). + * IOW: it is unsafe to call send_headers_and_exit + * if what is SEND_BODY! Can recurse! */ + if (what != SEND_BODY) + send_headers_and_exit(HTTP_NOT_FOUND); + log_and_exit(); + } +#if ENABLE_FEATURE_HTTPD_RANGES + if (what == SEND_BODY) + range_start = 0; /* err pages and ranges don't mix */ + range_len = MAXINT(off_t); + if (range_start) { + if (!range_end) { + range_end = file_size - 1; + } + if (range_end < range_start + || lseek(f, range_start, SEEK_SET) != range_start + ) { + lseek(f, 0, SEEK_SET); + range_start = 0; + } else { + range_len = range_end - range_start + 1; + send_headers(HTTP_PARTIAL_CONTENT); + what = SEND_BODY; + } + } +#endif + + if (what & SEND_HEADERS) + send_headers(HTTP_OK); + +#if ENABLE_FEATURE_HTTPD_USE_SENDFILE + offset = range_start; + do { + /* sz is rounded down to 64k */ + ssize_t sz = MAXINT(ssize_t) - 0xffff; + USE_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;) + count = sendfile(1, f, &offset, sz); + if (count < 0) { + if (offset == range_start) + goto fallback; + goto fin; + } + USE_FEATURE_HTTPD_RANGES(range_len -= sz;) + } while (count > 0 && range_len); + log_and_exit(); + + fallback: +#endif + while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) { + ssize_t n; + USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;) + n = full_write(STDOUT_FILENO, iobuf, count); + if (count != n) + break; + USE_FEATURE_HTTPD_RANGES(range_len -= count;) + if (!range_len) + break; + } +#if ENABLE_FEATURE_HTTPD_USE_SENDFILE + fin: +#endif + if (count < 0 && verbose > 1) + bb_perror_msg("error"); + log_and_exit(); +} + +static int checkPermIP(void) +{ + Htaccess_IP *cur; + + for (cur = ip_a_d; cur; cur = cur->next) { +#if DEBUG + fprintf(stderr, + "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n", + rmt_ip_str, + (unsigned char)(cur->ip >> 24), + (unsigned char)(cur->ip >> 16), + (unsigned char)(cur->ip >> 8), + (unsigned char)(cur->ip), + (unsigned char)(cur->mask >> 24), + (unsigned char)(cur->mask >> 16), + (unsigned char)(cur->mask >> 8), + (unsigned char)(cur->mask) + ); +#endif + if ((rmt_ip & cur->mask) == cur->ip) + return (cur->allow_deny == 'A'); /* A -> 1 */ + } + + return !flg_deny_all; /* depends on whether we saw "D:*" */ +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +/* + * Config file entries are of the form "/<path>:<user>:<passwd>". + * If config file has no prefix match for path, access is allowed. + * + * path The file path + * user_and_passwd "user:passwd" to validate + * + * Returns 1 if user_and_passwd is OK. + */ +static int check_user_passwd(const char *path, const char *user_and_passwd) +{ + Htaccess *cur; + const char *prev = NULL; + + for (cur = g_auth; cur; cur = cur->next) { + const char *dir_prefix; + size_t len; + + dir_prefix = cur->before_colon; + + /* WHY? */ + /* If already saw a match, don't accept other different matches */ + if (prev && strcmp(prev, dir_prefix) != 0) + continue; + + if (DEBUG) + fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd); + + /* If it's not a prefix match, continue searching */ + len = strlen(dir_prefix); + if (len != 1 /* dir_prefix "/" matches all, don't need to check */ + && (strncmp(dir_prefix, path, len) != 0 + || (path[len] != '/' && path[len] != '\0')) + ) { + continue; + } + + /* Path match found */ + prev = dir_prefix; + + if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { + char *md5_passwd; + + md5_passwd = strchr(cur->after_colon, ':'); + if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1' + && md5_passwd[3] == '$' && md5_passwd[4] + ) { + char *encrypted; + int r, user_len_p1; + + md5_passwd++; + user_len_p1 = md5_passwd - cur->after_colon; + /* comparing "user:" */ + if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) { + continue; + } + + encrypted = pw_encrypt( + user_and_passwd + user_len_p1 /* cleartext pwd from user */, + md5_passwd /*salt */, 1 /* cleanup */); + r = strcmp(encrypted, md5_passwd); + free(encrypted); + if (r == 0) + goto set_remoteuser_var; /* Ok */ + continue; + } + } + + /* Comparing plaintext "user:pass" in one go */ + if (strcmp(cur->after_colon, user_and_passwd) == 0) { + set_remoteuser_var: + remoteuser = xstrndup(user_and_passwd, + strchrnul(user_and_passwd, ':') - user_and_passwd); + return 1; /* Ok */ + } + } /* for */ + + /* 0(bad) if prev is set: matches were found but passwd was wrong */ + return (prev == NULL); +} +#endif /* FEATURE_HTTPD_BASIC_AUTH */ + +#if ENABLE_FEATURE_HTTPD_PROXY +static Htaccess_Proxy *find_proxy_entry(const char *url) +{ + Htaccess_Proxy *p; + for (p = proxy; p; p = p->next) { + if (strncmp(url, p->url_from, strlen(p->url_from)) == 0) + return p; + } + return NULL; +} +#endif + +/* + * Handle timeouts + */ +static void exit_on_signal(int sig) NORETURN; +static void exit_on_signal(int sig UNUSED_PARAM) +{ + send_headers_and_exit(HTTP_REQUEST_TIMEOUT); +} + +/* + * Handle an incoming http request and exit. + */ +static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN; +static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) +{ + static const char request_GET[] ALIGN1 = "GET"; + struct stat sb; + char *urlcopy; + char *urlp; + char *tptr; +#if ENABLE_FEATURE_HTTPD_CGI + static const char request_HEAD[] ALIGN1 = "HEAD"; + const char *prequest; + char *cookie = NULL; + char *content_type = NULL; + unsigned long length = 0; +#elif ENABLE_FEATURE_HTTPD_PROXY +#define prequest request_GET + unsigned long length = 0; +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + smallint authorized = -1; +#endif + smallint ip_allowed; + char http_major_version; +#if ENABLE_FEATURE_HTTPD_PROXY + char http_minor_version; + char *header_buf = header_buf; /* for gcc */ + char *header_ptr = header_ptr; + Htaccess_Proxy *proxy_entry; +#endif + + /* Allocation of iobuf is postponed until now + * (IOW, server process doesn't need to waste 8k) */ + iobuf = xmalloc(IOBUF_SIZE); + + rmt_ip = 0; + if (fromAddr->u.sa.sa_family == AF_INET) { + rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); + } +#if ENABLE_FEATURE_IPV6 + if (fromAddr->u.sa.sa_family == AF_INET6 + && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 + && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 + && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) + rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); +#endif + if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { + /* NB: can be NULL (user runs httpd -i by hand?) */ + rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); + } + if (verbose) { + /* this trick makes -v logging much simpler */ + if (rmt_ip_str) + applet_name = rmt_ip_str; + if (verbose > 2) + bb_error_msg("connected"); + } + + /* Install timeout handler */ + signal_no_SA_RESTART_empty_mask(SIGALRM, exit_on_signal); + alarm(HEADER_READ_TIMEOUT); + + if (!get_line()) /* EOF or error or empty line */ + send_headers_and_exit(HTTP_BAD_REQUEST); + + /* Determine type of request (GET/POST) */ + urlp = strpbrk(iobuf, " \t"); + if (urlp == NULL) + send_headers_and_exit(HTTP_BAD_REQUEST); + *urlp++ = '\0'; +#if ENABLE_FEATURE_HTTPD_CGI + prequest = request_GET; + if (strcasecmp(iobuf, prequest) != 0) { + prequest = request_HEAD; + if (strcasecmp(iobuf, prequest) != 0) { + prequest = "POST"; + if (strcasecmp(iobuf, prequest) != 0) + send_headers_and_exit(HTTP_NOT_IMPLEMENTED); + } + } +#else + if (strcasecmp(iobuf, request_GET) != 0) + send_headers_and_exit(HTTP_NOT_IMPLEMENTED); +#endif + urlp = skip_whitespace(urlp); + if (urlp[0] != '/') + send_headers_and_exit(HTTP_BAD_REQUEST); + + /* Find end of URL and parse HTTP version, if any */ + http_major_version = '0'; + USE_FEATURE_HTTPD_PROXY(http_minor_version = '0';) + tptr = strchrnul(urlp, ' '); + /* Is it " HTTP/"? */ + if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) { + http_major_version = tptr[6]; + USE_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];) + } + *tptr = '\0'; + + /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ + urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page)); + /*if (urlcopy == NULL) + * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/ + strcpy(urlcopy, urlp); + /* NB: urlcopy ptr is never changed after this */ + + /* Extract url args if present */ + g_query = NULL; + tptr = strchr(urlcopy, '?'); + if (tptr) { + *tptr++ = '\0'; + g_query = tptr; + } + + /* Decode URL escape sequences */ + tptr = decodeString(urlcopy, 0); + if (tptr == NULL) + send_headers_and_exit(HTTP_BAD_REQUEST); + if (tptr == urlcopy + 1) { + /* '/' or NUL is encoded */ + send_headers_and_exit(HTTP_NOT_FOUND); + } + + /* Canonicalize path */ + /* Algorithm stolen from libbb bb_simplify_path(), + * but don't strdup, retain trailing slash, protect root */ + urlp = tptr = urlcopy; + do { + if (*urlp == '/') { + /* skip duplicate (or initial) slash */ + if (*tptr == '/') { + continue; + } + if (*tptr == '.') { + /* skip extra "/./" */ + if (tptr[1] == '/' || !tptr[1]) { + continue; + } + /* "..": be careful */ + if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) { + ++tptr; + if (urlp == urlcopy) /* protect root */ + send_headers_and_exit(HTTP_BAD_REQUEST); + while (*--urlp != '/') /* omit previous dir */; + continue; + } + } + } + *++urlp = *tptr; + } while (*++tptr); + *++urlp = '\0'; /* terminate after last character */ + + /* If URL is a directory, add '/' */ + if (urlp[-1] != '/') { + if (is_directory(urlcopy + 1, 1, &sb)) { + found_moved_temporarily = urlcopy; + } + } + + /* Log it */ + if (verbose > 1) + bb_error_msg("url:%s", urlcopy); + + tptr = urlcopy; + ip_allowed = checkPermIP(); + while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) { + /* have path1/path2 */ + *tptr = '\0'; + if (is_directory(urlcopy + 1, 1, &sb)) { + /* may have subdir config */ + parse_conf(urlcopy + 1, SUBDIR_PARSE); + ip_allowed = checkPermIP(); + } + *tptr = '/'; + } + +#if ENABLE_FEATURE_HTTPD_PROXY + proxy_entry = find_proxy_entry(urlcopy); + if (proxy_entry) + header_buf = header_ptr = xmalloc(IOBUF_SIZE); +#endif + + if (http_major_version >= '0') { + /* Request was with "... HTTP/nXXX", and n >= 0 */ + + /* Read until blank line for HTTP version specified, else parse immediate */ + while (1) { + alarm(HEADER_READ_TIMEOUT); + if (!get_line()) + break; /* EOF or error or empty line */ + if (DEBUG) + bb_error_msg("header: '%s'", iobuf); + +#if ENABLE_FEATURE_HTTPD_PROXY + /* We need 2 more bytes for yet another "\r\n" - + * see near fdprintf(proxy_fd...) further below */ + if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) { + int len = strlen(iobuf); + if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4) + len = IOBUF_SIZE - (header_ptr - header_buf) - 4; + memcpy(header_ptr, iobuf, len); + header_ptr += len; + header_ptr[0] = '\r'; + header_ptr[1] = '\n'; + header_ptr += 2; + } +#endif + +#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY + /* Try and do our best to parse more lines */ + if ((STRNCASECMP(iobuf, "Content-length:") == 0)) { + /* extra read only for POST */ + if (prequest != request_GET +#if ENABLE_FEATURE_HTTPD_CGI + && prequest != request_HEAD +#endif + ) { + tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1); + if (!tptr[0]) + send_headers_and_exit(HTTP_BAD_REQUEST); + /* not using strtoul: it ignores leading minus! */ + length = bb_strtou(tptr, NULL, 10); + /* length is "ulong", but we need to pass it to int later */ + if (errno || length > INT_MAX) + send_headers_and_exit(HTTP_BAD_REQUEST); + } + } +#endif +#if ENABLE_FEATURE_HTTPD_CGI + else if (STRNCASECMP(iobuf, "Cookie:") == 0) { + cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1)); + } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) { + content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1)); + } else if (STRNCASECMP(iobuf, "Referer:") == 0) { + referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1)); + } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) { + user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1)); + } else if (STRNCASECMP(iobuf, "Accept:") == 0) { + http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1)); + } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) { + http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1)); + } +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (STRNCASECMP(iobuf, "Authorization:") == 0) { + /* We only allow Basic credentials. + * It shows up as "Authorization: Basic <user>:<passwd>" where + * "<user>:<passwd>" is base64 encoded. + */ + tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1); + if (STRNCASECMP(tptr, "Basic") != 0) + continue; + tptr += sizeof("Basic")-1; + /* decodeBase64() skips whitespace itself */ + decodeBase64(tptr); + authorized = check_user_passwd(urlcopy, tptr); + } +#endif +#if ENABLE_FEATURE_HTTPD_RANGES + if (STRNCASECMP(iobuf, "Range:") == 0) { + /* We know only bytes=NNN-[MMM] */ + char *s = skip_whitespace(iobuf + sizeof("Range:")-1); + if (strncmp(s, "bytes=", 6) == 0) { + s += sizeof("bytes=")-1; + range_start = BB_STRTOOFF(s, &s, 10); + if (s[0] != '-' || range_start < 0) { + range_start = 0; + } else if (s[1]) { + range_end = BB_STRTOOFF(s+1, NULL, 10); + if (errno || range_end < range_start) + range_start = 0; + } + } + } +#endif + } /* while extra header reading */ + } + + /* We are done reading headers, disable peer timeout */ + alarm(0); + + if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || !ip_allowed) { + /* protect listing [/path]/httpd_conf or IP deny */ + send_headers_and_exit(HTTP_FORBIDDEN); + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + /* Case: no "Authorization:" was seen, but page does require passwd. + * Check that with dummy user:pass */ + if (authorized < 0) + authorized = check_user_passwd(urlcopy, ":"); + if (!authorized) + send_headers_and_exit(HTTP_UNAUTHORIZED); +#endif + + if (found_moved_temporarily) { + send_headers_and_exit(HTTP_MOVED_TEMPORARILY); + } + +#if ENABLE_FEATURE_HTTPD_PROXY + if (proxy_entry != NULL) { + int proxy_fd; + len_and_sockaddr *lsa; + + proxy_fd = socket(AF_INET, SOCK_STREAM, 0); + if (proxy_fd < 0) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + lsa = host2sockaddr(proxy_entry->host_port, 80); + if (lsa == NULL) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n", + prequest, /* GET or POST */ + proxy_entry->url_to, /* url part 1 */ + urlcopy + strlen(proxy_entry->url_from), /* url part 2 */ + (g_query ? "?" : ""), /* "?" (maybe) */ + (g_query ? g_query : ""), /* query string (maybe) */ + http_major_version, http_minor_version); + header_ptr[0] = '\r'; + header_ptr[1] = '\n'; + header_ptr += 2; + write(proxy_fd, header_buf, header_ptr - header_buf); + free(header_buf); /* on the order of 8k, free it */ + /* cgi_io_loop_and_exit needs to have two disctinct fds */ + cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length); + } +#endif + + tptr = urlcopy + 1; /* skip first '/' */ + +#if ENABLE_FEATURE_HTTPD_CGI + if (strncmp(tptr, "cgi-bin/", 8) == 0) { + if (tptr[8] == '\0') { + /* protect listing "cgi-bin/" */ + send_headers_and_exit(HTTP_FORBIDDEN); + } + send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); + } +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + { + char *suffix = strrchr(tptr, '.'); + if (suffix) { + Htaccess *cur; + for (cur = script_i; cur; cur = cur->next) { + if (strcmp(cur->before_colon + 1, suffix) == 0) { + send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); + } + } + } + } +#endif + if (prequest != request_GET && prequest != request_HEAD) { + send_headers_and_exit(HTTP_NOT_IMPLEMENTED); + } +#endif /* FEATURE_HTTPD_CGI */ + + if (urlp[-1] == '/') + strcpy(urlp, index_page); + if (stat(tptr, &sb) == 0) { + file_size = sb.st_size; + last_mod = sb.st_mtime; + } +#if ENABLE_FEATURE_HTTPD_CGI + else if (urlp[-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) { + urlp[0] = '\0'; + g_query = urlcopy; + send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type); + } + } +#endif + /* else { + * fall through to send_file, it errors out if open fails + * } + */ + + send_file_and_exit(tptr, +#if ENABLE_FEATURE_HTTPD_CGI + (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS) +#else + SEND_HEADERS_AND_BODY +#endif + ); +} + +/* + * The main http server function. + * Given a socket, listen for new connections and farm out + * the processing as a [v]forked process. + * Never returns. + */ +#if BB_MMU +static void mini_httpd(int server_socket) NORETURN; +static void mini_httpd(int server_socket) +{ + /* NB: it's best to not use xfuncs in this loop before fork(). + * Otherwise server may die on transient errors (temporary + * out-of-memory condition, etc), which is Bad(tm). + * Try to do any dangerous calls after fork. + */ + while (1) { + int n; + len_and_sockaddr fromAddr; + + /* Wait for connections... */ + fromAddr.len = LSA_SIZEOF_SA; + n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); + + if (n < 0) + continue; + /* set the KEEPALIVE option to cull dead connections */ + setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + + if (fork() == 0) { + /* child */ +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + /* Do not reload config on HUP */ + signal(SIGHUP, SIG_IGN); +#endif + close(server_socket); + xmove_fd(n, 0); + xdup2(0, 1); + + handle_incoming_and_exit(&fromAddr); + } + /* parent, or fork failed */ + close(n); + } /* while (1) */ + /* never reached */ +} +#else +static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN; +static void mini_httpd_nommu(int server_socket, int argc, char **argv) +{ + char *argv_copy[argc + 2]; + + argv_copy[0] = argv[0]; + argv_copy[1] = (char*)"-i"; + memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0])); + + /* NB: it's best to not use xfuncs in this loop before vfork(). + * Otherwise server may die on transient errors (temporary + * out-of-memory condition, etc), which is Bad(tm). + * Try to do any dangerous calls after fork. + */ + while (1) { + int n; + len_and_sockaddr fromAddr; + + /* Wait for connections... */ + fromAddr.len = LSA_SIZEOF_SA; + n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); + + if (n < 0) + continue; + /* set the KEEPALIVE option to cull dead connections */ + setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + + if (vfork() == 0) { + /* child */ +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + /* Do not reload config on HUP */ + signal(SIGHUP, SIG_IGN); +#endif + close(server_socket); + xmove_fd(n, 0); + xdup2(0, 1); + + /* Run a copy of ourself in inetd mode */ + re_exec(argv_copy); + } + /* parent, or vfork failed */ + close(n); + } /* while (1) */ + /* never reached */ +} +#endif + +/* + * Process a HTTP connection on stdin/out. + * Never returns. + */ +static void mini_httpd_inetd(void) NORETURN; +static void mini_httpd_inetd(void) +{ + len_and_sockaddr fromAddr; + + memset(&fromAddr, 0, sizeof(fromAddr)); + fromAddr.len = LSA_SIZEOF_SA; + /* NB: can fail if user runs it by hand and types in http cmds */ + getpeername(0, &fromAddr.u.sa, &fromAddr.len); + handle_incoming_and_exit(&fromAddr); +} + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +static void sighup_handler(int sig) +{ + parse_conf(default_path_httpd_conf, sig ? SIGNALED_PARSE : FIRST_PARSE); + signal_SA_RESTART_empty_mask(SIGHUP, sighup_handler); +} +#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, + p_opt_verbose , + 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, + OPT_VERBOSE = 1 << p_opt_verbose, +}; + + +int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int httpd_main(int argc UNUSED_PARAM, char **argv) +{ + int server_socket = server_socket; /* for gcc */ + unsigned opt; + char *url_for_decode; + USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;) + 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;) + + INIT_G(); + +#if ENABLE_LOCALE_SUPPORT + /* Undo busybox.c: we want to speak English in http (dates etc) */ + setlocale(LC_TIME, "C"); +#endif + + home_httpd = xrealloc_getcwd_or_warn(NULL); + /* -v counts, -i implies -f */ + opt_complementary = "vv:if"; + /* We do not "absolutize" path given by -h (home) opt. + * If user gives relative path in -h, + * $SCRIPT_FILENAME will not be set. */ + opt = getopt32(argv, "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:ifv", + &configFile, &url_for_decode, &home_httpd + USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) + USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm) + USE_FEATURE_HTTPD_AUTH_MD5(, &pass) + USE_FEATURE_HTTPD_SETUID(, &s_ugid) + , &bind_addr_or_port + , &verbose + ); + if (opt & OPT_DECODE_URL) { + fputs(decodeString(url_for_decode, 1), stdout); + return 0; + } +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR + if (opt & OPT_ENCODE_URL) { + fputs(encodeString(url_for_encode), stdout); + return 0; + } +#endif +#if ENABLE_FEATURE_HTTPD_AUTH_MD5 + if (opt & OPT_MD5) { + puts(pw_encrypt(pass, "$1$", 1)); + return 0; + } +#endif +#if ENABLE_FEATURE_HTTPD_SETUID + if (opt & OPT_SETUID) { + xget_uidgid(&ugid, s_ugid); + } +#endif + +#if !BB_MMU + if (!(opt & OPT_FOREGROUND)) { + bb_daemonize_or_rexec(0, argv); /* don't change current directory */ + } +#endif + + xchdir(home_httpd); + if (!(opt & OPT_INETD)) { + signal(SIGCHLD, SIG_IGN); + 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 0 /*was #if ENABLE_FEATURE_HTTPD_CGI*/ + /* User can do it himself: 'env - PATH="$PATH" httpd' + * We don't do it because we don't want to screw users + * which want to do + * 'env - VAR1=val1 VAR2=val2 httpd' + * and have VAR1 and VAR2 values visible in their CGIs. + * Besides, it is also smaller. */ + { + char *p = getenv("PATH"); + /* env strings themself are not freed, no need to xstrdup(p): */ + clearenv(); + if (p) + putenv(p - 5); +// if (!(opt & OPT_INETD)) +// setenv_long("SERVER_PORT", ???); + } +#endif + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + if (!(opt & OPT_INETD)) { + /* runs parse_conf() inside */ + sighup_handler(0); + } else +#endif + { + parse_conf(default_path_httpd_conf, FIRST_PARSE); + } + + xfunc_error_retval = 0; + if (opt & OPT_INETD) + mini_httpd_inetd(); +#if BB_MMU + if (!(opt & OPT_FOREGROUND)) + bb_daemonize(0); /* don't change current directory */ + mini_httpd(server_socket); /* never returns */ +#else + mini_httpd_nommu(server_socket, argc, argv); /* never returns */ +#endif + /* return 0; */ +} diff --git a/networking/httpd_indexcgi.c b/networking/httpd_indexcgi.c new file mode 100644 index 0000000..94c6a69 --- /dev/null +++ b/networking/httpd_indexcgi.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* + * This program is a CGI application. It outputs directory index page. + * Put it into cgi-bin/index.cgi and chmod 0755. + */ + +/* Build a-la +i486-linux-uclibc-gcc \ +-static -static-libgcc \ +-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ +-Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \ +-Wold-style-definition -Wdeclaration-after-statement -Wno-pointer-sign \ +-Wmissing-prototypes -Wmissing-declarations \ +-Os -fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer \ +-ffunction-sections -fdata-sections -fno-guess-branch-probability \ +-funsigned-char \ +-falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1 \ +-march=i386 -mpreferred-stack-boundary=2 \ +-Wl,-Map -Wl,link.map -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ +httpd_indexcgi.c -o index.cgi +*/ + +/* We don't use printf, as it pulls in >12 kb of code from uclibc (i386). */ +/* Currently malloc machinery is the biggest part of libc we pull in. */ +/* We have only one realloc and one strdup, any idea how to do without? */ +/* Size (i386, approximate): + * text data bss dec hex filename + * 13036 44 3052 16132 3f04 index.cgi + * 2576 4 2048 4628 1214 index.cgi.o + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <dirent.h> +#include <time.h> + +/* Appearance of the table is controlled by style sheet *ONLY*, + * formatting code uses <TAG class=CLASS> to apply style + * to elements. Edit stylesheet to your liking and recompile. */ + +#define STYLE_STR \ +"<style>" "\n"\ +"table {" "\n"\ + "width:100%;" "\n"\ + "background-color:#fff5ee;" "\n"\ + "border-width:1px;" /* 1px 1px 1px 1px; */ "\n"\ + "border-spacing:2px;" "\n"\ + "border-style:solid;" /* solid solid solid solid; */ "\n"\ + "border-color:black;" /* black black black black; */ "\n"\ + "border-collapse:collapse;" "\n"\ +"}" "\n"\ +"th {" "\n"\ + "border-width:1px;" /* 1px 1px 1px 1px; */ "\n"\ + "padding:1px;" /* 1px 1px 1px 1px; */ "\n"\ + "border-style:solid;" /* solid solid solid solid; */ "\n"\ + "border-color:black;" /* black black black black; */ "\n"\ +"}" "\n"\ +"td {" "\n"\ + /* top right bottom left */ \ + "border-width:0px 1px 0px 1px;" "\n"\ + "padding:1px;" /* 1px 1px 1px 1px; */ "\n"\ + "border-style:solid;" /* solid solid solid solid; */ "\n"\ + "border-color:black;" /* black black black black; */ "\n"\ + "white-space:nowrap;" "\n"\ +"}" "\n"\ +"tr.hdr { background-color:#eee5de; }" "\n"\ +"tr.o { background-color:#ffffff; }" "\n"\ +/* tr.e { ... } - for even rows (currently none) */ \ +"tr.foot { background-color:#eee5de; }" "\n"\ +"th.cnt { text-align:left; }" "\n"\ +"th.sz { text-align:right; }" "\n"\ +"th.dt { text-align:right; }" "\n"\ +"td.sz { text-align:right; }" "\n"\ +"td.dt { text-align:right; }" "\n"\ +"col.nm { width:98%; }" "\n"\ +"col.sz { width:1%; }" "\n"\ +"col.dt { width:1%; }" "\n"\ +"</style>" "\n"\ + +typedef struct dir_list_t { + char *dl_name; + mode_t dl_mode; + off_t dl_size; + time_t dl_mtime; +} dir_list_t; + +static int compare_dl(dir_list_t *a, dir_list_t *b) +{ + /* ".." is 'less than' any other dir entry */ + if (strcmp(a->dl_name, "..") == 0) { + return -1; + } + if (strcmp(b->dl_name, "..") == 0) { + return 1; + } + if (S_ISDIR(a->dl_mode) != S_ISDIR(b->dl_mode)) { + /* 1 if b is a dir (and thus a is 'after' b, a > b), + * else -1 (a < b) */ + return (S_ISDIR(b->dl_mode) != 0) ? 1 : -1; + } + return strcmp(a->dl_name, b->dl_name); +} + +static char buffer[2*1024 > sizeof(STYLE_STR) ? 2*1024 : sizeof(STYLE_STR)]; +static char *dst = buffer; +enum { + BUFFER_SIZE = sizeof(buffer), + HEADROOM = 64, +}; + +/* After this call, you have at least size + HEADROOM bytes available + * ahead of dst */ +static void guarantee(int size) +{ + if (buffer + (BUFFER_SIZE-HEADROOM) - dst >= size) + return; + write(STDOUT_FILENO, buffer, dst - buffer); + dst = buffer; +} + +/* NB: formatters do not store terminating NUL! */ + +/* HEADROOM bytes are available after dst after this call */ +static void fmt_str(/*char *dst,*/ const char *src) +{ + unsigned len = strlen(src); + guarantee(len); + memcpy(dst, src, len); + dst += len; +} + +/* HEADROOM bytes after dst are available after this call */ +static void fmt_url(/*char *dst,*/ const char *name) +{ + while (*name) { + unsigned c = *name++; + guarantee(3); + *dst = c; + if ((c - '0') > 9 /* not a digit */ + && ((c|0x20) - 'a') > 26 /* not A-Z or a-z */ + && !strchr("._-+@", c) + ) { + *dst++ = '%'; + *dst++ = "0123456789ABCDEF"[c >> 4]; + *dst = "0123456789ABCDEF"[c & 0xf]; + } + dst++; + } +} + +/* HEADROOM bytes are available after dst after this call */ +static void fmt_html(/*char *dst,*/ const char *name) +{ + while (*name) { + char c = *name++; + if (c == '<') + fmt_str("<"); + else if (c == '>') + fmt_str(">"); + else if (c == '&') { + fmt_str("&"); + } else { + guarantee(1); + *dst++ = c; + continue; + } + } +} + +/* HEADROOM bytes are available after dst after this call */ +static void fmt_ull(/*char *dst,*/ unsigned long long n) +{ + char buf[sizeof(n)*3 + 2]; + char *p; + + p = buf + sizeof(buf) - 1; + *p = '\0'; + do { + *--p = (n % 10) + '0'; + n /= 10; + } while (n); + fmt_str(/*dst,*/ p); +} + +/* Does not call guarantee - eats into headroom instead */ +static void fmt_02u(/*char *dst,*/ unsigned n) +{ + /* n %= 100; - not needed, callers don't pass big n */ + dst[0] = (n / 10) + '0'; + dst[1] = (n % 10) + '0'; + dst += 2; +} + +/* Does not call guarantee - eats into headroom instead */ +static void fmt_04u(/*char *dst,*/ unsigned n) +{ + /* n %= 10000; - not needed, callers don't pass big n */ + fmt_02u(n / 100); + fmt_02u(n % 100); +} + +int main(void) +{ + dir_list_t *dir_list; + dir_list_t *cdir; + unsigned dir_list_count; + unsigned count_dirs; + unsigned count_files; + unsigned long long size_total; + int odd; + DIR *dirp; + char *QUERY_STRING; + + QUERY_STRING = getenv("QUERY_STRING"); + if (!QUERY_STRING + || QUERY_STRING[0] != '/' + || strstr(QUERY_STRING, "/../") + || strcmp(strrchr(QUERY_STRING, '/'), "/..") == 0 + ) { + return 1; + } + + if (chdir("..") + || (QUERY_STRING[1] && chdir(QUERY_STRING + 1)) + ) { + return 1; + } + + dirp = opendir("."); + if (!dirp) + return 1; + dir_list = NULL; + dir_list_count = 0; + while (1) { + struct dirent *dp; + struct stat sb; + + dp = readdir(dirp); + if (!dp) + break; + if (dp->d_name[0] == '.' && !dp->d_name[1]) + continue; + if (stat(dp->d_name, &sb) != 0) + continue; + dir_list = realloc(dir_list, (dir_list_count + 1) * sizeof(dir_list[0])); + dir_list[dir_list_count].dl_name = strdup(dp->d_name); + dir_list[dir_list_count].dl_mode = sb.st_mode; + dir_list[dir_list_count].dl_size = sb.st_size; + dir_list[dir_list_count].dl_mtime = sb.st_mtime; + dir_list_count++; + } + closedir(dirp); + + qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl); + + fmt_str( + "" /* Additional headers (currently none) */ + "\r\n" /* Mandatory empty line after headers */ + "<html><head><title>Index of "); + /* Guard against directories with &, > etc */ + fmt_html(QUERY_STRING); + fmt_str( + "</title>\n" + STYLE_STR + "</head>" "\n" + "<body>" "\n" + "<h1>Index of "); + fmt_html(QUERY_STRING); + fmt_str( + "</h1>" "\n" + "<table>" "\n" + "<col class=nm><col class=sz><col class=dt>" "\n" + "<tr class=hdr><th class=cnt>Name<th class=sz>Size<th class=dt>Last modified" "\n"); + + odd = 0; + count_dirs = 0; + count_files = 0; + size_total = 0; + cdir = dir_list; + while (dir_list_count--) { + struct tm *tm; + + if (S_ISDIR(cdir->dl_mode)) { + count_dirs++; + } else if (S_ISREG(cdir->dl_mode)) { + count_files++; + size_total += cdir->dl_size; + } else + goto next; + + fmt_str("<tr class="); + *dst++ = (odd ? 'o' : 'e'); + fmt_str("><td class=nm><a href='"); + fmt_url(cdir->dl_name); /* %20 etc */ + if (S_ISDIR(cdir->dl_mode)) + *dst++ = '/'; + fmt_str("'>"); + fmt_html(cdir->dl_name); /* < etc */ + if (S_ISDIR(cdir->dl_mode)) + *dst++ = '/'; + fmt_str("</a><td class=sz>"); + if (S_ISREG(cdir->dl_mode)) + fmt_ull(cdir->dl_size); + fmt_str("<td class=dt>"); + tm = gmtime(&cdir->dl_mtime); + fmt_04u(1900 + tm->tm_year); *dst++ = '-'; + fmt_02u(tm->tm_mon + 1); *dst++ = '-'; + fmt_02u(tm->tm_mday); *dst++ = ' '; + fmt_02u(tm->tm_hour); *dst++ = ':'; + fmt_02u(tm->tm_min); *dst++ = ':'; + fmt_02u(tm->tm_sec); + *dst++ = '\n'; + + odd = 1 - odd; + next: + cdir++; + } + + fmt_str("<tr class=foot><th class=cnt>Files: "); + fmt_ull(count_files); + /* count_dirs - 1: we don't want to count ".." */ + fmt_str(", directories: "); + fmt_ull(count_dirs - 1); + fmt_str("<th class=sz>"); + fmt_ull(size_total); + fmt_str("<th class=dt>\n"); + /* "</table></body></html>" - why bother? */ + guarantee(BUFFER_SIZE * 2); /* flush */ + + return 0; +} diff --git a/networking/httpd_post_upload.txt b/networking/httpd_post_upload.txt new file mode 100644 index 0000000..a53b114 --- /dev/null +++ b/networking/httpd_post_upload.txt @@ -0,0 +1,76 @@ +POST upload example: + +post_upload.htm +=============== +<html> +<body> +<form action=/cgi-bin/post_upload.cgi method=post enctype=multipart/form-data> +File to upload: <input type=file name=file1> <input type=submit> +</form> + + +post_upload.cgi +=============== +#!/bin/sh + +# POST upload format: +# -----------------------------29995809218093749221856446032^M +# Content-Disposition: form-data; name="file1"; filename="..."^M +# Content-Type: application/octet-stream^M +# ^M <--------- headers end with empty line +# file contents +# file contents +# file contents +# ^M <--------- extra empty line +# -----------------------------29995809218093749221856446032--^M + +# Beware: bashism $'\r' is used to handle ^M + +file=/tmp/$$-$RANDOM + +# CGI output must start with at least empty line (or headers) +printf '\r\n' + +IFS=$'\r' +read -r delim_line + +IFS='' +delim_line="${delim_line}--"$'\r' + +while read -r line; do + test "$line" = '' && break + test "$line" = $'\r' && break +done + +# This will not work well for binary files: bash 3.2 is upset +# by reading NUL bytes and loses chunks of data. +# If you are not bothered by having junk appended to the uploaded file, +# consider using simple "cat >file" instead of the entire +# fragment below. + +while read -r line; do + + while test "$line" = $'\r'; do + read -r line + test "$line" = "$delim_line" && { + # Aha! Empty line + delimiter! All done + cat <<EOF +<html> +<body> +File upload has been accepted +EOF + exit 0 + } + # Empty line + NOT delimiter. Save empty line, + # and go check next line + printf "%s\n" $'\r' -vC >&3 + done + # Not empty line - just save + printf "%s\n" "$line" -vC >&3 +done 3>"$file" + +cat <<EOF +<html> +<body> +File upload was not terminated with '$delim_line' - ??! +EOF diff --git a/networking/ifconfig.c b/networking/ifconfig.c new file mode 100644 index 0000000..e999741 --- /dev/null +++ b/networking/ifconfig.c @@ -0,0 +1,540 @@ +/* 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 defined(__GLIBC__) && __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 "libbb.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; + unsigned short 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[] = { + { "SIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric) }, + { "SIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu) }, + { "SIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen) }, + { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) }, + { "SIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask) }, + { "SIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr) }, +#if ENABLE_FEATURE_IFCONFIG_HW + { "SIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr) }, +#endif + { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) }, +#ifdef SIOCSKEEPALIVE + { "SKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data) }, +#endif +#ifdef SIOCSOUTFILL + { "SOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data) }, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start) }, + { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr) }, + { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq) }, +#endif + /* Last entry if for unmatched (possibly hostname) arg. */ +#if ENABLE_FEATURE_IPV6 + { "SIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */ + { "DIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */ +#endif + { "SIFADDR", 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 + +/* + * Our main function. + */ +int ifconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ifconfig_main(int argc, char **argv) +{ + struct ifreq ifr; + struct sockaddr_in sai; +#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 */ + 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 + ARRAY_SIZE(Arg1Opt) - 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 + else { + len_and_sockaddr *lsa; + if (strcmp(host, "inet") == 0) + continue; /* compat stuff */ + lsa = xhost2sockaddr(host, 0); +#if ENABLE_FEATURE_IPV6 + if (lsa->u.sa.sa_family == AF_INET6) { + int sockfd6; + struct in6_ifreq ifr6; + + memcpy((char *) &ifr6.ifr6_addr, + (char *) &(lsa->u.sin6.sin6_addr), + sizeof(struct in6_addr)); + + /* Create a channel to the NET kernel. */ + sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0); + xioctl(sockfd6, SIOGIFINDEX, &ifr); + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + ifr6.ifr6_prefixlen = prefix_len; + ioctl_or_perror_and_die(sockfd6, a1op->selector, &ifr6, "SIOC%s", a1op->name); + if (ENABLE_FEATURE_CLEAN_UP) + free(lsa); + continue; + } +#endif + sai.sin_addr = lsa->u.sin.sin_addr; + if (ENABLE_FEATURE_CLEAN_UP) + free(lsa); + } +#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. */ + smalluint hw_class= index_in_substrings("ether\0" + USE_FEATURE_HWIB("infiniband\0"), *argv) + 1; + if (!hw_class || !*++argv) + bb_show_usage(); + /*safe_strncpy(host, *argv, sizeof(host));*/ + host = *argv; + if (hw_class == 1 ? in_ether(host, &sa) : in_ib(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) { + xioctl(sockfd, SIOCGIFMAP, &ifr); + 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; + } + + ioctl_or_perror_and_die(sockfd, a1op->selector, &ifr, "SIOC%s", 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; + } + + xioctl(sockfd, SIOCGIFFLAGS, &ifr); + selector = op->selector; + if (mask & SET_MASK) + ifr.ifr_flags |= selector; + else + ifr.ifr_flags &= ~selector; + xioctl(sockfd, SIOCSIFFLAGS, &ifr); + } /* 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 = (char *) 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/ifenslave.c b/networking/ifenslave.c new file mode 100644 index 0000000..ae97457 --- /dev/null +++ b/networking/ifenslave.c @@ -0,0 +1,597 @@ +/* Mode: C; + * + * Mini ifenslave implementation for busybox + * Copyright (C) 2005 by Marc Leeman <marc.leeman@barco.com> + * + * ifenslave.c: Configure network interfaces for parallel routing. + * + * This program controls the Linux implementation of running multiple + * network interfaces in parallel. + * + * Author: Donald Becker <becker@cesdis.gsfc.nasa.gov> + * Copyright 1994-1996 Donald Becker + * + * 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. + * + * The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O + * Center of Excellence in Space Data and Information Sciences + * Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + * + * Changes : + * - 2000/10/02 Willy Tarreau <willy at meta-x.org> : + * - few fixes. Master's MAC address is now correctly taken from + * the first device when not previously set ; + * - detach support : call BOND_RELEASE to detach an enslaved interface. + * - give a mini-howto from command-line help : # ifenslave -h + * + * - 2001/02/16 Chad N. Tindel <ctindel at ieee dot org> : + * - Master is now brought down before setting the MAC address. In + * the 2.4 kernel you can't change the MAC address while the device is + * up because you get EBUSY. + * + * - 2001/09/13 Takao Indoh <indou dot takao at jp dot fujitsu dot com> + * - Added the ability to change the active interface on a mode 1 bond + * at runtime. + * + * - 2001/10/23 Chad N. Tindel <ctindel at ieee dot org> : + * - No longer set the MAC address of the master. The bond device will + * take care of this itself + * - Try the SIOC*** versions of the bonding ioctls before using the + * old versions + * - 2002/02/18 Erik Habbinga <erik_habbinga @ hp dot com> : + * - ifr2.ifr_flags was not initialized in the hwaddr_notset case, + * SIOCGIFFLAGS now called before hwaddr_notset test + * + * - 2002/10/31 Tony Cureington <tony.cureington * hp_com> : + * - If the master does not have a hardware address when the first slave + * is enslaved, the master is assigned the hardware address of that + * slave - there is a comment in bonding.c stating "ifenslave takes + * care of this now." This corrects the problem of slaves having + * different hardware addresses in active-backup mode when + * multiple interfaces are specified on a single ifenslave command + * (ifenslave bond0 eth0 eth1). + * + * - 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and + * Shmulik Hen <shmulik.hen at intel dot com> + * - Moved setting the slave's mac address and openning it, from + * the application to the driver. This enables support of modes + * that need to use the unique mac address of each slave. + * The driver also takes care of closing the slave and restoring its + * original mac address upon release. + * In addition, block possibility of enslaving before the master is up. + * This prevents putting the system in an undefined state. + * + * - 2003/05/01 - Amir Noam <amir.noam at intel dot com> + * - Added ABI version control to restore compatibility between + * new/old ifenslave and new/old bonding. + * - Prevent adding an adapter that is already a slave. + * Fixes the problem of stalling the transmission and leaving + * the slave in a down state. + * + * - 2003/05/01 - Shmulik Hen <shmulik.hen at intel dot com> + * - Prevent enslaving if the bond device is down. + * Fixes the problem of leaving the system in unstable state and + * halting when trying to remove the module. + * - Close socket on all abnormal exists. + * - Add versioning scheme that follows that of the bonding driver. + * current version is 1.0.0 as a base line. + * + * - 2003/05/22 - Jay Vosburgh <fubar at us dot ibm dot com> + * - ifenslave -c was broken; it's now fixed + * - Fixed problem with routes vanishing from master during enslave + * processing. + * + * - 2003/05/27 - Amir Noam <amir.noam at intel dot com> + * - Fix backward compatibility issues: + * For drivers not using ABI versions, slave was set down while + * it should be left up before enslaving. + * Also, master was not set down and the default set_mac_address() + * would fail and generate an error message in the system log. + * - For opt_c: slave should not be set to the master's setting + * while it is running. It was already set during enslave. To + * simplify things, it is now handeled separately. + * + * - 2003/12/01 - Shmulik Hen <shmulik.hen at intel dot com> + * - Code cleanup and style changes + * set version to 1.1.0 + */ + +#include "libbb.h" + +/* #include <net/if.h> - no. linux/if_bonding.h pulls in linux/if.h */ +#include <net/if_arp.h> +#include <linux/if_bonding.h> +#include <linux/sockios.h> + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +typedef uint64_t u64; /* hack, so we may include kernel's ethtool.h */ +typedef uint32_t u32; /* ditto */ +typedef uint16_t u16; /* ditto */ +typedef uint8_t u8; /* ditto */ +#include <linux/ethtool.h> + + +struct dev_data { + struct ifreq mtu, flags, hwaddr; +}; + + +enum { skfd = 3 }; /* AF_INET socket for ioctl() calls. */ +struct globals { + unsigned abi_ver; /* userland - kernel ABI version */ + smallint hwaddr_set; /* Master's hwaddr is set */ + struct dev_data master; + struct dev_data slave; +}; +#define G (*ptr_to_globals) +#define abi_ver (G.abi_ver ) +#define hwaddr_set (G.hwaddr_set) +#define master (G.master ) +#define slave (G.slave ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} while (0) + + +/* NOINLINEs are placed where it results in smaller code (gcc 4.3.1) */ + +static void strncpy_IFNAMSIZ(char *dst, const char *src) +{ + strncpy(dst, src, IFNAMSIZ); +} + +static int ioctl_on_skfd(unsigned request, struct ifreq *ifr) +{ + return ioctl(skfd, request, ifr); +} + +static int set_ifrname_and_do_ioctl(unsigned request, struct ifreq *ifr, const char *ifname) +{ + strncpy_IFNAMSIZ(ifr->ifr_name, ifname); + return ioctl_on_skfd(request, ifr); +} + +static int get_if_settings(char *ifname, struct dev_data *dd) +{ + int res; + + res = set_ifrname_and_do_ioctl(SIOCGIFMTU, &dd->mtu, ifname); + res |= set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &dd->flags, ifname); + res |= set_ifrname_and_do_ioctl(SIOCGIFHWADDR, &dd->hwaddr, ifname); + + return res; +} + +static int get_slave_flags(char *slave_ifname) +{ + return set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &slave.flags, slave_ifname); +} + +static int set_hwaddr(char *ifname, struct sockaddr *hwaddr) +{ + struct ifreq ifr; + + memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(*hwaddr)); + return set_ifrname_and_do_ioctl(SIOCSIFHWADDR, &ifr, ifname); +} + +static int set_mtu(char *ifname, int mtu) +{ + struct ifreq ifr; + + ifr.ifr_mtu = mtu; + return set_ifrname_and_do_ioctl(SIOCSIFMTU, &ifr, ifname); +} + +static int set_if_flags(char *ifname, int flags) +{ + struct ifreq ifr; + + ifr.ifr_flags = flags; + return set_ifrname_and_do_ioctl(SIOCSIFFLAGS, &ifr, ifname); +} + +static int set_if_up(char *ifname, int flags) +{ + int res = set_if_flags(ifname, flags | IFF_UP); + if (res) + bb_perror_msg("%s: can't up", ifname); + return res; +} + +static int set_if_down(char *ifname, int flags) +{ + int res = set_if_flags(ifname, flags & ~IFF_UP); + if (res) + bb_perror_msg("%s: can't down", ifname); + return res; +} + +static int clear_if_addr(char *ifname) +{ + struct ifreq ifr; + + ifr.ifr_addr.sa_family = AF_INET; + memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data)); + return set_ifrname_and_do_ioctl(SIOCSIFADDR, &ifr, ifname); +} + +static int set_if_addr(char *master_ifname, char *slave_ifname) +{ +#if (SIOCGIFADDR | SIOCSIFADDR \ + | SIOCGIFDSTADDR | SIOCSIFDSTADDR \ + | SIOCGIFBRDADDR | SIOCSIFBRDADDR \ + | SIOCGIFNETMASK | SIOCSIFNETMASK) <= 0xffff +#define INT uint16_t +#else +#define INT int +#endif + static const struct { + INT g_ioctl; + INT s_ioctl; + } ifra[] = { + { SIOCGIFADDR, SIOCSIFADDR }, + { SIOCGIFDSTADDR, SIOCSIFDSTADDR }, + { SIOCGIFBRDADDR, SIOCSIFBRDADDR }, + { SIOCGIFNETMASK, SIOCSIFNETMASK }, + }; + + struct ifreq ifr; + int res; + unsigned i; + + for (i = 0; i < ARRAY_SIZE(ifra); i++) { + res = set_ifrname_and_do_ioctl(ifra[i].g_ioctl, &ifr, master_ifname); + if (res < 0) { + ifr.ifr_addr.sa_family = AF_INET; + memset(ifr.ifr_addr.sa_data, 0, + sizeof(ifr.ifr_addr.sa_data)); + } + + res = set_ifrname_and_do_ioctl(ifra[i].s_ioctl, &ifr, slave_ifname); + if (res < 0) + return res; + } + + return 0; +} + +static void change_active(char *master_ifname, char *slave_ifname) +{ + struct ifreq ifr; + + if (!(slave.flags.ifr_flags & IFF_SLAVE)) { + bb_error_msg_and_die("%s is not a slave", slave_ifname); + } + + strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname); + if (set_ifrname_and_do_ioctl(SIOCBONDCHANGEACTIVE, &ifr, master_ifname) + && ioctl_on_skfd(BOND_CHANGE_ACTIVE_OLD, &ifr) + ) { + bb_perror_msg_and_die( + "master %s, slave %s: can't " + "change active", + master_ifname, slave_ifname); + } +} + +static NOINLINE int enslave(char *master_ifname, char *slave_ifname) +{ + struct ifreq ifr; + int res; + + if (slave.flags.ifr_flags & IFF_SLAVE) { + bb_error_msg( + "%s is already a slave", + slave_ifname); + return 1; + } + + res = set_if_down(slave_ifname, slave.flags.ifr_flags); + if (res) + return res; + + if (abi_ver < 2) { + /* Older bonding versions would panic if the slave has no IP + * address, so get the IP setting from the master. + */ + res = set_if_addr(master_ifname, slave_ifname); + if (res) { + bb_perror_msg("%s: can't set address", slave_ifname); + return res; + } + } else { + res = clear_if_addr(slave_ifname); + if (res) { + bb_perror_msg("%s: can't clear address", slave_ifname); + return res; + } + } + + if (master.mtu.ifr_mtu != slave.mtu.ifr_mtu) { + res = set_mtu(slave_ifname, master.mtu.ifr_mtu); + if (res) { + bb_perror_msg("%s: can't set MTU", slave_ifname); + return res; + } + } + + if (hwaddr_set) { + /* Master already has an hwaddr + * so set it's hwaddr to the slave + */ + if (abi_ver < 1) { + /* The driver is using an old ABI, so + * the application sets the slave's + * hwaddr + */ + if (set_hwaddr(slave_ifname, &(master.hwaddr.ifr_hwaddr))) { + bb_perror_msg("%s: can't set hw address", + slave_ifname); + goto undo_mtu; + } + + /* For old ABI the application needs to bring the + * slave back up + */ + if (set_if_up(slave_ifname, slave.flags.ifr_flags)) + goto undo_slave_mac; + } + /* The driver is using a new ABI, + * so the driver takes care of setting + * the slave's hwaddr and bringing + * it up again + */ + } else { + /* No hwaddr for master yet, so + * set the slave's hwaddr to it + */ + if (abi_ver < 1) { + /* For old ABI, the master needs to be + * down before setting it's hwaddr + */ + if (set_if_down(master_ifname, master.flags.ifr_flags)) + goto undo_mtu; + } + + if (set_hwaddr(master_ifname, &(slave.hwaddr.ifr_hwaddr))) { + bb_error_msg("%s: can't set hw address", + master_ifname); + goto undo_mtu; + } + + if (abi_ver < 1) { + /* For old ABI, bring the master + * back up + */ + if (set_if_up(master_ifname, master.flags.ifr_flags)) + goto undo_master_mac; + } + + hwaddr_set = 1; + } + + /* Do the real thing */ + strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname); + if (set_ifrname_and_do_ioctl(SIOCBONDENSLAVE, &ifr, master_ifname) + && ioctl_on_skfd(BOND_ENSLAVE_OLD, &ifr) + ) { + goto undo_master_mac; + } + + return 0; + +/* rollback (best effort) */ + undo_master_mac: + set_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr)); + hwaddr_set = 0; + goto undo_mtu; + + undo_slave_mac: + set_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr)); + undo_mtu: + set_mtu(slave_ifname, slave.mtu.ifr_mtu); + return 1; +} + +static int release(char *master_ifname, char *slave_ifname) +{ + struct ifreq ifr; + int res = 0; + + if (!(slave.flags.ifr_flags & IFF_SLAVE)) { + bb_error_msg("%s is not a slave", slave_ifname); + return 1; + } + + strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname); + if (set_ifrname_and_do_ioctl(SIOCBONDRELEASE, &ifr, master_ifname) < 0 + && ioctl_on_skfd(BOND_RELEASE_OLD, &ifr) < 0 + ) { + return 1; + } + if (abi_ver < 1) { + /* The driver is using an old ABI, so we'll set the interface + * down to avoid any conflicts due to same MAC/IP + */ + res = set_if_down(slave_ifname, slave.flags.ifr_flags); + } + + /* set to default mtu */ + set_mtu(slave_ifname, 1500); + + return res; +} + +static NOINLINE void get_drv_info(char *master_ifname) +{ + struct ifreq ifr; + struct ethtool_drvinfo info; + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_data = (caddr_t)&info; + info.cmd = ETHTOOL_GDRVINFO; + /* both fields are 32 bytes long (long enough) */ + strcpy(info.driver, "ifenslave"); + strcpy(info.fw_version, utoa(BOND_ABI_VERSION)); + if (set_ifrname_and_do_ioctl(SIOCETHTOOL, &ifr, master_ifname) < 0) { + if (errno == EOPNOTSUPP) + return; + bb_perror_msg_and_die("%s: SIOCETHTOOL error", master_ifname); + } + + abi_ver = bb_strtou(info.fw_version, NULL, 0); + if (errno) + bb_error_msg_and_die("%s: SIOCETHTOOL error", master_ifname); +} + +int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ifenslave_main(int argc UNUSED_PARAM, char **argv) +{ + char *master_ifname, *slave_ifname; + int rv; + int res; + unsigned opt; + enum { + OPT_c = (1 << 0), + OPT_d = (1 << 1), + OPT_f = (1 << 2), + }; +#if ENABLE_GETOPT_LONG + static const char ifenslave_longopts[] ALIGN1 = + "change-active\0" No_argument "c" + "detach\0" No_argument "d" + "force\0" No_argument "f" + /* "all-interfaces\0" No_argument "a" */ + ; + + applet_long_options = ifenslave_longopts; +#endif + INIT_G(); + + opt = getopt32(argv, "cdfa"); + argv += optind; + if (opt & (opt-1)) /* Only one option can be given */ + bb_show_usage(); + + master_ifname = *argv++; + + /* No interface names - show all interfaces. */ + if (!master_ifname) { + display_interfaces(NULL); + return EXIT_SUCCESS; + } + + /* Open a basic socket */ + xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), skfd); + + /* Exchange abi version with bonding module */ + get_drv_info(master_ifname); + + slave_ifname = *argv++; + if (!slave_ifname) { + if (opt & (OPT_d|OPT_c)) { + /* --change or --detach, and no slaves given - + * show all interfaces. */ + display_interfaces(slave_ifname /* == NULL */); + return 2; /* why 2? */ + } + /* A single arg means show the + * configuration for this interface + */ + display_interfaces(master_ifname); + return EXIT_SUCCESS; + } + + if (get_if_settings(master_ifname, &master)) { + /* Probably a good reason not to go on */ + bb_perror_msg_and_die("%s: can't get settings", master_ifname); + } + + /* Check if master is indeed a master; + * if not then fail any operation + */ + if (!(master.flags.ifr_flags & IFF_MASTER)) + bb_error_msg_and_die("%s is not a master", master_ifname); + + /* Check if master is up; if not then fail any operation */ + if (!(master.flags.ifr_flags & IFF_UP)) + bb_error_msg_and_die("%s is not up", master_ifname); + +#ifdef WHY_BOTHER + /* Neither -c[hange] nor -d[etach] -> it's "enslave" then; + * and -f[orce] is not there too. Check that it's ethernet. */ + if (!(opt & (OPT_d|OPT_c|OPT_f)) { + /* The family '1' is ARPHRD_ETHER for ethernet. */ + if (master.hwaddr.ifr_hwaddr.sa_family != 1) { + bb_error_msg_and_die( + "%s is not ethernet-like (-f overrides)", + master_ifname); + } + } +#endif + + /* Accepts only one slave */ + if (opt & OPT_c) { + /* Change active slave */ + if (get_slave_flags(slave_ifname)) { + bb_perror_msg_and_die( + "%s: can't get flags", slave_ifname); + } + change_active(master_ifname, slave_ifname); + return EXIT_SUCCESS; + } + + /* Accepts multiple slaves */ + res = 0; + do { + if (opt & OPT_d) { + /* Detach a slave interface from the master */ + rv = get_slave_flags(slave_ifname); + if (rv) { + /* Can't work with this slave, */ + /* remember the error and skip it */ + bb_perror_msg( + "skipping %s: can't get flags", + slave_ifname); + res = rv; + continue; + } + rv = release(master_ifname, slave_ifname); + if (rv) { + bb_perror_msg("can't release %s from %s", + slave_ifname, master_ifname); + res = rv; + } + } else { + /* Attach a slave interface to the master */ + rv = get_if_settings(slave_ifname, &slave); + if (rv) { + /* Can't work with this slave, */ + /* remember the error and skip it */ + bb_perror_msg( + "skipping %s: can't get settings", + slave_ifname); + res = rv; + continue; + } + rv = enslave(master_ifname, slave_ifname); + if (rv) { + bb_perror_msg("can't enslave %s to %s", + slave_ifname, master_ifname); + res = rv; + } + } + } while ((slave_ifname = *argv++) != NULL); + + if (ENABLE_FEATURE_CLEAN_UP) { + close(skfd); + } + + return res; +} diff --git a/networking/ifupdown.c b/networking/ifupdown.c new file mode 100644 index 0000000..d7cb40f --- /dev/null +++ b/networking/ifupdown.c @@ -0,0 +1,1313 @@ +/* vi: set sw=4 ts=4: */ +/* + * ifupdown for busybox + * Copyright (c) 2002 Glenn McGrath + * 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 + * (defined via CONFIG_IFUPDOWN_IFSTATE_PATH) and can be overridden by build + * configuration. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include <sys/utsname.h> +#include <fnmatch.h> + +#include "libbb.h" + +#define MAX_OPT_DEPTH 10 +#define EUNBALBRACK 10001 +#define EUNDEFVAR 10002 +#define EUNBALPER 10000 + +#if ENABLE_FEATURE_IFUPDOWN_MAPPING +#define MAX_INTERFACE_LENGTH 10 +#endif + +#define debug_noise(args...) /*fprintf(stderr, args)*/ + +/* Forward declaration */ +struct interface_defn_t; + +typedef int execfn(char *command); + +struct method_t { + const char *name; + int (*up)(struct interface_defn_t *ifd, execfn *e); + int (*down)(struct interface_defn_t *ifd, execfn *e); +}; + +struct address_family_t { + const 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 **my_environ; + +static const char *startup_PATH; + +#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6 + +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) { + static char *label_buf; + //char *result; + + free(label_buf); + label_buf = xstrdup(ifd->iface); + // Remove virtual iface suffix - why? + // ubuntu's ifup doesn't do this + //result = strchrnul(label_buf, ':'); + //*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; +} + +#if ENABLE_FEATURE_IFUPDOWN_IP +static int count_netmask_bits(const char *dotted_quad) +{ +// int result; +// unsigned 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; +// if ((a|b|c|d) >> 8) +// return -1; /* one of numbers is >= 256 */ +// d |= (a << 24) | (b << 16) | (c << 8); /* IP */ +// d = ~d; /* 11110000 -> 00001111 */ + + /* Shorter version */ + int result; + struct in_addr ip; + unsigned d; + + if (inet_aton(dotted_quad, &ip) == 0) + return -1; /* malformed dotted IP */ + d = ntohl(ip.s_addr); /* IP in host order */ + d = ~d; /* 11110000 -> 00001111 */ + if (d & (d+1)) /* check that it is in 00001111 form */ + return -1; /* no it is not */ + result = 32; + while (d) { + d >>= 1; + result--; + } + return result; +} +#endif + +static char *parse(const char *command, struct interface_defn_t *ifd) +{ + size_t old_pos[MAX_OPT_DEPTH] = { 0 }; + int okay[MAX_OPT_DEPTH] = { 1 }; + int opt_depth = 1; + char *result = NULL; + + 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] = result ? strlen(result) : 0; + 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) { +#if ENABLE_FEATURE_IFUPDOWN_IP + /* "hwaddress <class> <address>": + * unlike ifconfig, ip doesnt want <class> + * (usually "ether" keyword). Skip it. */ + if (strncmp(command, "hwaddress", 9) == 0) { + varvalue = skip_whitespace(skip_non_whitespace(varvalue)); + } +#endif + addstr(&result, varvalue, strlen(varvalue)); + } else { +#if ENABLE_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); + if (res > 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) { + /* parse error? */ + return 0; + } + /* out == "": parsed ok but not all needed variables known, skip */ + ret = out[0] ? (*exec)(out) : 1; + + free(out); + if (ret != 1) { + return 0; + } + return 1; +} +#endif + +#if ENABLE_FEATURE_IFUPDOWN_IPV6 +static int loopback_up6(struct interface_defn_t *ifd, execfn *exec) +{ +#if ENABLE_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) +{ +#if ENABLE_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; +#if ENABLE_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%netmask% dev %iface%[[ label %label%]]", ifd, exec); + result += execute("ip link set[[ mtu %mtu%]][[ addr %hwaddress%]] %iface% up", ifd, exec); + /* Was: "[[ ip ....%gateway% ]]". Removed extra spaces w/o checking */ + 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) +{ +#if ENABLE_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% down", ifd, exec); +#endif +} + +#if ENABLE_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[] = { +#if ENABLE_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", + ARRAY_SIZE(methods6), + methods6 +}; +#endif /* FEATURE_IFUPDOWN_IPV6 */ + +#if ENABLE_FEATURE_IFUPDOWN_IPV4 +static int loopback_up(struct interface_defn_t *ifd, execfn *exec) +{ +#if ENABLE_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) +{ +#if ENABLE_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; +#if ENABLE_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%]][[ addr %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; +#if ENABLE_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); */ + /* Bringing the interface down deletes the routes in itself. + Otherwise this fails if we reference 'gateway' when using this from dhcp_down */ + result = 1; + result += execute("ifconfig %iface% down", ifd, exec); +#endif + return ((result == 2) ? 2 : 0); +} + +#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP +struct dhcp_client_t +{ + const char *name; + const char *startcmd; + const char *stopcmd; +}; + +static const struct dhcp_client_t ext_dhcp_clients[] = { + { "dhcpcd", + "dhcpcd[[ -h %hostname%]][[ -i %vendor%]][[ -I %clientid%]][[ -l %leasetime%]] %iface%", + "dhcpcd -k %iface%", + }, + { "dhclient", + "dhclient -pf /var/run/dhclient.%iface%.pid %iface%", + "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null", + }, + { "pump", + "pump -i %iface%[[ -h %hostname%]][[ -l %leasehours%]]", + "pump -i %iface% -k", + }, + { "udhcpc", + "udhcpc -R -n -p /var/run/udhcpc.%iface%.pid -i %iface%[[ -H %hostname%]][[ -c %clientid%]]" + "[[ -s %script%]][[ %udhcpc_opts%]]", + "kill `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", + }, +}; +#endif /* ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCPC */ + +#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP +static int dhcp_up(struct interface_defn_t *ifd, execfn *exec) +{ + unsigned i; +#if ENABLE_FEATURE_IFUPDOWN_IP + /* ip doesn't up iface when it configures it (unlike ifconfig) */ + if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec)) + return 0; +#else + /* needed if we have hwaddress on dhcp iface */ + if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec)) + return 0; +#endif + for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); 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; +} +#elif ENABLE_APP_UDHCPC +static int dhcp_up(struct interface_defn_t *ifd, execfn *exec) +{ +#if ENABLE_FEATURE_IFUPDOWN_IP + /* ip doesn't up iface when it configures it (unlike ifconfig) */ + if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec)) + return 0; +#else + /* needed if we have hwaddress on dhcp iface */ + if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec)) + return 0; +#endif + return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid " + "-i %iface%[[ -H %hostname%]][[ -c %clientid%]][[ -s %script%]][[ %udhcpc_opts%]]", + ifd, exec); +} +#else +static int dhcp_up(struct interface_defn_t *ifd UNUSED_PARAM, + execfn *exec UNUSED_PARAM) +{ + return 0; /* no dhcp support */ +} +#endif + +#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP +static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) +{ + int result = 0; + unsigned i; + + for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) { + if (exists_execable(ext_dhcp_clients[i].name)) { + result += execute(ext_dhcp_clients[i].stopcmd, ifd, exec); + if (result) + break; + } + } + + if (!result) + bb_error_msg("warning: no dhcp clients found and stopped"); + + /* Sleep a bit, otherwise static_down tries to bring down interface too soon, + and it may come back up because udhcpc is still shutting down */ + usleep(100000); + result += static_down(ifd, exec); + return ((result == 3) ? 3 : 0); +} +#elif ENABLE_APP_UDHCPC +static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) +{ + int result; + result = execute("kill " + "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec); + /* Also bring the hardware interface down since + killing the dhcp client alone doesn't do it. + This enables consecutive ifup->ifdown->ifup */ + /* Sleep a bit, otherwise static_down tries to bring down interface too soon, + and it may come back up because udhcpc is still shutting down */ + usleep(100000); + result += static_down(ifd, exec); + return ((result == 3) ? 3 : 0); +} +#else +static int dhcp_down(struct interface_defn_t *ifd UNUSED_PARAM, + execfn *exec UNUSED_PARAM) +{ + return 0; /* no dhcp support */ +} +#endif + +static int manual_up_down(struct interface_defn_t *ifd UNUSED_PARAM, execfn *exec UNUSED_PARAM) +{ + 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("start-stop-daemon --start -x wvdial " + "-p /var/run/wvdial.%iface% -b -m --[[ %provider%]]", ifd, exec); +} + +static int wvdial_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("start-stop-daemon --stop -x 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", + ARRAY_SIZE(methods), + methods +}; + +#endif /* if ENABLE_FEATURE_IFUPDOWN_IPV4 */ + +static char *next_word(char **buf) +{ + unsigned length; + char *word; + + /* Skip over leading whitespace */ + word = skip_whitespace(*buf); + + /* Stop on EOL */ + if (*word == '\0') + return NULL; + + /* Find the length of this word (can't be 0) */ + length = strcspn(word, " \t\n"); + + /* Unless we are already at NUL, store NUL and advance */ + if (word[length] != '\0') + word[length++] = '\0'; + + *buf = word + length; + + 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; + /* TODO: use index_in_str_array() */ + 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) +{ + /* Let's try to be compatible. + * + * "man 5 interfaces" says: + * Lines starting with "#" are ignored. Note that end-of-line + * comments are NOT supported, comments must be on a line of their own. + * A line may be extended across multiple lines by making + * the last character a backslash. + * + * Seen elsewhere in example config file: + * A first non-blank "#" character makes the rest of the line + * be ignored. Blank lines are ignored. Lines may be indented freely. + * A "\" character at the very end of the line indicates the next line + * should be treated as a continuation of the current one. + */ +#if ENABLE_FEATURE_IFUPDOWN_MAPPING + struct mapping_defn_t *currmap = NULL; +#endif + struct interface_defn_t *currif = NULL; + struct interfaces_file_t *defn; + FILE *f; + char *buf; + char *first_word; + char *rest_of_line; + enum { NONE, IFACE, MAPPING } currently_processing = NONE; + + defn = xzalloc(sizeof(*defn)); + f = xfopen_for_read(filename); + + while ((buf = xmalloc_fgetline(f)) != NULL) { +#if ENABLE_DESKTOP + /* Trailing "\" concatenates lines */ + char *p; + while ((p = last_char_is(buf, '\\')) != NULL) { + *p = '\0'; + rest_of_line = xmalloc_fgetline(f); + if (!rest_of_line) + break; + p = xasprintf("%s%s", buf, rest_of_line); + free(buf); + free(rest_of_line); + buf = p; + } +#endif + rest_of_line = buf; + first_word = next_word(&rest_of_line); + if (!first_word || *first_word == '#') { + free(buf); + continue; /* blank/comment line */ + } + + if (strcmp(first_word, "mapping") == 0) { +#if ENABLE_FEATURE_IFUPDOWN_MAPPING + currmap = xzalloc(sizeof(*currmap)); + + while ((first_word = next_word(&rest_of_line)) != NULL) { + currmap->match = xrealloc_vector(currmap->match, 4, currmap->n_matches); + currmap->match[currmap->n_matches++] = xstrdup(first_word); + } + /*currmap->max_mappings = 0; - done by xzalloc */ + /*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(first_word, "iface") == 0) { + static const struct address_family_t *const addr_fams[] = { +#if ENABLE_FEATURE_IFUPDOWN_IPV4 + &addr_inet, +#endif +#if ENABLE_FEATURE_IFUPDOWN_IPV6 + &addr_inet6, +#endif + NULL + }; + char *iface_name; + char *address_family_name; + char *method_name; + llist_t *iface_list; + + currif = xzalloc(sizeof(*currif)); + iface_name = next_word(&rest_of_line); + address_family_name = next_word(&rest_of_line); + method_name = next_word(&rest_of_line); + + if (method_name == NULL) + bb_error_msg_and_die("too few parameters for line \"%s\"", buf); + + /* ship any trailing whitespace */ + rest_of_line = skip_whitespace(rest_of_line); + + if (rest_of_line[0] != '\0' /* && rest_of_line[0] != '#' */) + bb_error_msg_and_die("too many parameters \"%s\"", buf); + + currif->iface = xstrdup(iface_name); + + currif->address_family = get_address_family(addr_fams, address_family_name); + if (!currif->address_family) + bb_error_msg_and_die("unknown address type \"%s\"", address_family_name); + + currif->method = get_method(currif->address_family, method_name); + if (!currif->method) + bb_error_msg_and_die("unknown method \"%s\"", method_name); + + 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_and_die("duplicate interface \"%s\"", tmp->iface); + } + } + 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(first_word, "auto") == 0) { + while ((first_word = next_word(&rest_of_line)) != NULL) { + + /* Check the interface isnt already listed */ + if (find_list_string(defn->autointerfaces, first_word)) { + bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf); + } + + /* Add the interface to the list */ + llist_add_to_end(&(defn->autointerfaces), xstrdup(first_word)); + debug_noise("\nauto %s\n", first_word); + } + currently_processing = NONE; + } else { + switch (currently_processing) { + case IFACE: + if (rest_of_line[0] == '\0') + bb_error_msg_and_die("option with empty value \"%s\"", buf); + + if (strcmp(first_word, "up") != 0 + && strcmp(first_word, "down") != 0 + && strcmp(first_word, "pre-up") != 0 + && strcmp(first_word, "post-down") != 0 + ) { + int i; + for (i = 0; i < currif->n_options; i++) { + if (strcmp(currif->option[i].name, first_word) == 0) + bb_error_msg_and_die("duplicate option \"%s\"", buf); + } + } + if (currif->n_options >= currif->max_options) { + currif->max_options += 10; + currif->option = xrealloc(currif->option, + sizeof(*currif->option) * currif->max_options); + } + debug_noise("\t%s=%s\n", first_word, rest_of_line); + currif->option[currif->n_options].name = xstrdup(first_word); + currif->option[currif->n_options].value = xstrdup(rest_of_line); + currif->n_options++; + break; + case MAPPING: +#if ENABLE_FEATURE_IFUPDOWN_MAPPING + if (strcmp(first_word, "script") == 0) { + if (currmap->script != NULL) + bb_error_msg_and_die("duplicate script in mapping \"%s\"", buf); + currmap->script = xstrdup(next_word(&rest_of_line)); + } else if (strcmp(first_word, "map") == 0) { + if (currmap->n_mappings >= currmap->max_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(&rest_of_line)); + currmap->n_mappings++; + } else { + bb_error_msg_and_die("misplaced option \"%s\"", buf); + } +#endif + break; + case NONE: + default: + bb_error_msg_and_die("misplaced option \"%s\"", buf); + } + } + free(buf); + } /* while (fgets) */ + + if (ferror(f) != 0) { + /* ferror does NOT set errno! */ + bb_error_msg_and_die("%s: I/O error", filename); + } + fclose(f); + + return defn; +} + +static char *setlocalenv(const 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 (my_environ != NULL) { + for (ppch = my_environ; *ppch; ppch++) { + free(*ppch); + *ppch = NULL; + } + free(my_environ); + } + my_environ = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ )); + environend = my_environ; + + 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); + child = vfork(); + switch (child) { + case -1: /* failure */ + return 0; + case 0: /* child */ + execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, NULL, my_environ); + _exit(127); + } + safe_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); + /* heh, we don't bother free'ing it */ + return doit(buf); +} + +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; +} + +#if ENABLE_FEATURE_IFUPDOWN_MAPPING +static int popen2(FILE **in, FILE **out, char *command, char *param) +{ + char *argv[3] = { command, param, NULL }; + struct fd_pair infd, outfd; + pid_t pid; + + xpiped_pair(infd); + xpiped_pair(outfd); + + fflush(NULL); + pid = vfork(); + + switch (pid) { + case -1: /* failure */ + bb_perror_msg_and_die("vfork"); + case 0: /* child */ + /* NB: close _first_, then move fds! */ + close(infd.wr); + close(outfd.rd); + xmove_fd(infd.rd, 0); + xmove_fd(outfd.wr, 1); + BB_EXECVP(command, argv); + _exit(127); + } + /* parent */ + close(infd.rd); + close(outfd.wr); + *in = fdopen(infd.wr, "w"); + *out = fdopen(outfd.rd, "r"); + return pid; +} + +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. Never fails. */ + pid = popen2(&in, &out, map->script, physical); + + /* Write mappings to stdin of mapping script. */ + for (i = 0; i < map->n_mappings; i++) { + fprintf(in, "%s\n", map->mapping[i]); + } + fclose(in); + safe_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 = xmalloc_fgetline(out); + + if (new_logical) { + /* 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; + } + } + + fclose(out); + + return logical; +} +#endif /* FEATURE_IFUPDOWN_MAPPING */ + +static llist_t *find_iface_state(llist_t *state_list, const char *iface) +{ + unsigned 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; +} + +/* read the previous state from the state file */ +static llist_t *read_iface_state(void) +{ + llist_t *state_list = NULL; + FILE *state_fp = fopen_for_read(CONFIG_IFUPDOWN_IFSTATE_PATH); + + if (state_fp) { + char *start, *end_ptr; + while ((start = xmalloc_fgets(state_fp)) != NULL) { + /* We should only need to check for a single character */ + end_ptr = start + strcspn(start, " \t\n"); + *end_ptr = '\0'; + llist_add_to(&state_list, start); + } + fclose(state_fp); + } + return state_list; +} + + +int ifupdown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ifupdown_main(int argc, char **argv) +{ + int (*cmds)(struct interface_defn_t *); + struct interfaces_file_t *defn; + llist_t *target_list = NULL; + const char *interfaces = "/etc/network/interfaces"; + bool any_failures = 0; + + cmds = iface_down; + if (applet_name[2] == 'u') { + /* ifup command */ + cmds = iface_up; + } + + getopt32(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); + + startup_PATH = getenv("PATH"); + if (!startup_PATH) startup_PATH = ""; + + /* Create a list of interfaces to work on */ + if (DO_ALL) { + 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; + bool 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) { + llist_t *state_list = read_iface_state(); + 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; + } + } + llist_free(state_list, free); + } + +#if ENABLE_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) { + bb_putchar('\n'); + } + + if (!okay && !FORCE) { + bb_error_msg("ignoring unknown interface %s", liface); + any_failures = 1; + } else if (!NO_ACT) { + /* update the state file */ + FILE *state_fp; + llist_t *state; + llist_t *state_list = read_iface_state(); + llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + char * const 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 state_list */ + llist_unlink(&state_list, iface_state); + free(llist_pop(&iface_state)); + } + + /* Actually write the new state */ + state_fp = xfopen_for_write(CONFIG_IFUPDOWN_IFSTATE_PATH); + state = state_list; + while (state) { + if (state->data) { + fprintf(state_fp, "%s\n", state->data); + } + state = state->link; + } + fclose(state_fp); + llist_free(state_list, free); + } + } + + return any_failures; +} diff --git a/networking/inetd.c b/networking/inetd.c new file mode 100644 index 0000000..bf018d7 --- /dev/null +++ b/networking/inetd.c @@ -0,0 +1,1592 @@ +/* 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> */ +/* IPv6 support, many bug fixes by Denys Vlasenko (c) 2008 */ +/* + * 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 configured services when a connection + * from a peer is established or a datagram arrives. + * 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 peer's 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. peer's address can be obtained + * using recvfrom. + * + * 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 + * (usually "tcp" or "udp") + * 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 + * rpc/protocol "rpc/tcp" etc + * 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. + */ + +/* inetd rules for passing file descriptors to children + * (http://www.freebsd.org/cgi/man.cgi?query=inetd): + * + * The wait/nowait entry specifies whether the server that is invoked by + * inetd will take over the socket associated with the service access point, + * and thus whether inetd should wait for the server to exit before listen- + * ing for new service requests. Datagram servers must use "wait", as + * they are always invoked with the original datagram socket bound to the + * specified service address. These servers must read at least one datagram + * from the socket before exiting. If a datagram server connects to its + * peer, freeing the socket so inetd can receive further messages on the + * socket, it is said to be a "multi-threaded" server; it should read one + * datagram from the socket and create a new socket connected to the peer. + * It should fork, and the parent should then exit to allow inetd to check + * for new service requests to spawn new servers. Datagram servers which + * process all incoming datagrams on a socket and eventually time out are + * said to be "single-threaded". The comsat(8), biff(1) and talkd(8) + * utilities are both examples of the latter type of datagram server. The + * tftpd(8) utility is an example of a multi-threaded datagram server. + * + * Servers using stream sockets generally are multi-threaded and use the + * "nowait" entry. Connection requests for these services are accepted by + * inetd, and the server is given only the newly-accepted socket connected + * to a client of the service. Most stream-based services operate in this + * manner. Stream-based servers that use "wait" are started with the lis- + * tening service socket, and must accept at least one connection request + * before exiting. Such a server would normally accept and process incoming + * connection requests until a timeout. + */ + +/* Despite of above doc saying that dgram services must use "wait", + * "udp nowait" servers are implemented in busyboxed inetd. + * IPv6 addresses are also implemented. However, they may look ugly - + * ":::service..." means "address '::' (IPv6 wildcard addr)":"service"... + * You have to put "tcp6"/"udp6" in protocol field to select IPv6. + */ + +/* Here's the scoop concerning the user[:group] feature: + * 1) group is not specified: + * a) user = root: NO setuid() or setgid() is done + * b) other: initgroups(name, primary group) + * setgid(primary group as found in passwd) + * setuid() + * 2) group is specified: + * a) user = root: setgid(specified group) + * NO initgroups() + * NO setuid() + * b) other: initgroups(name, specified group) + * setgid(specified group) + * setuid() + */ + +#include <syslog.h> +#include <sys/un.h> + +#include "libbb.h" + +#if ENABLE_FEATURE_INETD_RPC +#include <rpc/rpc.h> +#include <rpc/pmap_clnt.h> +#endif + +#if !BB_MMU +/* stream version of chargen is forking but not execing, + * can't do that (easily) on NOMMU */ +#undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0 +#endif + +#define _PATH_INETDPID "/var/run/inetd.pid" + +#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */ +#define RETRYTIME 60 /* retry after bind or server fail */ + +// TODO: explain, or get rid of setrlimit games + +#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 + +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD \ + || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO \ + || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN \ + || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME \ + || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +# define INETD_BUILTINS_ENABLED +#endif + +typedef struct servtab_t { + /* The most frequently referenced one: */ + int se_fd; /* open descriptor */ + /* NB: 'biggest fields last' saves on code size (~250 bytes) */ + /* [addr:]service socktype proto wait user[:group] prog [args] */ + char *se_local_hostname; /* addr to listen on */ + char *se_service; /* "80" or "www" or "mount/2[-3]" */ + /* socktype is in se_socktype */ /* "stream" "dgram" "raw" "rdm" "seqpacket" */ + char *se_proto; /* "unix" or "[rpc/]tcp[6]" */ +#if ENABLE_FEATURE_INETD_RPC + int se_rpcprog; /* rpc program number */ + int se_rpcver_lo; /* rpc program lowest version */ + int se_rpcver_hi; /* rpc program highest version */ +#define is_rpc_service(sep) ((sep)->se_rpcver_lo != 0) +#else +#define is_rpc_service(sep) 0 +#endif + pid_t se_wait; /* 0:"nowait", 1:"wait", >1:"wait" */ + /* and waiting for this pid */ + socktype_t se_socktype; /* SOCK_STREAM/DGRAM/RDM/... */ + family_t se_family; /* AF_UNIX/INET[6] */ + /* se_proto_no is used by RPC code only... hmm */ + smallint se_proto_no; /* IPPROTO_TCP/UDP, n/a for AF_UNIX */ + smallint se_checked; /* looked at during merge */ + unsigned se_max; /* allowed instances per minute */ + unsigned se_count; /* number started since se_time */ + unsigned se_time; /* when we started counting */ + char *se_user; /* user name to run as */ + char *se_group; /* group name to run as, can be NULL */ +#ifdef INETD_BUILTINS_ENABLED + const struct builtin *se_builtin; /* if built-in, description */ +#endif + struct servtab_t *se_next; + len_and_sockaddr *se_lsa; + char *se_program; /* server program */ +#define MAXARGV 20 + char *se_argv[MAXARGV + 1]; /* program arguments */ +} servtab_t; + +#ifdef INETD_BUILTINS_ENABLED +/* Echo received data */ +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +static void echo_stream(int, servtab_t *); +static void echo_dg(int, servtab_t *); +#endif +/* Internet /dev/null */ +#if ENABLE_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 */ +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME +static void machtime_stream(int, servtab_t *); +static void machtime_dg(int, servtab_t *); +#endif +/* Return human-readable time */ +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +static void daytime_stream(int, servtab_t *); +static void daytime_dg(int, servtab_t *); +#endif +/* Familiar character generator */ +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +static void chargen_stream(int, servtab_t *); +static void chargen_dg(int, servtab_t *); +#endif + +struct builtin { + /* NB: not necessarily NUL terminated */ + char bi_service7[7]; /* internally provided service name */ + uint8_t bi_fork; /* 1 if stream fn should run in child */ + void (*bi_stream_fn)(int, servtab_t *); + void (*bi_dgram_fn)(int, servtab_t *); +}; + +static const struct builtin builtins[] = { +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO + { "echo", 1, echo_stream, echo_dg }, +#endif +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + { "discard", 1, discard_stream, discard_dg }, +#endif +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + { "chargen", 1, chargen_stream, chargen_dg }, +#endif +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME + { "time", 0, machtime_stream, machtime_dg }, +#endif +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + { "daytime", 0, daytime_stream, daytime_dg }, +#endif +}; +#endif /* INETD_BUILTINS_ENABLED */ + +struct globals { + rlim_t rlim_ofile_cur; + struct rlimit rlim_ofile; + servtab_t *serv_list; + int global_queuelen; + int maxsock; /* max fd# in allsock, -1: unknown */ + /* whenever maxsock grows, prev_maxsock is set to new maxsock, + * but if maxsock is set to -1, prev_maxsock is not changed */ + int prev_maxsock; + unsigned max_concurrency; + smallint alarm_armed; + uid_t real_uid; /* user ID who ran us */ + const char *config_filename; + parser_t *parser; + char *default_local_hostname; +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + char *end_ring; + char *ring_pos; + char ring[128]; +#endif + fd_set allsock; + /* Used in next_line(), and as scratch read buffer */ + char line[256]; /* _at least_ 256, see LINE_SIZE */ +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +enum { LINE_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line) }; +struct BUG_G_too_big { + char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1]; +}; +#define rlim_ofile_cur (G.rlim_ofile_cur ) +#define rlim_ofile (G.rlim_ofile ) +#define serv_list (G.serv_list ) +#define global_queuelen (G.global_queuelen) +#define maxsock (G.maxsock ) +#define prev_maxsock (G.prev_maxsock ) +#define max_concurrency (G.max_concurrency) +#define alarm_armed (G.alarm_armed ) +#define real_uid (G.real_uid ) +#define config_filename (G.config_filename) +#define parser (G.parser ) +#define default_local_hostname (G.default_local_hostname) +#define first_ps_byte (G.first_ps_byte ) +#define last_ps_byte (G.last_ps_byte ) +#define end_ring (G.end_ring ) +#define ring_pos (G.ring_pos ) +#define ring (G.ring ) +#define allsock (G.allsock ) +#define line (G.line ) +#define INIT_G() do { \ + rlim_ofile_cur = OPEN_MAX; \ + global_queuelen = 128; \ + config_filename = "/etc/inetd.conf"; \ +} while (0) + +static void maybe_close(int fd) +{ + if (fd >= 0) + close(fd); +} + +// TODO: move to libbb? +static len_and_sockaddr *xzalloc_lsa(int family) +{ + len_and_sockaddr *lsa; + int sz; + + sz = sizeof(struct sockaddr_in); + if (family == AF_UNIX) + sz = sizeof(struct sockaddr_un); +#if ENABLE_FEATURE_IPV6 + if (family == AF_INET6) + sz = sizeof(struct sockaddr_in6); +#endif + lsa = xzalloc(LSA_LEN_SIZE + sz); + lsa->len = sz; + lsa->u.sa.sa_family = family; + return lsa; +} + +static void rearm_alarm(void) +{ + if (!alarm_armed) { + alarm_armed = 1; + alarm(RETRYTIME); + } +} + +static void block_CHLD_HUP_ALRM(sigset_t *m) +{ + sigemptyset(m); + sigaddset(m, SIGCHLD); + sigaddset(m, SIGHUP); + sigaddset(m, SIGALRM); + sigprocmask(SIG_BLOCK, m, m); /* old sigmask is stored in m */ +} + +static void restore_sigmask(sigset_t *m) +{ + sigprocmask(SIG_SETMASK, m, NULL); +} + +#if ENABLE_FEATURE_INETD_RPC +static void register_rpc(servtab_t *sep) +{ + int n; + struct sockaddr_in ir_sin; + socklen_t size; + + size = sizeof(ir_sin); + if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) { + bb_perror_msg("getsockname"); + return; + } + + for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) { + pmap_unset(sep->se_rpcprog, n); + if (!pmap_set(sep->se_rpcprog, n, sep->se_proto_no, 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, sep->se_proto_no, ntohs(ir_sin.sin_port)); + } +} + +static void unregister_rpc(servtab_t *sep) +{ + int n; + + for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) { + if (!pmap_unset(sep->se_rpcprog, n)) + bb_perror_msg("pmap_unset(%u,%u)", sep->se_rpcprog, n); + } +} +#endif /* FEATURE_INETD_RPC */ + +static void bump_nofile(void) +{ + enum { FD_CHUNK = 32 }; + struct rlimit rl; + + /* Never fails under Linux (except if you pass it bad arguments) */ + getrlimit(RLIMIT_NOFILE, &rl); + 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("can't extend file limit, max = %d", + (int) rl.rlim_cur); + return; + } + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("setrlimit"); + return; + } + + rlim_ofile_cur = rl.rlim_cur; +} + +static void remove_fd_from_set(int fd) +{ + if (fd >= 0) { + FD_CLR(fd, &allsock); + maxsock = -1; + } +} + +static void add_fd_to_set(int fd) +{ + if (fd >= 0) { + FD_SET(fd, &allsock); + if (maxsock >= 0 && fd > maxsock) { + prev_maxsock = maxsock = fd; + if ((rlim_t)fd > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); + } + } +} + +static void recalculate_maxsock(void) +{ + int fd = 0; + + /* We may have no services, in this case maxsock should still be >= 0 + * (code elsewhere is not happy with maxsock == -1) */ + maxsock = 0; + while (fd <= prev_maxsock) { + if (FD_ISSET(fd, &allsock)) + maxsock = fd; + fd++; + } + prev_maxsock = maxsock; + if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); +} + +static void prepare_socket_fd(servtab_t *sep) +{ + int r, fd; + + fd = socket(sep->se_family, sep->se_socktype, 0); + if (fd < 0) { + bb_perror_msg("socket"); + return; + } + setsockopt_reuseaddr(fd); + +#if ENABLE_FEATURE_INETD_RPC + if (is_rpc_service(sep)) { + struct passwd *pwd; + + /* zero out the port for all RPC services; let bind() + * find one. */ + set_nport(sep->se_lsa, 0); + + /* for RPC services, attempt to use a reserved port + * if they are going to be running as root. */ + if (real_uid == 0 && sep->se_family == AF_INET + && (pwd = getpwnam(sep->se_user)) != NULL + && pwd->pw_uid == 0 + ) { + r = bindresvport(fd, &sep->se_lsa->u.sin); + } else { + r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len); + } + if (r == 0) { + int saveerrno = errno; + /* update lsa with port# */ + getsockname(fd, &sep->se_lsa->u.sa, &sep->se_lsa->len); + errno = saveerrno; + } + } else +#endif + { + if (sep->se_family == AF_UNIX) { + struct sockaddr_un *sun; + sun = (struct sockaddr_un*)&(sep->se_lsa->u.sa); + unlink(sun->sun_path); + } + r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len); + } + if (r < 0) { + bb_perror_msg("%s/%s: bind", + sep->se_service, sep->se_proto); + close(fd); + rearm_alarm(); + return; + } + if (sep->se_socktype == SOCK_STREAM) + listen(fd, global_queuelen); + + add_fd_to_set(fd); + sep->se_fd = fd; +} + +static int reopen_config_file(void) +{ + free(default_local_hostname); + default_local_hostname = xstrdup("*"); + if (parser != NULL) + config_close(parser); + parser = config_open(config_filename); + return (parser != NULL); +} + +static void close_config_file(void) +{ + if (parser) { + config_close(parser); + parser = NULL; + } +} + +static void free_servtab_strings(servtab_t *cp) +{ + int i; + + free(cp->se_local_hostname); + free(cp->se_service); + free(cp->se_proto); + free(cp->se_user); + free(cp->se_group); + free(cp->se_lsa); /* not a string in fact */ + free(cp->se_program); + for (i = 0; i < MAXARGV; i++) + free(cp->se_argv[i]); +} + +static servtab_t *new_servtab(void) +{ + servtab_t *newtab = xzalloc(sizeof(servtab_t)); + newtab->se_fd = -1; /* paranoia */ + return newtab; +} + +static servtab_t *dup_servtab(servtab_t *sep) +{ + servtab_t *newtab; + int argc; + + newtab = new_servtab(); + *newtab = *sep; /* struct copy */ + /* deep-copying strings */ + newtab->se_service = xstrdup(newtab->se_service); + newtab->se_proto = xstrdup(newtab->se_proto); + newtab->se_user = xstrdup(newtab->se_user); + newtab->se_group = xstrdup(newtab->se_group); + newtab->se_program = xstrdup(newtab->se_program); + for (argc = 0; argc <= MAXARGV; argc++) + newtab->se_argv[argc] = xstrdup(newtab->se_argv[argc]); + /* NB: se_fd, se_hostaddr and se_next are always + * overwrittend by callers, so we don't bother resetting them + * to NULL/0/-1 etc */ + + return newtab; +} + +/* gcc generates much more code if this is inlined */ +static servtab_t *parse_one_line(void) +{ + int argc; + char *token[6+MAXARGV]; + char *p, *arg; + char *hostdelim; + servtab_t *sep; + servtab_t *nsep; + new: + sep = new_servtab(); + more: + argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL); + if (!argc) { + free(sep); + return NULL; + } + + /* [host:]service socktype proto wait user[:group] prog [args] */ + /* Check for "host:...." line */ + arg = token[0]; + hostdelim = strrchr(arg, ':'); + if (hostdelim) { + *hostdelim = '\0'; + sep->se_local_hostname = xstrdup(arg); + arg = hostdelim + 1; + if (*arg == '\0' && argc == 1) { + /* Line has just "host:", change the + * default host for the following lines. */ + free(default_local_hostname); + default_local_hostname = sep->se_local_hostname; + goto more; + } + } else + sep->se_local_hostname = xstrdup(default_local_hostname); + + /* service socktype proto wait user[:group] prog [args] */ + sep->se_service = xstrdup(arg); + + /* socktype proto wait user[:group] prog [args] */ + if (argc < 6) { + parse_err: + bb_error_msg("parse error on line %u, line is ignored", + parser->lineno); + free_servtab_strings(sep); + /* Just "goto more" can make sep to carry over e.g. + * "rpc"-ness (by having se_rpcver_lo != 0). + * We will be more paranoid: */ + free(sep); + goto new; + } + + { + static int8_t SOCK_xxx[] ALIGN1 = { + -1, + SOCK_STREAM, SOCK_DGRAM, SOCK_RDM, + SOCK_SEQPACKET, SOCK_RAW + }; + sep->se_socktype = SOCK_xxx[1 + index_in_strings( + "stream""\0" "dgram""\0" "rdm""\0" + "seqpacket""\0" "raw""\0" + , token[1])]; + } + + /* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */ + sep->se_proto = arg = xstrdup(token[2]); + if (strcmp(arg, "unix") == 0) { + sep->se_family = AF_UNIX; + } else { + char *six; + sep->se_family = AF_INET; + six = last_char_is(arg, '6'); + if (six) { +#if ENABLE_FEATURE_IPV6 + *six = '\0'; + sep->se_family = AF_INET6; +#else + bb_error_msg("%s: no support for IPv6", sep->se_proto); + goto parse_err; +#endif + } + if (strncmp(arg, "rpc/", 4) == 0) { +#if ENABLE_FEATURE_INETD_RPC + unsigned n; + arg += 4; + p = strchr(sep->se_service, '/'); + if (p == NULL) { + bb_error_msg("no rpc version: '%s'", sep->se_service); + goto parse_err; + } + *p++ = '\0'; + n = bb_strtou(p, &p, 10); + if (n > INT_MAX) { + bad_ver_spec: + bb_error_msg("bad rpc version"); + goto parse_err; + } + sep->se_rpcver_lo = sep->se_rpcver_hi = n; + if (*p == '-') { + p++; + n = bb_strtou(p, &p, 10); + if (n > INT_MAX || (int)n < sep->se_rpcver_lo) + goto bad_ver_spec; + sep->se_rpcver_hi = n; + } + if (*p != '\0') + goto bad_ver_spec; +#else + bb_error_msg("no support for rpc services"); + goto parse_err; +#endif + } + /* we don't really need getprotobyname()! */ + if (strcmp(arg, "tcp") == 0) + sep->se_proto_no = IPPROTO_TCP; /* = 6 */ + if (strcmp(arg, "udp") == 0) + sep->se_proto_no = IPPROTO_UDP; /* = 17 */ + if (six) + *six = '6'; + if (!sep->se_proto_no) /* not tcp/udp?? */ + goto parse_err; + } + + /* [no]wait[.max] user[:group] prog [args] */ + arg = token[3]; + sep->se_max = max_concurrency; + p = strchr(arg, '.'); + if (p) { + *p++ = '\0'; + sep->se_max = bb_strtou(p, NULL, 10); + if (errno) + goto parse_err; + } + sep->se_wait = (arg[0] != 'n' || arg[1] != 'o'); + if (!sep->se_wait) /* "no" seen */ + arg += 2; + if (strcmp(arg, "wait") != 0) + goto parse_err; + + /* user[:group] prog [args] */ + sep->se_user = xstrdup(token[4]); + arg = strchr(sep->se_user, '.'); + if (arg == NULL) + arg = strchr(sep->se_user, ':'); + if (arg) { + *arg++ = '\0'; + sep->se_group = xstrdup(arg); + } + + /* prog [args] */ + sep->se_program = xstrdup(token[5]); +#ifdef INETD_BUILTINS_ENABLED + if (strcmp(sep->se_program, "internal") == 0 + && strlen(sep->se_service) <= 7 + && (sep->se_socktype == SOCK_STREAM + || sep->se_socktype == SOCK_DGRAM) + ) { + unsigned i; + for (i = 0; i < ARRAY_SIZE(builtins); i++) + if (strncmp(builtins[i].bi_service7, sep->se_service, 7) == 0) + goto found_bi; + bb_error_msg("unknown internal service %s", sep->se_service); + goto parse_err; + found_bi: + sep->se_builtin = &builtins[i]; + /* stream builtins must be "nowait", dgram must be "wait" */ + if (sep->se_wait != (sep->se_socktype == SOCK_DGRAM)) + goto parse_err; + } +#endif + argc = 0; + while ((arg = token[6+argc]) != NULL && argc < MAXARGV) + sep->se_argv[argc++] = xstrdup(arg); + + /* catch mixups. "<service> stream udp ..." == wtf */ + if (sep->se_socktype == SOCK_STREAM) { + if (sep->se_proto_no == IPPROTO_UDP) + goto parse_err; + } + if (sep->se_socktype == SOCK_DGRAM) { + if (sep->se_proto_no == IPPROTO_TCP) + goto parse_err; + } + +// bb_info_msg( +// "ENTRY[%s][%s][%s][%d][%d][%d][%d][%d][%s][%s][%s]", +// sep->se_local_hostname, sep->se_service, sep->se_proto, sep->se_wait, sep->se_proto_no, +// sep->se_max, sep->se_count, sep->se_time, sep->se_user, sep->se_group, sep->se_program); + + /* check if the hostname specifier is a comma separated list + * of hostnames. we'll make new entries for each address. */ + while ((hostdelim = strrchr(sep->se_local_hostname, ',')) != NULL) { + nsep = dup_servtab(sep); + /* NUL terminate the hostname field of the existing entry, + * and make a dup for the new entry. */ + *hostdelim++ = '\0'; + nsep->se_local_hostname = xstrdup(hostdelim); + nsep->se_next = sep->se_next; + sep->se_next = nsep; + } + + /* was doing it here: */ + /* DNS resolution, create copies for each IP address */ + /* IPv6-ization destroyed it :( */ + + return sep; +} + +static servtab_t *insert_in_servlist(servtab_t *cp) +{ + servtab_t *sep; + sigset_t omask; + + sep = new_servtab(); + *sep = *cp; /* struct copy */ + sep->se_fd = -1; +#if ENABLE_FEATURE_INETD_RPC + sep->se_rpcprog = -1; +#endif + block_CHLD_HUP_ALRM(&omask); + sep->se_next = serv_list; + serv_list = sep; + restore_sigmask(&omask); + return sep; +} + +static int same_serv_addr_proto(servtab_t *old, servtab_t *new) +{ + if (strcmp(old->se_local_hostname, new->se_local_hostname) != 0) + return 0; + if (strcmp(old->se_service, new->se_service) != 0) + return 0; + if (strcmp(old->se_proto, new->se_proto) != 0) + return 0; + return 1; +} + +static void reread_config_file(int sig UNUSED_PARAM) +{ + servtab_t *sep, *cp, **sepp; + len_and_sockaddr *lsa; + sigset_t omask; + unsigned n; + uint16_t port; + int save_errno = errno; + + if (!reopen_config_file()) + goto ret; + for (sep = serv_list; sep; sep = sep->se_next) + sep->se_checked = 0; + + goto first_line; + while (1) { + if (cp == NULL) { + first_line: + cp = parse_one_line(); + if (cp == NULL) + break; + } + for (sep = serv_list; sep; sep = sep->se_next) + if (same_serv_addr_proto(sep, cp)) + goto equal_servtab; + /* not an "equal" servtab */ + sep = insert_in_servlist(cp); + goto after_check; + equal_servtab: + { + int i; + + block_CHLD_HUP_ALRM(&omask); +#if ENABLE_FEATURE_INETD_RPC + if (is_rpc_service(sep)) + unregister_rpc(sep); + sep->se_rpcver_lo = cp->se_rpcver_lo; + sep->se_rpcver_hi = cp->se_rpcver_hi; +#endif + if (cp->se_wait == 0) { + /* New config says "nowait". If old one + * was "wait", we currently may be waiting + * for a child (and not accepting connects). + * Stop waiting, start listening again. + * (if it's not true, this op is harmless) */ + add_fd_to_set(sep->se_fd); + } + sep->se_wait = cp->se_wait; + sep->se_max = cp->se_max; + /* string fields need more love - we don't want to leak them */ +#define SWAP(type, a, b) do { type c = (type)a; a = (type)b; b = (type)c; } while (0) + SWAP(char*, sep->se_user, cp->se_user); + SWAP(char*, sep->se_group, cp->se_group); + SWAP(char*, sep->se_program, cp->se_program); + for (i = 0; i < MAXARGV; i++) + SWAP(char*, sep->se_argv[i], cp->se_argv[i]); +#undef SWAP + restore_sigmask(&omask); + free_servtab_strings(cp); + } + after_check: + /* cp->string_fields are consumed by insert_in_servlist() + * or freed at this point, cp itself is not yet freed. */ + sep->se_checked = 1; + + /* create new len_and_sockaddr */ + switch (sep->se_family) { + struct sockaddr_un *sun; + case AF_UNIX: + lsa = xzalloc_lsa(AF_UNIX); + sun = (struct sockaddr_un*)&lsa->u.sa; + safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path)); + break; + + default: /* case AF_INET, case AF_INET6 */ + n = bb_strtou(sep->se_service, NULL, 10); +#if ENABLE_FEATURE_INETD_RPC + if (is_rpc_service(sep)) { + sep->se_rpcprog = n; + if (errno) { /* se_service is not numeric */ + struct rpcent *rp = getrpcbyname(sep->se_service); + if (rp == NULL) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto next_cp; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + prepare_socket_fd(sep); + if (sep->se_fd != -1) + register_rpc(sep); + goto next_cp; + } +#endif + /* what port to listen on? */ + port = htons(n); + if (errno || n > 0xffff) { /* se_service is not numeric */ + char protoname[4]; + struct servent *sp; + /* can result only in "tcp" or "udp": */ + safe_strncpy(protoname, sep->se_proto, 4); + sp = getservbyname(sep->se_service, protoname); + if (sp == NULL) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto next_cp; + } + port = sp->s_port; + } + if (LONE_CHAR(sep->se_local_hostname, '*')) { + lsa = xzalloc_lsa(sep->se_family); + set_nport(lsa, port); + } else { + lsa = host_and_af2sockaddr(sep->se_local_hostname, + ntohs(port), sep->se_family); + if (!lsa) { + bb_error_msg("%s/%s: unknown host '%s'", + sep->se_service, sep->se_proto, + sep->se_local_hostname); + goto next_cp; + } + } + break; + } /* end of "switch (sep->se_family)" */ + + /* did lsa change? Then close/open */ + if (sep->se_lsa == NULL + || lsa->len != sep->se_lsa->len + || memcmp(&lsa->u.sa, &sep->se_lsa->u.sa, lsa->len) != 0 + ) { + remove_fd_from_set(sep->se_fd); + maybe_close(sep->se_fd); + free(sep->se_lsa); + sep->se_lsa = lsa; + sep->se_fd = -1; + } else { + free(lsa); + } + if (sep->se_fd == -1) + prepare_socket_fd(sep); + next_cp: + sep = cp->se_next; + free(cp); + cp = sep; + } /* end of "while (1) parse lines" */ + close_config_file(); + + /* Purge anything not looked at above - these are stale entries, + * new config file doesnt have them. */ + block_CHLD_HUP_ALRM(&omask); + sepp = &serv_list; + while ((sep = *sepp)) { + if (sep->se_checked) { + sepp = &sep->se_next; + continue; + } + *sepp = sep->se_next; + remove_fd_from_set(sep->se_fd); + maybe_close(sep->se_fd); +#if ENABLE_FEATURE_INETD_RPC + if (is_rpc_service(sep)) + unregister_rpc(sep); +#endif + if (sep->se_family == AF_UNIX) + unlink(sep->se_service); + free_servtab_strings(sep); + free(sep); + } + restore_sigmask(&omask); + ret: + errno = save_errno; +} + +static void reap_child(int sig UNUSED_PARAM) +{ + pid_t pid; + int status; + servtab_t *sep; + int save_errno = errno; + + for (;;) { + pid = wait_any_nohang(&status); + if (pid <= 0) + break; + for (sep = serv_list; sep; sep = sep->se_next) { + if (sep->se_wait != pid) + continue; + /* One of our "wait" services */ + if (WIFEXITED(status) && WEXITSTATUS(status)) + bb_error_msg("%s: exit status 0x%x", + sep->se_program, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + bb_error_msg("%s: exit signal 0x%x", + sep->se_program, WTERMSIG(status)); + sep->se_wait = 1; + add_fd_to_set(sep->se_fd); + break; + } + } + errno = save_errno; +} + +static void retry_network_setup(int sig UNUSED_PARAM) +{ + int save_errno = errno; + servtab_t *sep; + + alarm_armed = 0; + for (sep = serv_list; sep; sep = sep->se_next) { + if (sep->se_fd == -1) { + prepare_socket_fd(sep); +#if ENABLE_FEATURE_INETD_RPC + if (sep->se_fd != -1 && is_rpc_service(sep)) + register_rpc(sep); +#endif + } + } + errno = save_errno; +} + +static void clean_up_and_exit(int sig UNUSED_PARAM) +{ + servtab_t *sep; + + /* XXX signal race walking sep list */ + for (sep = serv_list; sep; sep = sep->se_next) { + if (sep->se_fd == -1) + continue; + + switch (sep->se_family) { + case AF_UNIX: + unlink(sep->se_service); + break; + default: /* case AF_INET, AF_INET6 */ +#if ENABLE_FEATURE_INETD_RPC + if (sep->se_wait == 1 && is_rpc_service(sep)) + unregister_rpc(sep); /* XXX signal race */ +#endif + break; + } + if (ENABLE_FEATURE_CLEAN_UP) + close(sep->se_fd); + } + remove_pidfile(_PATH_INETDPID); + exit(EXIT_SUCCESS); +} + +int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int inetd_main(int argc UNUSED_PARAM, char **argv) +{ + struct sigaction sa, saved_pipe_handler; + servtab_t *sep, *sep2; + struct passwd *pwd; + struct group *grp = grp; /* for compiler */ + int opt; + pid_t pid; + sigset_t omask; + + INIT_G(); + + real_uid = getuid(); + if (real_uid != 0) /* run by non-root user */ + config_filename = NULL; + + opt_complementary = "R+:q+"; /* -q N, -R N */ + opt = getopt32(argv, "R:feq:", &max_concurrency, &global_queuelen); + argv += optind; + //argc -= optind; + if (argv[0]) + config_filename = argv[0]; + if (config_filename == NULL) + bb_error_msg_and_die("non-root must specify config file"); + if (!(opt & 2)) + bb_daemonize_or_rexec(0, argv - optind); + else + bb_sanitize_stdio(); + if (!(opt & 4)) { + openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + if (real_uid == 0) { + /* run by root, ensure groups vector gets trashed */ + gid_t gid = getgid(); + setgroups(1, &gid); + } + + write_pidfile(_PATH_INETDPID); + + /* never fails under Linux (except if you pass it bad arguments) */ + getrlimit(RLIMIT_NOFILE, &rlim_ofile); + rlim_ofile_cur = rlim_ofile.rlim_cur; + if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ + rlim_ofile_cur = OPEN_MAX; + + memset(&sa, 0, sizeof(sa)); + /*sigemptyset(&sa.sa_mask); - memset did it */ + sigaddset(&sa.sa_mask, SIGALRM); + sigaddset(&sa.sa_mask, SIGCHLD); + sigaddset(&sa.sa_mask, SIGHUP); + sa.sa_handler = retry_network_setup; + sigaction_set(SIGALRM, &sa); + sa.sa_handler = reread_config_file; + sigaction_set(SIGHUP, &sa); + sa.sa_handler = reap_child; + sigaction_set(SIGCHLD, &sa); + sa.sa_handler = clean_up_and_exit; + sigaction_set(SIGTERM, &sa); + sa.sa_handler = clean_up_and_exit; + sigaction_set(SIGINT, &sa); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, &saved_pipe_handler); + + reread_config_file(SIGHUP); /* load config from file */ + + for (;;) { + int ready_fd_cnt; + int ctrl, accepted_fd, new_udp_fd; + fd_set readable; + + if (maxsock < 0) + recalculate_maxsock(); + + readable = allsock; /* struct copy */ + /* if there are no fds to wait on, we will block + * until signal wakes us up (maxsock == 0, but readable + * never contains fds 0 and 1...) */ + ready_fd_cnt = select(maxsock + 1, &readable, NULL, NULL, NULL); + if (ready_fd_cnt < 0) { + if (errno != EINTR) { + bb_perror_msg("select"); + sleep(1); + } + continue; + } + + for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) { + if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) + continue; + + ready_fd_cnt--; + ctrl = sep->se_fd; + accepted_fd = -1; + new_udp_fd = -1; + if (!sep->se_wait) { + if (sep->se_socktype == SOCK_STREAM) { + ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL); + if (ctrl < 0) { + if (errno != EINTR) + bb_perror_msg("accept (for %s)", sep->se_service); + continue; + } + } + /* "nowait" udp */ + if (sep->se_socktype == SOCK_DGRAM + && sep->se_family != AF_UNIX + ) { +/* How udp "nowait" works: + * child peeks at (received and buffered by kernel) UDP packet, + * performs connect() on the socket so that it is linked only + * to this peer. But this also affects parent, because descriptors + * are shared after fork() a-la dup(). When parent performs + * select(), it will see this descriptor connected to the peer (!) + * and still readable, will act on it and mess things up + * (can create many copies of same child, etc). + * Parent must create and use new socket instead. */ + new_udp_fd = socket(sep->se_family, SOCK_DGRAM, 0); + if (new_udp_fd < 0) { /* error: eat packet, forget about it */ + udp_err: + recv(sep->se_fd, line, LINE_SIZE, MSG_DONTWAIT); + continue; + } + setsockopt_reuseaddr(new_udp_fd); + /* TODO: better do bind after vfork in parent, + * so that we don't have two wildcard bound sockets + * even for a brief moment? */ + if (bind(new_udp_fd, &sep->se_lsa->u.sa, sep->se_lsa->len) < 0) { + close(new_udp_fd); + goto udp_err; + } + } + } + + block_CHLD_HUP_ALRM(&omask); + pid = 0; +#ifdef INETD_BUILTINS_ENABLED + /* do we need to fork? */ + if (sep->se_builtin == NULL + || (sep->se_socktype == SOCK_STREAM + && sep->se_builtin->bi_fork)) +#endif + { + if (sep->se_max != 0) { + if (++sep->se_count == 1) + sep->se_time = monotonic_sec(); + else if (sep->se_count >= sep->se_max) { + unsigned now = monotonic_sec(); + /* did we accumulate se_max connects too quickly? */ + if (now - sep->se_time <= CNT_INTERVAL) { + bb_error_msg("%s/%s: too many connections, pausing", + sep->se_service, sep->se_proto); + remove_fd_from_set(sep->se_fd); + close(sep->se_fd); + sep->se_fd = -1; + sep->se_count = 0; + rearm_alarm(); /* will revive it in RETRYTIME sec */ + restore_sigmask(&omask); + maybe_close(accepted_fd); + continue; /* -> check next fd in fd set */ + } + sep->se_count = 0; + } + } + /* on NOMMU, streamed chargen + * builtin wouldn't work, but it is + * not allowed on NOMMU (ifdefed out) */ +#ifdef INETD_BUILTINS_ENABLED + if (BB_MMU && sep->se_builtin) + pid = fork(); + else +#endif + pid = vfork(); + + if (pid < 0) { /* fork error */ + bb_perror_msg("fork"); + sleep(1); + restore_sigmask(&omask); + maybe_close(accepted_fd); + continue; /* -> check next fd in fd set */ + } + if (pid == 0) + pid--; /* -1: "we did fork and we are child" */ + } + /* if pid == 0 here, we never forked */ + + if (pid > 0) { /* parent */ + if (sep->se_wait) { + /* tcp wait: we passed listening socket to child, + * will wait for child to terminate */ + sep->se_wait = pid; + remove_fd_from_set(sep->se_fd); + } + if (new_udp_fd >= 0) { + /* udp nowait: child connected the socket, + * we created and will use new, unconnected one */ + xmove_fd(new_udp_fd, sep->se_fd); + } + restore_sigmask(&omask); + maybe_close(accepted_fd); + continue; /* -> check next fd in fd set */ + } + + /* we are either child or didn't vfork at all */ +#ifdef INETD_BUILTINS_ENABLED + if (sep->se_builtin) { + if (pid) { /* "pid" is -1: we did vfork */ + close(sep->se_fd); /* listening socket */ + logmode = 0; /* make xwrite etc silent */ + } + restore_sigmask(&omask); + if (sep->se_socktype == SOCK_STREAM) + sep->se_builtin->bi_stream_fn(ctrl, sep); + else + sep->se_builtin->bi_dgram_fn(ctrl, sep); + if (pid) /* we did vfork */ + _exit(EXIT_FAILURE); + maybe_close(accepted_fd); + continue; /* -> check next fd in fd set */ + } +#endif + /* child */ + setsid(); + /* "nowait" udp */ + if (new_udp_fd >= 0) { + len_and_sockaddr *lsa = xzalloc_lsa(sep->se_family); + /* peek at the packet and remember peer addr */ + int r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT, + &lsa->u.sa, &lsa->len); + if (r < 0) + goto do_exit1; + /* make this socket "connected" to peer addr: + * only packets from this peer will be recv'ed, + * and bare write()/send() will work on it */ + connect(ctrl, &lsa->u.sa, lsa->len); + free(lsa); + } + /* prepare env and exec program */ + pwd = getpwnam(sep->se_user); + if (pwd == NULL) { + bb_error_msg("%s: no such %s", sep->se_user, "user"); + goto do_exit1; + } + if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { + bb_error_msg("%s: no such %s", sep->se_group, "group"); + goto do_exit1; + } + if (real_uid != 0 && real_uid != pwd->pw_uid) { + /* a user running private inetd */ + bb_error_msg("non-root must run services as himself"); + goto do_exit1; + } + if (pwd->pw_uid) { + if (sep->se_group) + pwd->pw_gid = grp->gr_gid; + /* initgroups, setgid, setuid: */ + change_identity(pwd); + } else if (sep->se_group) { + xsetgid(grp->gr_gid); + setgroups(1, &grp->gr_gid); + } + if (rlim_ofile.rlim_cur != rlim_ofile_cur) + if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) + bb_perror_msg("setrlimit"); + closelog(); + xmove_fd(ctrl, 0); + xdup2(0, 1); + xdup2(0, 2); + /* NB: among others, this loop closes listening socket + * for nowait stream children */ + for (sep2 = serv_list; sep2; sep2 = sep2->se_next) + maybe_close(sep2->se_fd); + sigaction_set(SIGPIPE, &saved_pipe_handler); + restore_sigmask(&omask); + BB_EXECVP(sep->se_program, sep->se_argv); + bb_perror_msg("exec %s", sep->se_program); + do_exit1: + /* eat packet in udp case */ + if (sep->se_socktype != SOCK_STREAM) + recv(0, line, LINE_SIZE, MSG_DONTWAIT); + _exit(EXIT_FAILURE); + } /* for (sep = servtab...) */ + } /* for (;;) */ +} + +#if !BB_MMU +static const char *const cat_args[] = { "cat", NULL }; +#endif + +/* + * Internet services provided internally by inetd: + */ +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +/* Echo service -- echo data back. */ +/* ARGSUSED */ +static void echo_stream(int s, servtab_t *sep UNUSED_PARAM) +{ +#if BB_MMU + while (1) { + ssize_t sz = safe_read(s, line, LINE_SIZE); + if (sz <= 0) + break; + xwrite(s, line, sz); + } +#else + /* We are after vfork here! */ + /* move network socket to stdin/stdout */ + xmove_fd(s, STDIN_FILENO); + xdup2(STDIN_FILENO, STDOUT_FILENO); + /* no error messages please... */ + close(STDERR_FILENO); + xopen(bb_dev_null, O_WRONLY); + BB_EXECVP("cat", (char**)cat_args); + /* on failure we return to main, which does exit(EXIT_FAILURE) */ +#endif +} +static void echo_dg(int s, servtab_t *sep) +{ + enum { BUFSIZE = 12*1024 }; /* for jumbo sized packets! :) */ + char *buf = xmalloc(BUFSIZE); /* too big for stack */ + int sz; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); + + lsa->len = sep->se_lsa->len; + /* dgram builtins are non-forking - DONT BLOCK! */ + sz = recvfrom(s, buf, BUFSIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len); + if (sz > 0) + sendto(s, buf, sz, 0, &lsa->u.sa, lsa->len); + free(buf); +} +#endif /* FEATURE_INETD_SUPPORT_BUILTIN_ECHO */ + + +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +/* Discard service -- ignore data. */ +/* ARGSUSED */ +static void discard_stream(int s, servtab_t *sep UNUSED_PARAM) +{ +#if BB_MMU + while (safe_read(s, line, LINE_SIZE) > 0) + continue; +#else + /* We are after vfork here! */ + /* move network socket to stdin */ + xmove_fd(s, STDIN_FILENO); + /* discard output */ + close(STDOUT_FILENO); + xopen(bb_dev_null, O_WRONLY); + /* no error messages please... */ + xdup2(STDOUT_FILENO, STDERR_FILENO); + BB_EXECVP("cat", (char**)cat_args); + /* on failure we return to main, which does exit(EXIT_FAILURE) */ +#endif +} +/* ARGSUSED */ +static void discard_dg(int s, servtab_t *sep UNUSED_PARAM) +{ + /* dgram builtins are non-forking - DONT BLOCK! */ + recv(s, line, LINE_SIZE, MSG_DONTWAIT); +} +#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */ + + +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +#define LINESIZ 72 +static void init_ring(void) +{ + int i; + + end_ring = ring; + for (i = 0; i <= 128; ++i) + if (isprint(i)) + *end_ring++ = i; +} +/* Character generator. MMU arches only. */ +/* ARGSUSED */ +static void chargen_stream(int s, servtab_t *sep UNUSED_PARAM) +{ + char *rs; + int len; + char text[LINESIZ + 2]; + + if (!end_ring) { + init_ring(); + rs = ring; + } + + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + rs = ring; + for (;;) { + len = end_ring - rs; + if (len >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == end_ring) + rs = ring; + xwrite(s, text, sizeof(text)); + } +} +/* ARGSUSED */ +static void chargen_dg(int s, servtab_t *sep) +{ + int len; + char text[LINESIZ + 2]; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); + + /* Eat UDP packet which started it all */ + /* dgram builtins are non-forking - DONT BLOCK! */ + lsa->len = sep->se_lsa->len; + if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) + return; + + if (!end_ring) { + init_ring(); + ring_pos = ring; + } + + len = end_ring - ring_pos; + if (len >= LINESIZ) + memmove(text, ring_pos, LINESIZ); + else { + memmove(text, ring_pos, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++ring_pos == end_ring) + ring_pos = ring; + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len); +} +#endif /* FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */ + + +#if ENABLE_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 uint32_t machtime(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return htonl((uint32_t)(tv.tv_sec + 2208988800)); +} +/* ARGSUSED */ +static void machtime_stream(int s, servtab_t *sep UNUSED_PARAM) +{ + uint32_t result; + + result = machtime(); + full_write(s, &result, sizeof(result)); +} +static void machtime_dg(int s, servtab_t *sep) +{ + uint32_t result; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); + + lsa->len = sep->se_lsa->len; + if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) + return; + + result = machtime(); + sendto(s, &result, sizeof(result), 0, &lsa->u.sa, lsa->len); +} +#endif /* FEATURE_INETD_SUPPORT_BUILTIN_TIME */ + + +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +/* Return human-readable time of day */ +/* ARGSUSED */ +static void daytime_stream(int s, servtab_t *sep UNUSED_PARAM) +{ + time_t t; + + t = time(NULL); + fdprintf(s, "%.24s\r\n", ctime(&t)); +} +static void daytime_dg(int s, servtab_t *sep) +{ + time_t t; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); + + lsa->len = sep->se_lsa->len; + if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) + return; + + t = time(NULL); + sprintf(line, "%.24s\r\n", ctime(&t)); + sendto(s, line, strlen(line), 0, &lsa->u.sa, lsa->len); +} +#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */ diff --git a/networking/interface.c b/networking/interface.c new file mode 100644 index 0000000..7861b9f --- /dev/null +++ b/networking/interface.c @@ -0,0 +1,1282 @@ +/* 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. + * + * + * 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 <net/if.h> +#include <net/if_arp.h> +#include "inet_common.h" +#include "libbb.h" + + +#if ENABLE_FEATURE_HWIB +/* #include <linux/if_infiniband.h> */ +#undef INFINIBAND_ALEN +#define INFINIBAND_ALEN 20 +#endif + +#if ENABLE_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 + +/* Display an Internet socket address. */ +static const char* FAST_FUNC INET_sprint(struct sockaddr *sap, int numeric) +{ + static char *buff; + + free(buff); + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return "[NONE SET]"; + buff = INET_rresolve((struct sockaddr_in *) sap, numeric, 0xffffff00); + return buff; +} + +#ifdef UNUSED_AND_BUGGY +static int INET_getsock(char *bufp, struct sockaddr *sap) +{ + char *sp = bufp, *bp; + unsigned int i; + unsigned val; + struct sockaddr_in *sock_in; + + sock_in = (struct sockaddr_in *) sap; + sock_in->sin_family = AF_INET; + sock_in->sin_port = 0; + + val = 0; + bp = (char *) &val; + for (i = 0; i < sizeof(sock_in->sin_addr.s_addr); i++) { + *sp = toupper(*sp); + + if ((unsigned)(*sp - 'A') <= 5) + bp[i] |= (int) (*sp - ('A' - 10)); + else if (isdigit(*sp)) + bp[i] |= (int) (*sp - '0'); + else + return -1; + + bp[i] <<= 4; + sp++; + *sp = toupper(*sp); + + if ((unsigned)(*sp - 'A') <= 5) + bp[i] |= (int) (*sp - ('A' - 10)); + else if (isdigit(*sp)) + bp[i] |= (int) (*sp - '0'); + else + return -1; + + sp++; + } + sock_in->sin_addr.s_addr = htonl(val); + + return (sp - bufp); +} +#endif + +static int FAST_FUNC INET_input(/*int type,*/ const char *bufp, struct sockaddr *sap) +{ + return INET_resolve(bufp, (struct sockaddr_in *) sap, 0); +/* + switch (type) { + case 1: + return (INET_getsock(bufp, sap)); + case 256: + return (INET_resolve(bufp, (struct sockaddr_in *) sap, 1)); + default: + return (INET_resolve(bufp, (struct sockaddr_in *) sap, 0)); + } +*/ +} + +static const struct aftype inet_aftype = { + .name = "inet", + .title = "DARPA Internet", + .af = AF_INET, + .alen = 4, + .sprint = INET_sprint, + .input = INET_input, +}; + +#ifdef HAVE_AFINET6 + +/* Display an Internet socket address. */ +/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */ +static const char* FAST_FUNC INET6_sprint(struct sockaddr *sap, int numeric) +{ + static char *buff; + + free(buff); + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return "[NONE SET]"; + buff = INET6_rresolve((struct sockaddr_in6 *) sap, numeric); + return buff; +} + +#ifdef UNUSED +static int INET6_getsock(char *bufp, struct sockaddr *sap) +{ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *) sap; + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + + if (inet_pton(AF_INET6, bufp, sin6->sin6_addr.s6_addr) <= 0) + return -1; + + return 16; /* ?;) */ +} +#endif + +static int FAST_FUNC INET6_input(/*int type,*/ const char *bufp, struct sockaddr *sap) +{ + return INET6_resolve(bufp, (struct sockaddr_in6 *) sap); +/* + switch (type) { + case 1: + return (INET6_getsock(bufp, sap)); + default: + return (INET6_resolve(bufp, (struct sockaddr_in6 *) sap)); + } +*/ +} + +static const struct aftype inet6_aftype = { + .name = "inet6", + .title = "IPv6", + .af = AF_INET6, + .alen = sizeof(struct in6_addr), + .sprint = INET6_sprint, + .input = INET6_input, +}; + +#endif /* HAVE_AFINET6 */ + +/* Display an UNSPEC address. */ +static char* FAST_FUNC UNSPEC_print(unsigned char *ptr) +{ + static char *buff; + + char *pos; + unsigned int i; + + if (!buff) + buff = xmalloc(sizeof(struct sockaddr) * 3 + 1); + 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 const char* FAST_FUNC UNSPEC_sprint(struct sockaddr *sap, int numeric UNUSED_PARAM) +{ + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return "[NONE SET]"; + return UNSPEC_print((unsigned char *)sap->sa_data); +} + +static const struct aftype unspec_aftype = { + .name = "unspec", + .title = "UNSPEC", + .af = AF_UNSPEC, + .alen = 0, + .print = UNSPEC_print, + .sprint = UNSPEC_sprint, +}; + +static const struct aftype *const aftypes[] = { + &inet_aftype, +#ifdef HAVE_AFINET6 + &inet6_aftype, +#endif + &unspec_aftype, + NULL +}; + +/* Check our protocol family table for this family. */ +const struct aftype* FAST_FUNC get_aftype(const char *name) +{ + const struct aftype *const *afp; + + afp = aftypes; + while (*afp != NULL) { + if (!strcmp((*afp)->name, name)) + return (*afp); + afp++; + } + return NULL; +} + +/* Check our protocol family table for this family. */ +static const struct aftype *get_afntype(int af) +{ + const struct aftype *const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return *afp; + afp++; + } + return NULL; +} + +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 */ +}; + + +smallint interface_opt_a; /* show all interfaces */ + +static struct interface *int_list, *int_last; + + +#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)); + 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 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 int procnetdev_version(char *buf) +{ + if (strstr(buf, "compressed")) + return 2; + if (strstr(buf, "bytes")) + return 1; + return 0; +} + +static int if_readconf(void) +{ + int numreqs = 30; + struct ifconf ifc; + struct ifreq *ifr; + int n, err = -1; + int skfd; + + ifc.ifc_buf = NULL; + + /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets + (as of 2.1.128) */ + skfd = socket(AF_INET, SOCK_DGRAM, 0); + if (skfd < 0) { + bb_perror_msg("error: no inet socket available"); + return -1; + } + + for (;;) { + ifc.ifc_len = sizeof(struct ifreq) * numreqs; + ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len); + + if (ioctl_or_warn(skfd, SIOCGIFCONF, &ifc) < 0) { + goto out; + } + if (ifc.ifc_len == (int)(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: + close(skfd); + free(ifc.ifc_buf); + return err; +} + +static int if_readlist_proc(char *target) +{ + static smallint 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_or_warn(_PATH_PROCNET_DEV, "r"); + if (!fh) { + 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)) { + bb_perror_msg(_PATH_PROCNET_DEV); + err = -1; + proc_read = 0; + } + fclose(fh); + return err; +} + +static int if_readlist(void) +{ + int err = if_readlist_proc(NULL); + /* Needed in order to get ethN:M aliases */ + if (!err) + err = if_readconf(); + return err; +} + +/* Fetch the interface configuration from the kernel. */ +static int if_fetch(struct interface *ife) +{ + struct ifreq ifr; + char *ifname = ife->name; + int skfd; + + skfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) { + close(skfd); + return -1; + } + ife->flags = ifr.ifr_flags; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + memset(ife->hwaddr, 0, 32); + if (ioctl(skfd, SIOCGIFHWADDR, &ifr) >= 0) + 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)); + ife->metric = 0; + if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0) + ife->metric = ifr.ifr_metric; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ife->mtu = 0; + if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0) + ife->mtu = ifr.ifr_mtu; + + memset(&ife->map, 0, sizeof(struct ifmap)); +#ifdef SIOCGIFMAP + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0) + ife->map = ifr.ifr_map; +#endif + +#ifdef HAVE_TXQUEUELEN + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ife->tx_queue_len = -1; /* unknown value */ + if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) >= 0) + ife->tx_queue_len = ifr.ifr_qlen; +#else + ife->tx_queue_len = -1; /* unknown value */ +#endif + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_addr.sa_family = AF_INET; + memset(&ife->addr, 0, sizeof(struct sockaddr)); + if (ioctl(skfd, SIOCGIFADDR, &ifr) == 0) { + ife->has_ip = 1; + ife->addr = ifr.ifr_addr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + memset(&ife->dstaddr, 0, sizeof(struct sockaddr)); + if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0) + ife->dstaddr = ifr.ifr_dstaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + memset(&ife->broadaddr, 0, sizeof(struct sockaddr)); + if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0) + ife->broadaddr = ifr.ifr_broadaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + memset(&ife->netmask, 0, sizeof(struct sockaddr)); + if (ioctl(skfd, SIOCGIFNETMASK, &ifr) >= 0) + ife->netmask = ifr.ifr_netmask; + } + + close(skfd); + return 0; +} + +static int do_if_fetch(struct interface *ife) +{ + if (if_fetch(ife) < 0) { + const 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; +} + +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 (defined(__GLIBC__) && __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* FAST_FUNC ether_print(unsigned char *ptr) +{ + static char *buff; + + free(buff); + buff = xasprintf("%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 int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap); + +static const struct hwtype ether_hwtype = { + .name = "ether", + .title = "Ethernet", + .type = ARPHRD_ETHER, + .alen = ETH_ALEN, + .print = ether_print, + .input = ether_input +}; + +static unsigned hexchar2int(char c) +{ + if (isdigit(c)) + return c - '0'; + c &= ~0x20; /* a -> A */ + if ((unsigned)(c - 'A') <= 5) + return c - ('A' - 10); + return ~0U; +} + +/* Input an Ethernet address and convert to binary. */ +static int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap) +{ + unsigned char *ptr; + char c; + int i; + unsigned val; + + sap->sa_family = ether_hwtype.type; + ptr = (unsigned char*) sap->sa_data; + + i = 0; + while ((*bufp != '\0') && (i < ETH_ALEN)) { + val = hexchar2int(*bufp++) * 0x10; + if (val > 0xff) { + errno = EINVAL; + return -1; + } + c = *bufp; + if (c == ':' || c == 0) + val >>= 4; + else { + val |= hexchar2int(c); + if (val > 0xff) { + errno = EINVAL; + return -1; + } + } + if (c != 0) + bufp++; + *ptr++ = (unsigned char) val; + i++; + + /* We might get a semicolon here - not required. */ + if (*bufp == ':') { + bufp++; + } + } + return 0; +} + +#include <net/if_arp.h> + +static const struct hwtype ppp_hwtype = { + .name = "ppp", + .title = "Point-to-Point Protocol", + .type = ARPHRD_PPP +}; + +#if ENABLE_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 +#if ENABLE_FEATURE_HWIB +static const struct hwtype ib_hwtype = { + .name = "infiniband", + .title = "InfiniBand", + .type = ARPHRD_INFINIBAND, + .alen = INFINIBAND_ALEN, + .print = UNSPEC_print, + .input = in_ib, +}; +#endif + + +static const struct hwtype *const hwtypes[] = { + &loop_hwtype, + ðer_hwtype, + &ppp_hwtype, + &unspec_hwtype, +#if ENABLE_FEATURE_IPV6 + &sit_hwtype, +#endif +#if ENABLE_FEATURE_HWIB + &ib_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. */ +const struct hwtype* FAST_FUNC get_hwtype(const char *name) +{ + const struct hwtype *const *hwp; + + hwp = hwtypes; + while (*hwp != NULL) { + if (!strcmp((*hwp)->name, name)) + return (*hwp); + hwp++; + } + return NULL; +} + +/* Check our hardware type table for this type. */ +const struct hwtype* FAST_FUNC 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) +{ + 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[] ALIGN1 = "\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 void ife_print(struct interface *ptr) +{ + const 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 + bb_putchar('\n'); + + 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 */ + + f = fopen_for_read(_PATH_PROCNET_IFINET6); + if (f != NULL) { + while (fscanf + (f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %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_sprint((struct sockaddr *) &sap, 1), + plen); + printf(" Scope:"); + switch (scope & IPV6_ADDR_SCOPE_MASK) { + case 0: + puts("Global"); + break; + case IPV6_ADDR_LINKLOCAL: + puts("Link"); + break; + case IPV6_ADDR_SITELOCAL: + puts("Site"); + break; + case IPV6_ADDR_COMPATv4: + puts("Compat"); + break; + case IPV6_ADDR_LOOPBACK: + puts("Host"); + break; + default: + puts("Unknown"); + } + } + } + fclose(f); + } +#endif + + printf(" "); + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */ + + if (ptr->flags == 0) { + printf("[NO FLAGS] "); + } else { + static const char ife_print_flags_strs[] ALIGN1 = + "UP\0" + "BROADCAST\0" + "DEBUG\0" + "LOOPBACK\0" + "POINTOPOINT\0" + "NOTRAILERS\0" + "RUNNING\0" + "NOARP\0" + "PROMISC\0" + "ALLMULTI\0" + "SLAVE\0" + "MASTER\0" + "MULTICAST\0" +#ifdef HAVE_DYNAMIC + "DYNAMIC\0" +#endif + ; + static const unsigned short ife_print_flags_mask[] ALIGN2 = { + 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 + }; + const unsigned short *mask = ife_print_flags_mask; + const char *str = ife_print_flags_strs; + do { + if (ptr->flags & *mask) { + printf("%s ", str); + } + mask++; + str += strlen(str) + 1; + } while (*str); + } + + /* 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 + bb_putchar('\n'); + + /* 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); + bb_putchar('\n'); + } + bb_putchar('\n'); +} + + +static int do_if_print(struct interface *ife) /*, int *opt_a)*/ +{ + int res; + + res = do_if_fetch(ife); + if (res >= 0) { + if ((ife->flags & IFF_UP) || interface_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; +} + +#ifdef UNUSED +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; +} +#endif + +/* for ipv4 add/del modes */ +static int if_print(char *ifname) +{ + struct interface *ife; + int res; + + if (!ifname) { + /*res = for_all_interfaces(do_if_print, &interface_opt_a);*/ + if (!int_list && (if_readlist() < 0)) + return -1; + for (ife = int_list; ife; ife = ife->next) { + int err = do_if_print(ife); /*, &interface_opt_a);*/ + if (err) + return err; + } + return 0; + } + ife = lookup_interface(ifname); + res = do_if_fetch(ife); + if (res >= 0) + ife_print(ife); + return res; +} + +#if ENABLE_FEATURE_HWIB +/* Input an Infiniband address and convert to binary. */ +int FAST_FUNC in_ib(const char *bufp, struct sockaddr *sap) +{ + unsigned char *ptr; + char c; + const char *orig; + int i; + unsigned val; + + sap->sa_family = ib_hwtype.type; + ptr = (unsigned char *) sap->sa_data; + + i = 0; + orig = bufp; + while ((*bufp != '\0') && (i < INFINIBAND_ALEN)) { + val = 0; + c = *bufp++; + if (isdigit(c)) + val = c - '0'; + else if (c >= 'a' && c <= 'f') + val = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = c - 'A' + 10; + else { + errno = EINVAL; + return -1; + } + val <<= 4; + c = *bufp; + if (isdigit(c)) + val |= c - '0'; + else if (c >= 'a' && c <= 'f') + val |= c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val |= c - 'A' + 10; + else if (c == ':' || c == 0) + val >>= 4; + else { + errno = EINVAL; + return -1; + } + if (c != 0) + bufp++; + *ptr++ = (unsigned char) (val & 0377); + i++; + + /* We might get a semicolon here - not required. */ + if (*bufp == ':') { + bufp++; + } + } +#ifdef DEBUG + fprintf(stderr, "in_ib(%s): %s\n", orig, UNSPEC_print(sap->sa_data)); +#endif + return 0; +} +#endif + + +int FAST_FUNC display_interfaces(char *ifname) +{ + int status; + + status = if_print(ifname); + + return (status < 0); /* status < 0 == 1 -- error */ +} diff --git a/networking/ip.c b/networking/ip.c new file mode 100644 index 0000000..9903c68 --- /dev/null +++ b/networking/ip.c @@ -0,0 +1,123 @@ +/* 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 + * Bernhard Reutner-Fischer rewrote to use index_in_substr_array + */ + +#include "libbb.h" + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#if ENABLE_FEATURE_IP_ADDRESS \ + || ENABLE_FEATURE_IP_ROUTE \ + || ENABLE_FEATURE_IP_LINK \ + || ENABLE_FEATURE_IP_TUNNEL \ + || ENABLE_FEATURE_IP_RULE + +static int NORETURN ip_print_help(char **argv UNUSED_PARAM) +{ + bb_show_usage(); +} + +static int ip_do(int (*ip_func)(char **argv), char **argv) +{ + argv = ip_parse_common_args(argv + 1); + return ip_func(argv); +} + +#if ENABLE_FEATURE_IP_ADDRESS +int ipaddr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ipaddr_main(int argc UNUSED_PARAM, char **argv) +{ + return ip_do(do_ipaddr, argv); +} +#endif +#if ENABLE_FEATURE_IP_LINK +int iplink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int iplink_main(int argc UNUSED_PARAM, char **argv) +{ + return ip_do(do_iplink, argv); +} +#endif +#if ENABLE_FEATURE_IP_ROUTE +int iproute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int iproute_main(int argc UNUSED_PARAM, char **argv) +{ + return ip_do(do_iproute, argv); +} +#endif +#if ENABLE_FEATURE_IP_RULE +int iprule_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int iprule_main(int argc UNUSED_PARAM, char **argv) +{ + return ip_do(do_iprule, argv); +} +#endif +#if ENABLE_FEATURE_IP_TUNNEL +int iptunnel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int iptunnel_main(int argc UNUSED_PARAM, char **argv) +{ + return ip_do(do_iptunnel, argv); +} +#endif + + +int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ip_main(int argc UNUSED_PARAM, char **argv) +{ + static const char keywords[] ALIGN1 = + USE_FEATURE_IP_ADDRESS("address\0") + USE_FEATURE_IP_ROUTE("route\0") + USE_FEATURE_IP_LINK("link\0") + USE_FEATURE_IP_TUNNEL("tunnel\0" "tunl\0") + USE_FEATURE_IP_RULE("rule\0") + ; + enum { + USE_FEATURE_IP_ADDRESS(IP_addr,) + USE_FEATURE_IP_ROUTE(IP_route,) + USE_FEATURE_IP_LINK(IP_link,) + USE_FEATURE_IP_TUNNEL(IP_tunnel, IP_tunl,) + USE_FEATURE_IP_RULE(IP_rule,) + IP_none + }; + int (*ip_func)(char**) = ip_print_help; + + argv = ip_parse_common_args(argv + 1); + if (*argv) { + int key = index_in_substrings(keywords, *argv); + argv++; +#if ENABLE_FEATURE_IP_ADDRESS + if (key == IP_addr) + ip_func = do_ipaddr; +#endif +#if ENABLE_FEATURE_IP_ROUTE + if (key == IP_route) + ip_func = do_iproute; +#endif +#if ENABLE_FEATURE_IP_LINK + if (key == IP_link) + ip_func = do_iplink; +#endif +#if ENABLE_FEATURE_IP_TUNNEL + if (key == IP_tunnel || key == IP_tunl) + ip_func = do_iptunnel; +#endif +#if ENABLE_FEATURE_IP_RULE + if (key == IP_rule) + ip_func = do_iprule; +#endif + } + return ip_func(argv); +} + +#endif /* any of ENABLE_FEATURE_IP_xxx is 1 */ diff --git a/networking/ipcalc.c b/networking/ipcalc.c new file mode 100644 index 0000000..d8fa5f3 --- /dev/null +++ b/networking/ipcalc.c @@ -0,0 +1,190 @@ +/* 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 <sys/socket.h> +#include <arpa/inet.h> + +#include "libbb.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; +} + +#if ENABLE_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 char ipcalc_longopts[] ALIGN1 = + "netmask\0" No_argument "m" + "broadcast\0" No_argument "b" + "network\0" No_argument "n" +# if ENABLE_FEATURE_IPCALC_FANCY + "prefix\0" No_argument "p" + "hostname\0" No_argument "h" + "silent\0" No_argument "s" +# endif + ; +#endif + +int ipcalc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +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 = ipcalc_longopts; +#endif + opt = getopt32(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; + + hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET); + if (!hostinfo) { + bb_herror_msg_and_die("cannot find hostname for %s", argv[0]); + } + str_tolower(hostinfo->h_name); + + printf("HOSTNAME=%s\n", hostinfo->h_name); + } + } + + return EXIT_SUCCESS; +} diff --git a/networking/isrv.c b/networking/isrv.c new file mode 100644 index 0000000..66bb371 --- /dev/null +++ b/networking/isrv.c @@ -0,0 +1,338 @@ +/* vi: set sw=4 ts=4: */ +/* + * Generic non-forking server infrastructure. + * Intended to make writing telnetd-type servers easier. + * + * Copyright (C) 2007 Denys Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "isrv.h" + +#define DEBUG 0 + +#if DEBUG +#define DPRINTF(args...) bb_error_msg(args) +#else +#define DPRINTF(args...) ((void)0) +#endif + +/* Helpers */ + +/* Opaque structure */ + +struct isrv_state_t { + short *fd2peer; /* one per registered fd */ + void **param_tbl; /* one per registered peer */ + /* one per registered peer; doesn't exist if !timeout */ + time_t *timeo_tbl; + int (*new_peer)(isrv_state_t *state, int fd); + time_t curtime; + int timeout; + int fd_count; + int peer_count; + int wr_count; + fd_set rd; + fd_set wr; +}; +#define FD2PEER (state->fd2peer) +#define PARAM_TBL (state->param_tbl) +#define TIMEO_TBL (state->timeo_tbl) +#define CURTIME (state->curtime) +#define TIMEOUT (state->timeout) +#define FD_COUNT (state->fd_count) +#define PEER_COUNT (state->peer_count) +#define WR_COUNT (state->wr_count) + +/* callback */ +void isrv_want_rd(isrv_state_t *state, int fd) +{ + FD_SET(fd, &state->rd); +} + +/* callback */ +void isrv_want_wr(isrv_state_t *state, int fd) +{ + if (!FD_ISSET(fd, &state->wr)) { + WR_COUNT++; + FD_SET(fd, &state->wr); + } +} + +/* callback */ +void isrv_dont_want_rd(isrv_state_t *state, int fd) +{ + FD_CLR(fd, &state->rd); +} + +/* callback */ +void isrv_dont_want_wr(isrv_state_t *state, int fd) +{ + if (FD_ISSET(fd, &state->wr)) { + WR_COUNT--; + FD_CLR(fd, &state->wr); + } +} + +/* callback */ +int isrv_register_fd(isrv_state_t *state, int peer, int fd) +{ + int n; + + DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd); + + if (FD_COUNT >= FD_SETSIZE) return -1; + if (FD_COUNT <= fd) { + n = FD_COUNT; + FD_COUNT = fd + 1; + + DPRINTF("register_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + while (n < fd) FD2PEER[n++] = -1; + } + + DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer); + + FD2PEER[fd] = peer; + return 0; +} + +/* callback */ +void isrv_close_fd(isrv_state_t *state, int fd) +{ + DPRINTF("close_fd(%d)", fd); + + close(fd); + isrv_dont_want_rd(state, fd); + if (WR_COUNT) isrv_dont_want_wr(state, fd); + + FD2PEER[fd] = -1; + if (fd == FD_COUNT-1) { + do fd--; while (fd >= 0 && FD2PEER[fd] == -1); + FD_COUNT = fd + 1; + + DPRINTF("close_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + } +} + +/* callback */ +int isrv_register_peer(isrv_state_t *state, void *param) +{ + int n; + + if (PEER_COUNT >= FD_SETSIZE) return -1; + n = PEER_COUNT++; + + DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT); + + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + PARAM_TBL[n] = param; + if (TIMEOUT) { + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); + TIMEO_TBL[n] = CURTIME; + } + return n; +} + +static void remove_peer(isrv_state_t *state, int peer) +{ + int movesize; + int fd; + + DPRINTF("remove_peer(%d)", peer); + + fd = FD_COUNT - 1; + while (fd >= 0) { + if (FD2PEER[fd] == peer) { + isrv_close_fd(state, fd); + fd--; + continue; + } + if (FD2PEER[fd] > peer) + FD2PEER[fd]--; + fd--; + } + + PEER_COUNT--; + DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT); + + movesize = (PEER_COUNT - peer) * sizeof(void*); + if (movesize > 0) { + memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize); + if (TIMEOUT) + memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize); + } + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + if (TIMEOUT) + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); +} + +static void handle_accept(isrv_state_t *state, int fd) +{ + int n, newfd; + + /* suppress gcc warning "cast from ptr to int of different size" */ + fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK); + newfd = accept(fd, NULL, 0); + fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0])); + if (newfd < 0) { + if (errno == EAGAIN) return; + /* Most probably someone gave us wrong fd type + * (for example, non-socket). Don't want + * to loop forever. */ + bb_perror_msg_and_die("accept"); + } + + DPRINTF("new_peer(%d)", newfd); + n = state->new_peer(state, newfd); + if (n) + remove_peer(state, n); /* unsuccesful peer start */ +} + +void BUG_sizeof_fd_set_is_strange(void); +static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **)) +{ + enum { LONG_CNT = sizeof(fd_set) / sizeof(long) }; + int fds_pos; + int fd, peer; + /* need to know value at _the beginning_ of this routine */ + int fd_cnt = FD_COUNT; + + if (LONG_CNT * sizeof(long) != sizeof(fd_set)) + BUG_sizeof_fd_set_is_strange(); + + fds_pos = 0; + while (1) { + /* Find next nonzero bit */ + while (fds_pos < LONG_CNT) { + if (((long*)fds)[fds_pos] == 0) { + fds_pos++; + continue; + } + /* Found non-zero word */ + fd = fds_pos * sizeof(long)*8; /* word# -> bit# */ + while (1) { + if (FD_ISSET(fd, fds)) { + FD_CLR(fd, fds); + goto found_fd; + } + fd++; + } + } + break; /* all words are zero */ + found_fd: + if (fd >= fd_cnt) { /* paranoia */ + DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)", + fd, fd_cnt); + break; + } + DPRINTF("handle_fd_set: fd %d is active", fd); + peer = FD2PEER[fd]; + if (peer < 0) + continue; /* peer is already gone */ + if (peer == 0) { + handle_accept(state, fd); + continue; + } + DPRINTF("h(fd:%d)", fd); + if (h(fd, &PARAM_TBL[peer])) { + /* this peer is gone */ + remove_peer(state, peer); + } else if (TIMEOUT) { + TIMEO_TBL[peer] = monotonic_sec(); + } + } +} + +static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **)) +{ + int n, peer; + peer = PEER_COUNT-1; + /* peer 0 is not checked */ + while (peer > 0) { + DPRINTF("peer %d: time diff %d", peer, + (int)(CURTIME - TIMEO_TBL[peer])); + if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) { + DPRINTF("peer %d: do_timeout()", peer); + n = do_timeout(&PARAM_TBL[peer]); + if (n) + remove_peer(state, peer); + } + peer--; + } +} + +/* Driver */ +void isrv_run( + int listen_fd, + int (*new_peer)(isrv_state_t *state, int fd), + int (*do_rd)(int fd, void **), + int (*do_wr)(int fd, void **), + int (*do_timeout)(void **), + int timeout, + int linger_timeout) +{ + isrv_state_t *state = xzalloc(sizeof(*state)); + state->new_peer = new_peer; + state->timeout = timeout; + + /* register "peer" #0 - it will accept new connections */ + isrv_register_peer(state, NULL); + isrv_register_fd(state, /*peer:*/ 0, listen_fd); + isrv_want_rd(state, listen_fd); + /* remember flags to make blocking<->nonblocking switch faster */ + /* (suppress gcc warning "cast from ptr to int of different size") */ + PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL)); + + while (1) { + struct timeval tv; + fd_set rd; + fd_set wr; + fd_set *wrp = NULL; + int n; + + tv.tv_sec = timeout; + if (PEER_COUNT <= 1) + tv.tv_sec = linger_timeout; + tv.tv_usec = 0; + rd = state->rd; + if (WR_COUNT) { + wr = state->wr; + wrp = ≀ + } + + DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...", + FD_COUNT, (int)tv.tv_sec); + n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL); + DPRINTF("run: ...select:%d", n); + + if (n < 0) { + if (errno != EINTR) + bb_perror_msg("select"); + continue; + } + + if (n == 0 && linger_timeout && PEER_COUNT <= 1) + break; + + if (timeout) { + time_t t = monotonic_sec(); + if (t != CURTIME) { + CURTIME = t; + handle_timeout(state, do_timeout); + } + } + if (n > 0) { + handle_fd_set(state, &rd, do_rd); + if (wrp) + handle_fd_set(state, wrp, do_wr); + } + } + DPRINTF("run: bailout"); + /* NB: accept socket is not closed. Caller is to decide what to do */ +} diff --git a/networking/isrv.h b/networking/isrv.h new file mode 100644 index 0000000..c0158a3 --- /dev/null +++ b/networking/isrv.h @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * Generic non-forking server infrastructure. + * Intended to make writing telnetd-type servers easier. + * + * Copyright (C) 2007 Denys Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +/* opaque structure */ +struct isrv_state_t; +typedef struct isrv_state_t isrv_state_t; + +/* callbacks */ +void isrv_want_rd(isrv_state_t *state, int fd); +void isrv_want_wr(isrv_state_t *state, int fd); +void isrv_dont_want_rd(isrv_state_t *state, int fd); +void isrv_dont_want_wr(isrv_state_t *state, int fd); +int isrv_register_fd(isrv_state_t *state, int peer, int fd); +void isrv_close_fd(isrv_state_t *state, int fd); +int isrv_register_peer(isrv_state_t *state, void *param); + +/* driver */ +void isrv_run( + int listen_fd, + int (*new_peer)(isrv_state_t *state, int fd), + int (*do_rd)(int fd, void **), + int (*do_wr)(int fd, void **), + int (*do_timeout)(void **), + int timeout, + int linger_timeout +); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c new file mode 100644 index 0000000..e08ebd4 --- /dev/null +++ b/networking/isrv_identd.c @@ -0,0 +1,147 @@ +/* vi: set sw=4 ts=4: */ +/* + * Fake identd server. + * + * Copyright (C) 2007 Denys Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include <syslog.h> +#include "isrv.h" + +enum { TIMEOUT = 20 }; + +typedef struct identd_buf_t { + int pos; + int fd_flag; + char buf[64 - 2*sizeof(int)]; +} identd_buf_t; + +#define bogouser bb_common_bufsiz1 + +static int new_peer(isrv_state_t *state, int fd) +{ + int peer; + identd_buf_t *buf = xzalloc(sizeof(*buf)); + + peer = isrv_register_peer(state, buf); + if (peer < 0) + return 0; /* failure */ + if (isrv_register_fd(state, peer, fd) < 0) + return peer; /* failure, unregister peer */ + + buf->fd_flag = fcntl(fd, F_GETFL) | O_NONBLOCK; + isrv_want_rd(state, fd); + return 0; +} + +static int do_rd(int fd, void **paramp) +{ + identd_buf_t *buf = *paramp; + char *cur, *p; + int retval = 0; /* session is ok (so far) */ + int sz; + + cur = buf->buf + buf->pos; + + if (buf->fd_flag & O_NONBLOCK) + fcntl(fd, F_SETFL, buf->fd_flag); + sz = safe_read(fd, cur, sizeof(buf->buf) - buf->pos); + + if (sz < 0) { + if (errno != EAGAIN) + goto term; /* terminate this session if !EAGAIN */ + goto ok; + } + + buf->pos += sz; + buf->buf[buf->pos] = '\0'; + p = strpbrk(cur, "\r\n"); + if (p) + *p = '\0'; + if (!p && sz && buf->pos <= (int)sizeof(buf->buf)) + goto ok; + /* Terminate session. If we are in server mode, then + * fd is still in nonblocking mode - we never block here */ + if (fd == 0) fd++; /* inetd mode? then write to fd 1 */ + fdprintf(fd, "%s : USERID : UNIX : %s\r\n", buf->buf, bogouser); + term: + free(buf); + retval = 1; /* terminate */ + ok: + if (buf->fd_flag & O_NONBLOCK) + fcntl(fd, F_SETFL, buf->fd_flag & ~O_NONBLOCK); + return retval; +} + +static int do_timeout(void **paramp UNUSED_PARAM) +{ + return 1; /* terminate session */ +} + +static void inetd_mode(void) +{ + identd_buf_t *buf = xzalloc(sizeof(*buf)); + /* buf->pos = 0; - xzalloc did it */ + /* We do NOT want nonblocking I/O here! */ + /* buf->fd_flag = 0; - xzalloc did it */ + do + alarm(TIMEOUT); + while (do_rd(0, (void*)&buf) == 0); +} + +int fakeidentd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int fakeidentd_main(int argc UNUSED_PARAM, char **argv) +{ + enum { + OPT_foreground = 0x1, + OPT_inetd = 0x2, + OPT_inetdwait = 0x4, + OPT_fiw = 0x7, + OPT_bindaddr = 0x8, + }; + + const char *bind_address = NULL; + unsigned opt; + int fd; + + opt = getopt32(argv, "fiwb:", &bind_address); + strcpy(bogouser, "nobody"); + if (argv[optind]) + strncpy(bogouser, argv[optind], sizeof(bogouser)); + + /* Daemonize if no -f and no -i and no -w */ + if (!(opt & OPT_fiw)) + bb_daemonize_or_rexec(0, argv); + + /* Where to log in inetd modes? "Classic" inetd + * probably has its stderr /dev/null'ed (we need log to syslog?), + * but daemontools-like utilities usually expect that children + * log to stderr. I like daemontools more. Go their way. + * (Or maybe we need yet another option "log to syslog") */ + if (!(opt & OPT_fiw) /* || (opt & OPT_syslog) */) { + openlog(applet_name, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + if (opt & OPT_inetd) { + inetd_mode(); + return 0; + } + + /* Ignore closed connections when writing */ + signal(SIGPIPE, SIG_IGN); + + fd = 0; + if (!(opt & OPT_inetdwait)) { + fd = create_and_bind_stream_or_die(bind_address, + bb_lookup_port("identd", "tcp", 113)); + xlisten(fd, 5); + } + + isrv_run(fd, new_peer, do_rd, /*do_wr:*/ NULL, do_timeout, + TIMEOUT, (opt & OPT_inetdwait) ? TIMEOUT : 0); + return 0; +} diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild new file mode 100644 index 0000000..5f9dd32 --- /dev/null +++ b/networking/libiproute/Kbuild @@ -0,0 +1,64 @@ +# 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_SLATTACH) += \ + utils.o + +lib-$(CONFIG_IP) += \ + ip_parse_common_args.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_FEATURE_IP_ADDRESS) += \ + 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_FEATURE_IP_LINK) += \ + 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_FEATURE_IP_ROUTE) += \ + ip_parse_common_args.o \ + iproute.o \ + libnetlink.o \ + ll_map.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_FEATURE_IP_TUNNEL) += \ + ip_parse_common_args.o \ + iptunnel.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_FEATURE_IP_RULE) += \ + 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..305b491 --- /dev/null +++ b/networking/libiproute/ip_common.h @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _IP_COMMON_H +#define _IP_COMMON_H 1 + +#include "libbb.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 + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +extern char **ip_parse_common_args(char **argv); +extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ipaddr_list_or_flush(char **argv, int flush); +extern int iproute_monitor(char **argv); +extern void iplink_usage(void) NORETURN; +extern void ipneigh_reset_filter(void); + +extern int do_ipaddr(char **argv); +extern int do_iproute(char **argv); +extern int do_iprule(char **argv); +extern int do_ipneigh(char **argv); +extern int do_iptunnel(char **argv); +extern int do_iplink(char **argv); +extern int do_ipmonitor(char **argv); +extern int do_multiaddr(char **argv); +extern int do_multiroute(char **argv); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#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..5e4012b --- /dev/null +++ b/networking/libiproute/ip_parse_common_args.c @@ -0,0 +1,84 @@ +/* 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 "ip_common.h" /* #include "libbb.h" is inside */ +#include "utils.h" + +family_t preferred_family = AF_UNSPEC; +smallint oneline; +char _SL_; + +char **ip_parse_common_args(char **argv) +{ + static const char ip_common_commands[] ALIGN1 = + "oneline" "\0" + "family" "\0" + "4" "\0" + "6" "\0" + "0" "\0" + ; + enum { + ARG_oneline, + ARG_family, + ARG_IPv4, + ARG_IPv6, + ARG_packet, + }; + static const family_t af_numbers[] = { AF_INET, AF_INET6, AF_PACKET }; + int arg; + + while (*argv) { + char *opt = *argv; + + if (opt[0] != '-') + break; + opt++; + if (opt[0] == '-') { + opt++; + if (!opt[0]) { /* "--" */ + argv++; + break; + } + } + arg = index_in_substrings(ip_common_commands, opt); + if (arg < 0) + bb_show_usage(); + if (arg == ARG_oneline) { + oneline = 1; + argv++; + continue; + } + if (arg == ARG_family) { + static const char families[] ALIGN1 = + "inet" "\0" "inet6" "\0" "link" "\0"; + argv++; + if (!*argv) + bb_show_usage(); + arg = index_in_strings(families, *argv); + if (arg < 0) + invarg(*argv, "protocol family"); + /* now arg == 0, 1 or 2 */ + } else { + arg -= ARG_IPv4; + /* now arg == 0, 1 or 2 */ + } + preferred_family = af_numbers[arg]; + argv++; + } + _SL_ = oneline ? '\\' : '\n'; + return argv; +} diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c new file mode 100644 index 0000000..288dcca --- /dev/null +++ b/networking/libiproute/ipaddress.c @@ -0,0 +1,784 @@ +/* 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 <fnmatch.h> +#include <net/if.h> +#include <net/if_arp.h> + +#include "ip_common.h" /* #include "libbb.h" is inside */ +#include "rt_names.h" +#include "utils.h" + +#ifndef IFF_LOWER_UP +/* from linux/if.h */ +#define IFF_LOWER_UP 0x10000 /* driver signals L1 up*/ +#endif + +typedef struct filter_t { + char *label; + char *flushb; + struct rtnl_handle *rth; + int scope, scopemask; + int flags, flagmask; + int flushp; + int flushe; + int ifindex; + family_t family; + smallint showqueue; + smallint oneline; + smallint up; + smallint flushed; + inet_prefix pfx; +} filter_t; + +#define filter (*(filter_t*)&bb_common_bufsiz1) + + +static void print_link_flags(unsigned flags, unsigned mdown) +{ + static const int flag_masks[] = { + IFF_LOOPBACK, IFF_BROADCAST, IFF_POINTOPOINT, + IFF_MULTICAST, IFF_NOARP, IFF_UP, IFF_LOWER_UP }; + static const char flag_labels[] ALIGN1 = + "LOOPBACK\0""BROADCAST\0""POINTOPOINT\0" + "MULTICAST\0""NOARP\0""UP\0""LOWER_UP\0"; + + bb_putchar('<'); + flags &= ~IFF_RUNNING; +#if 0 + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); +#endif + flags = print_flags_separated(flag_masks, flag_labels, flags, ","); + if (flags) + printf("%x", flags); + if (mdown) + printf(",M-DOWN"); + printf("> "); +} + +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_or_warn(s, SIOCGIFTXQLEN, &ifr) < 0) { + close(s); + return; + } + close(s); + + if (ifr.ifr_qlen) + printf("qlen %d", ifr.ifr_qlen); +} + +static int print_linkinfo(const struct nlmsghdr *n) +{ + 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) + printf("Deleted "); + + printf("%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) + printf("@NONE: "); + else { + printf("@%s: ", ll_idx_n2a(iflink, b1)); + m_flag = ll_index_to_flags(iflink); + m_flag = !(m_flag & IFF_UP); + } + } else { + printf(": "); + } + print_link_flags(ifi->ifi_flags, m_flag); + + if (tb[IFLA_MTU]) + printf("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + if (tb[IFLA_QDISC]) + printf("qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC])); +#ifdef IFLA_MASTER + if (tb[IFLA_MASTER]) { + SPRINT_BUF(b1); + printf("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); + printf("%c link/%s ", _SL_, ll_type_n2a(ifi->ifi_type, b1, sizeof(b1))); + + if (tb[IFLA_ADDRESS]) { + fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]), + RTA_PAYLOAD(tb[IFLA_ADDRESS]), + ifi->ifi_type, + b1, sizeof(b1)), stdout); + } + if (tb[IFLA_BROADCAST]) { + if (ifi->ifi_flags & IFF_POINTOPOINT) + printf(" peer "); + else + printf(" brd "); + fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]), + RTA_PAYLOAD(tb[IFLA_BROADCAST]), + ifi->ifi_type, + b1, sizeof(b1)), stdout); + } + } + bb_putchar('\n'); + /*fflush(stdout);*/ + return 0; +} + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + bb_perror_msg("failed to send flush request"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_addrinfo(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *n, void *arg UNUSED_PARAM) +{ + 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 = 1; + return 0; + } + + if (n->nlmsg_type == RTM_DELADDR) + printf("Deleted "); + + if (filter.oneline) + printf("%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index)); + if (ifa->ifa_family == AF_INET) + printf(" inet "); + else if (ifa->ifa_family == AF_INET6) + printf(" inet6 "); + else + printf(" family %d ", ifa->ifa_family); + + if (rta_tb[IFA_LOCAL]) { + fputs(rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_LOCAL]), + RTA_DATA(rta_tb[IFA_LOCAL]), + abuf, sizeof(abuf)), stdout); + + if (rta_tb[IFA_ADDRESS] == NULL || + memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) { + printf("/%d ", ifa->ifa_prefixlen); + } else { + printf(" 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]) { + printf("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]) { + printf("any %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ANYCAST]), + RTA_DATA(rta_tb[IFA_ANYCAST]), + abuf, sizeof(abuf))); + } + printf("scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1))); + if (ifa->ifa_flags & IFA_F_SECONDARY) { + ifa->ifa_flags &= ~IFA_F_SECONDARY; + printf("secondary "); + } + if (ifa->ifa_flags & IFA_F_TENTATIVE) { + ifa->ifa_flags &= ~IFA_F_TENTATIVE; + printf("tentative "); + } + if (ifa->ifa_flags & IFA_F_DEPRECATED) { + ifa->ifa_flags &= ~IFA_F_DEPRECATED; + printf("deprecated "); + } + if (!(ifa->ifa_flags & IFA_F_PERMANENT)) { + printf("dynamic "); + } else + ifa->ifa_flags &= ~IFA_F_PERMANENT; + if (ifa->ifa_flags) + printf("flags %02x ", ifa->ifa_flags); + if (rta_tb[IFA_LABEL]) + fputs((char*)RTA_DATA(rta_tb[IFA_LABEL]), stdout); + if (rta_tb[IFA_CACHEINFO]) { + struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]); + char buf[128]; + bb_putchar(_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); + printf(" %s", buf); + } + bb_putchar('\n'); + /*fflush(stdout);*/ + return 0; +} + + +struct nlmsg_list +{ + struct nlmsg_list *next; + struct nlmsghdr h; +}; + +static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo) +{ + 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, NULL); + } + return 0; +} + + +static int store_nlmsg(const 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) + continue; + *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; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int ipaddr_list_or_flush(char **argv, int flush) +{ + static const char option[] ALIGN1 = "to\0""scope\0""up\0""label\0""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 (!*argv) { + bb_error_msg_and_die(bb_msg_requires_arg, "flush"); + } + if (filter.family == AF_PACKET) { + bb_error_msg_and_die("cannot flush link addresses"); + } + } + + while (*argv) { + const int option_num = index_in_strings(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++; + } + + xrtnl_open(&rth); + + xrtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK); + xrtnl_dump_filter(&rth, store_nlmsg, &linfo); + + if (filter_dev) { + filter.ifindex = xll_name_to_index(filter_dev); + } + + if (flush) { + char flushb[4096-512]; + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR); + filter.flushed = 0; + xrtnl_dump_filter(&rth, print_addrinfo, NULL); + if (filter.flushed == 0) { + return 0; + } + if (flush_update() < 0) + return 1; + } + } + + if (filter.family != AF_PACKET) { + xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR); + xrtnl_dump_filter(&rth, store_nlmsg, &ainfo); + } + + + 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(&l->h) == 0) { + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + if (filter.family != AF_PACKET) + print_selected_addrinfo(ifi->ifi_index, ainfo); + } + } + + return 0; +} + +static int default_scope(inet_prefix *lcl) +{ + if (lcl->family == AF_INET) { + if (lcl->bytelen >= 1 && *(uint8_t*)&lcl->data == 127) + return RT_SCOPE_HOST; + } + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int ipaddr_modify(int cmd, char **argv) +{ + static const char option[] ALIGN1 = + "peer\0""remote\0""broadcast\0""brd\0" + "anycast\0""scope\0""dev\0""label\0""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; + bool 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 (*argv) { + const int option_num = index_in_strings(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 (LONE_CHAR(*argv, '+')) { + brd_len = -1; + } else if (LONE_DASH(*argv)) { + 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; + } + argv++; + } + + if (d == NULL) { + bb_error_msg(bb_msg_requires_arg, "\"dev\""); + return -1; + } + if (l && strncmp(d, l, strlen(d)) != 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_and_die("broadcast can be set only for IPv4 addresses"); + } + 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); + + xrtnl_open(&rth); + + ll_init_map(&rth); + + req.ifa.ifa_index = xll_name_to_index(d); + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + return 2; + + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int do_ipaddr(char **argv) +{ + static const char commands[] ALIGN1 = + "add\0""delete\0""list\0""show\0""lst\0""flush\0"; + + int command_num = 2; /* default command is list */ + + if (*argv) { + command_num = index_in_substrings(commands, *argv); + if (command_num < 0 || command_num > 5) + bb_error_msg_and_die("unknown command %s", *argv); + argv++; + } + if (command_num == 0) /* add */ + return ipaddr_modify(RTM_NEWADDR, argv); + if (command_num == 1) /* delete */ + return ipaddr_modify(RTM_DELADDR, argv); + if (command_num == 5) /* flush */ + return ipaddr_list_or_flush(argv, 1); + /* 2 == list, 3 == show, 4 == lst */ + return ipaddr_list_or_flush(argv, 0); +} diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c new file mode 100644 index 0000000..8de17bf --- /dev/null +++ b/networking/libiproute/iplink.c @@ -0,0 +1,306 @@ +/* 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 <sys/ioctl.h> +//#include <sys/socket.h> +#include <net/if.h> +#include <net/if_packet.h> +#include <netpacket/packet.h> +#include <net/ethernet.h> + +#include "ip_common.h" /* #include "libbb.h" is inside */ +#include "rt_names.h" +#include "utils.h" + +/* taken from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +/* Exits on error */ +static int get_ctl_fd(void) +{ + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + fd = socket(PF_PACKET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + return xsocket(PF_INET6, SOCK_DGRAM, 0); +} + +/* Exits on error */ +static void do_chflags(char *dev, uint32_t flags, uint32_t mask) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = get_ctl_fd(); + xioctl(fd, SIOCGIFFLAGS, &ifr); + if ((ifr.ifr_flags ^ flags) & mask) { + ifr.ifr_flags &= ~mask; + ifr.ifr_flags |= mask & flags; + xioctl(fd, SIOCSIFFLAGS, &ifr); + } + close(fd); +} + +/* Exits on error */ +static void do_changename(char *dev, char *newdev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname)); + fd = get_ctl_fd(); + xioctl(fd, SIOCSIFNAME, &ifr); + close(fd); +} + +/* Exits on error */ +static void set_qlen(char *dev, int qlen) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_qlen = qlen; + xioctl(s, SIOCSIFTXQLEN, &ifr); + close(s); +} + +/* Exits on error */ +static void set_mtu(char *dev, int mtu) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + xioctl(s, SIOCSIFMTU, &ifr); + close(s); +} + +/* Exits on error */ +static int get_address(char *dev, int *htype) +{ + struct ifreq ifr; + struct sockaddr_ll me; + socklen_t alen; + int s; + + s = xsocket(PF_PACKET, SOCK_DGRAM, 0); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + xioctl(s, SIOCGIFINDEX, &ifr); + + memset(&me, 0, sizeof(me)); + me.sll_family = AF_PACKET; + me.sll_ifindex = ifr.ifr_ifindex; + me.sll_protocol = htons(ETH_P_LOOP); + xbind(s, (struct sockaddr*)&me, sizeof(me)); + + alen = sizeof(me); + if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { + bb_perror_msg_and_die("getsockname"); + } + close(s); + *htype = me.sll_hatype; + return me.sll_halen; +} + +/* Exits on error */ +static void 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 = hatype == 1/*ARPHRD_ETHER*/ ? 14/*ETH_HLEN*/ : 19/*INFINIBAND_HLEN*/; + alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), alen, lla); + if (alen < 0) + exit(EXIT_FAILURE); + if (alen != halen) { + bb_error_msg_and_die("wrong address (%s) length: expected %d bytes", lla, halen); + } +} + +/* Exits on error */ +static void set_address(struct ifreq *ifr, int brd) +{ + int s; + + s = get_ctl_fd(); + if (brd) + xioctl(s, SIOCSIFHWBROADCAST, ifr); + else + xioctl(s, SIOCSIFHWADDR, ifr); + close(s); +} + + +static void die_must_be_on_off(const char *msg) NORETURN; +static void die_must_be_on_off(const char *msg) +{ + bb_error_msg_and_die("argument of \"%s\" must be \"on\" or \"off\"", msg); +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int do_set(char **argv) +{ + char *dev = NULL; + uint32_t mask = 0; + uint32_t flags = 0; + int qlen = -1; + int mtu = -1; + char *newaddr = NULL; + char *newbrd = NULL; + struct ifreq ifr0, ifr1; + char *newname = NULL; + int htype, halen; + static const char keywords[] ALIGN1 = + "up\0""down\0""name\0""mtu\0""multicast\0" + "arp\0""address\0""dev\0"; + enum { ARG_up = 0, ARG_down, ARG_name, ARG_mtu, ARG_multicast, + ARG_arp, ARG_addr, ARG_dev }; + static const char str_on_off[] ALIGN1 = "on\0""off\0"; + enum { PARM_on = 0, PARM_off }; + smalluint key; + + while (*argv) { + /* substring search ensures that e.g. "addr" and "address" + * are both accepted */ + key = index_in_substrings(keywords, *argv); + if (key == ARG_up) { + mask |= IFF_UP; + flags |= IFF_UP; + } + if (key == ARG_down) { + mask |= IFF_UP; + flags &= ~IFF_UP; + } + if (key == ARG_name) { + NEXT_ARG(); + newname = *argv; + } + if (key == ARG_mtu) { + NEXT_ARG(); + if (mtu != -1) + duparg("mtu", *argv); + if (get_integer(&mtu, *argv, 0)) + invarg(*argv, "mtu"); + } + if (key == ARG_multicast) { + int param; + NEXT_ARG(); + mask |= IFF_MULTICAST; + param = index_in_strings(str_on_off, *argv); + if (param < 0) + die_must_be_on_off("multicast"); + if (param == PARM_on) + flags |= IFF_MULTICAST; + else + flags &= ~IFF_MULTICAST; + } + if (key == ARG_arp) { + int param; + NEXT_ARG(); + mask |= IFF_NOARP; + param = index_in_strings(str_on_off, *argv); + if (param < 0) + die_must_be_on_off("arp"); + if (param == PARM_on) + flags &= ~IFF_NOARP; + else + flags |= IFF_NOARP; + } + if (key == ARG_addr) { + NEXT_ARG(); + newaddr = *argv; + } + if (key >= ARG_dev) { + if (key == ARG_dev) { + NEXT_ARG(); + } + if (dev) + duparg2("dev", *argv); + dev = *argv; + } + argv++; + } + + if (!dev) { + bb_error_msg_and_die(bb_msg_requires_arg, "\"dev\""); + } + + if (newaddr || newbrd) { + halen = get_address(dev, &htype); + if (newaddr) { + parse_address(dev, htype, halen, newaddr, &ifr0); + } + if (newbrd) { + parse_address(dev, htype, halen, newbrd, &ifr1); + } + } + + if (newname && strcmp(dev, newname)) { + do_changename(dev, newname); + dev = newname; + } + if (qlen != -1) { + set_qlen(dev, qlen); + } + if (mtu != -1) { + set_mtu(dev, mtu); + } + if (newaddr || newbrd) { + if (newbrd) { + set_address(&ifr1, 1); + } + if (newaddr) { + set_address(&ifr0, 0); + } + } + if (mask) + do_chflags(dev, flags, mask); + return 0; +} + +static int ipaddr_list_link(char **argv) +{ + preferred_family = AF_PACKET; + return ipaddr_list_or_flush(argv, 0); +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int do_iplink(char **argv) +{ + static const char keywords[] ALIGN1 = + "set\0""show\0""lst\0""list\0"; + int key; + if (!*argv) + return ipaddr_list_link(argv); + key = index_in_substrings(keywords, *argv); + if (key < 0) + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name); + argv++; + if (key == 0) /* set */ + return do_set(argv); + /* show, lst, list */ + return ipaddr_list_link(argv); +} diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c new file mode 100644 index 0000000..a7ec66c --- /dev/null +++ b/networking/libiproute/iproute.c @@ -0,0 +1,907 @@ +/* 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 "ip_common.h" /* #include "libbb.h" is inside */ +#include "rt_names.h" +#include "utils.h" + +#ifndef RTAX_RTTVAR +#define RTAX_RTTVAR RTAX_HOPS +#endif + + +typedef struct filter_t { + int tb; + smallint 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_t; + +#define filter (*(filter_t*)&bb_common_bufsiz1) + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + bb_perror_msg("failed to send flush request"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static unsigned get_hz(void) +{ + static unsigned hz_internal; + FILE *fp; + + if (hz_internal) + return hz_internal; + + fp = fopen_for_read("/proc/net/psched"); + if (fp) { + unsigned nom, denom; + + if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) + if (nom == 1000000) + hz_internal = denom; + fclose(fp); + } + if (!hz_internal) + hz_internal = sysconf(_SC_CLK_TCK); + return hz_internal; +} + +static int print_route(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *n, void *arg UNUSED_PARAM) +{ + 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_and_die("wrong nlmsg len %d", len); + + 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()) + bb_error_msg_and_die("flush"); + } + 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 = 1; + return 0; + } + + if (n->nlmsg_type == RTM_DELROUTE) { + printf("Deleted "); + } + if (r->rtm_type != RTN_UNICAST && !filter.type) { + printf("%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + } + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + printf("%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 { + printf("%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) { + printf("0/%d ", r->rtm_dst_len); + } else { + printf("default "); + } + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + printf("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 { + printf("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) { + printf("from 0/%u ", r->rtm_src_len); + } + if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) { + printf("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) { + printf("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. + */ + printf(" 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]) { + printf(" metric %d ", *(uint32_t*)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)) { + if (r->rtm_flags & RTM_F_CLONED) { + printf("%c cache ", _SL_); + } + if (ci->rta_expires) { + printf(" expires %dsec", ci->rta_expires / get_hz()); + } + if (ci->rta_error != 0) { + printf(" error %d", ci->rta_error); + } + } else if (ci) { + if (ci->rta_error != 0) + printf(" error %d", ci->rta_error); + } + } + if (tb[RTA_IIF] && filter.iifmask != -1) { + printf(" iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF]))); + } + bb_putchar('\n'); + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int iproute_modify(int cmd, unsigned flags, char **argv) +{ + static const char keywords[] ALIGN1 = + "src\0""via\0""mtu\0""lock\0""protocol\0"USE_FEATURE_IP_RULE("table\0") + "dev\0""oif\0""to\0""metric\0"; + enum { + ARG_src, + ARG_via, + ARG_mtu, PARM_lock, + ARG_protocol, +USE_FEATURE_IP_RULE(ARG_table,) + ARG_dev, + ARG_oif, + ARG_to, + ARG_metric, + }; + enum { + gw_ok = 1 << 0, + dst_ok = 1 << 1, + proto_ok = 1 << 2, + type_ok = 1 << 3 + }; + 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; + smalluint ok = 0; + int arg; + + 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; + if (RT_TABLE_MAIN) /* if it is zero, memset already did it */ + req.r.rtm_table = RT_TABLE_MAIN; + if (RT_SCOPE_NOWHERE) + 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 (*argv) { + arg = index_in_substrings(keywords, *argv); + if (arg == ARG_src) { + 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 (arg == ARG_via) { + inet_prefix addr; + ok |= gw_ok; + 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 (arg == ARG_mtu) { + unsigned mtu; + NEXT_ARG(); + if (index_in_strings(keywords, *argv) == PARM_lock) { + 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 (arg == ARG_protocol) { + uint32_t prot; + NEXT_ARG(); + if (rtnl_rtprot_a2n(&prot, *argv)) + invarg(*argv, "protocol"); + req.r.rtm_protocol = prot; + ok |= proto_ok; +#if ENABLE_FEATURE_IP_RULE + } else if (arg == ARG_table) { + uint32_t tid; + NEXT_ARG(); + if (rtnl_rttable_a2n(&tid, *argv)) + invarg(*argv, "table"); + req.r.rtm_table = tid; +#endif + } else if (arg == ARG_dev || arg == ARG_oif) { + NEXT_ARG(); + d = *argv; + } else if (arg == ARG_metric) { + uint32_t metric; + NEXT_ARG(); + if (get_u32(&metric, *argv, 0)) + invarg(*argv, "metric"); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric); + } else { + int type; + inet_prefix dst; + + if (arg == ARG_to) { + NEXT_ARG(); + } + if ((**argv < '0' || **argv > '9') + && rtnl_rtntype_a2n(&type, *argv) == 0) { + NEXT_ARG(); + req.r.rtm_type = type; + ok |= type_ok; + } + + if (ok & 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; + ok |= dst_ok; + if (dst.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } + } + argv++; + } + + xrtnl_open(&rth); + + if (d) { + int idx; + + ll_init_map(&rth); + + if (d) { + idx = xll_name_to_index(d); + 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_type == RTN_LOCAL || req.r.rtm_type == RTN_NAT) + req.r.rtm_scope = RT_SCOPE_HOST; + else if (req.r.rtm_type == RTN_BROADCAST || + req.r.rtm_type == RTN_MULTICAST || + req.r.rtm_type == RTN_ANYCAST) + req.r.rtm_scope = RT_SCOPE_LINK; + else if (req.r.rtm_type == RTN_UNICAST || req.r.rtm_type == RTN_UNSPEC) { + if (cmd == RTM_DELROUTE) + req.r.rtm_scope = RT_SCOPE_NOWHERE; + else if (!(ok & gw_ok)) + req.r.rtm_scope = RT_SCOPE_LINK; + } + + 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) { + return 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); + if (RTM_GETROUTE) + req.nlh.nlmsg_type = RTM_GETROUTE; + if (NLM_F_ROOT | NLM_F_REQUEST) + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST; + /*req.nlh.nlmsg_pid = 0; - memset did it already */ + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.rtm.rtm_family = family; + if (RTM_F_CLONED) + req.rtm.rtm_flags = RTM_F_CLONED; + + return xsendto(rth->fd, (void*)&req, sizeof(req), (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +static void iproute_flush_cache(void) +{ + static const char fn[] ALIGN1 = "/proc/sys/net/ipv4/route/flush"; + int flush_fd = open_or_warn(fn, O_WRONLY); + + if (flush_fd < 0) { + return; + } + + if (write(flush_fd, "-1", 2) < 2) { + bb_perror_msg("cannot flush routing cache"); + return; + } + close(flush_fd); +} + +static void iproute_reset_filter(void) +{ + memset(&filter, 0, sizeof(filter)); + filter.mdst.bitlen = -1; + filter.msrc.bitlen = -1; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int iproute_list_or_flush(char **argv, int flush) +{ + int do_ipv6 = preferred_family; + struct rtnl_handle rth; + char *id = NULL; + char *od = NULL; + static const char keywords[] ALIGN1 = + /* "ip route list/flush" parameters: */ + "protocol\0" "dev\0" "oif\0" "iif\0" + "via\0" "table\0" "cache\0" + "from\0" "to\0" + /* and possible further keywords */ + "all\0" + "root\0" + "match\0" + "exact\0" + "main\0" + ; + enum { + KW_proto, KW_dev, KW_oif, KW_iif, + KW_via, KW_table, KW_cache, + KW_from, KW_to, + /* */ + KW_all, + KW_root, + KW_match, + KW_exact, + KW_main, + }; + int arg, parm; + + iproute_reset_filter(); + filter.tb = RT_TABLE_MAIN; + + if (flush && !*argv) + bb_error_msg_and_die(bb_msg_requires_arg, "\"ip route flush\""); + + while (*argv) { + arg = index_in_substrings(keywords, *argv); + if (arg == KW_proto) { + uint32_t prot = 0; + NEXT_ARG(); + filter.protocolmask = -1; + if (rtnl_rtprot_a2n(&prot, *argv)) { + if (index_in_strings(keywords, *argv) != KW_all) + invarg(*argv, "protocol"); + prot = 0; + filter.protocolmask = 0; + } + filter.protocol = prot; + } else if (arg == KW_dev || arg == KW_oif) { + NEXT_ARG(); + od = *argv; + } else if (arg == KW_iif) { + NEXT_ARG(); + id = *argv; + } else if (arg == KW_via) { + NEXT_ARG(); + get_prefix(&filter.rvia, *argv, do_ipv6); + } else if (arg == KW_table) { /* table all/cache/main */ + NEXT_ARG(); + parm = index_in_substrings(keywords, *argv); + if (parm == KW_cache) + filter.tb = -1; + else if (parm == KW_all) + filter.tb = 0; + else if (parm != KW_main) { +#if ENABLE_FEATURE_IP_RULE + uint32_t tid; + if (rtnl_rttable_a2n(&tid, *argv)) + invarg(*argv, "table"); + filter.tb = tid; +#else + invarg(*argv, "table"); +#endif + } + } else if (arg == KW_cache) { + /* The command 'ip route flush cache' is used by OpenSWAN. + * Assuming it's a synonym for 'ip route flush table cache' */ + filter.tb = -1; + } else if (arg == KW_from) { + NEXT_ARG(); + parm = index_in_substrings(keywords, *argv); + if (parm == KW_root) { + NEXT_ARG(); + get_prefix(&filter.rsrc, *argv, do_ipv6); + } else if (parm == KW_match) { + NEXT_ARG(); + get_prefix(&filter.msrc, *argv, do_ipv6); + } else { + if (parm == KW_exact) + NEXT_ARG(); + get_prefix(&filter.msrc, *argv, do_ipv6); + filter.rsrc = filter.msrc; + } + } else { /* "to" is the default parameter */ + if (arg == KW_to) { + NEXT_ARG(); + arg = index_in_substrings(keywords, *argv); + } + /* parm = arg; - would be more plausible, but we reuse 'arg' here */ + if (arg == KW_root) { + NEXT_ARG(); + get_prefix(&filter.rdst, *argv, do_ipv6); + } else if (arg == KW_match) { + NEXT_ARG(); + get_prefix(&filter.mdst, *argv, do_ipv6); + } else { /* "to exact" is the default */ + if (arg == KW_exact) + NEXT_ARG(); + get_prefix(&filter.mdst, *argv, do_ipv6); + filter.rdst = filter.mdst; + } + } + argv++; + } + + if (do_ipv6 == AF_UNSPEC && filter.tb) { + do_ipv6 = AF_INET; + } + + xrtnl_open(&rth); + ll_init_map(&rth); + + if (id || od) { + int idx; + + if (id) { + idx = xll_name_to_index(id); + filter.iif = idx; + filter.iifmask = -1; + } + if (od) { + idx = xll_name_to_index(od); + filter.oif = idx; + filter.oifmask = -1; + } + } + + if (flush) { + char flushb[4096-512]; + + if (filter.tb == -1) { /* "flush table cache" */ + 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 (;;) { + xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE); + filter.flushed = 0; + xrtnl_dump_filter(&rth, print_route, NULL); + if (filter.flushed == 0) + return 0; + if (flush_update()) + return 1; + } + } + + if (filter.tb != -1) { + xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE); + } else if (rtnl_rtcache_request(&rth, do_ipv6) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + xrtnl_dump_filter(&rth, print_route, NULL); + + return 0; +} + + +/* Return value becomes exitcode. It's okay to not return at all */ +static int iproute_get(char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char *idev = NULL; + char *odev = NULL; + bool connected = 0; + bool from_ok = 0; + static const char options[] ALIGN1 = + "from\0""iif\0""oif\0""dev\0""notify\0""connected\0""to\0"; + + memset(&req, 0, sizeof(req)); + + iproute_reset_filter(); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + if (NLM_F_REQUEST) + req.n.nlmsg_flags = NLM_F_REQUEST; + if (RTM_GETROUTE) + req.n.nlmsg_type = RTM_GETROUTE; + req.r.rtm_family = preferred_family; + /*req.r.rtm_table = 0; - memset did this already */ + /*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 (*argv) { + switch (index_in_strings(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; + } + argv++; + } + } + + if (req.r.rtm_dst_len == 0) { + bb_error_msg_and_die("need at least destination address"); + } + + xrtnl_open(&rth); + + ll_init_map(&rth); + + if (idev || odev) { + int idx; + + if (idev) { + idx = xll_name_to_index(idev); + addattr32(&req.n, sizeof(req), RTA_IIF, idx); + } + if (odev) { + idx = xll_name_to_index(odev); + 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) { + return 2; + } + + if (connected && !from_ok) { + struct rtmsg *r = NLMSG_DATA(&req.n); + int len = req.n.nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + + print_route(NULL, &req.n, NULL); + + if (req.n.nlmsg_type != RTM_NEWROUTE) { + bb_error_msg_and_die("not a route?"); + } + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg_and_die("wrong len %d", len); + } + + 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_and_die("failed to connect the route"); + } + 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) { + return 2; + } + } + print_route(NULL, &req.n, NULL); + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int do_iproute(char **argv) +{ + static const char ip_route_commands[] ALIGN1 = + /*0-3*/ "add\0""append\0""change\0""chg\0" + /*4-7*/ "delete\0""get\0""list\0""show\0" + /*8..*/ "prepend\0""replace\0""test\0""flush\0"; + int command_num; + unsigned flags = 0; + int cmd = RTM_NEWROUTE; + + if (!*argv) + return iproute_list_or_flush(argv, 0); + + /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */ + /* It probably means that it is using "first match" rule */ + command_num = index_in_substrings(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 */ + cmd = RTM_DELROUTE; + break; + case 5: /* get */ + return iproute_get(argv+1); + case 6: /* list */ + case 7: /* show */ + return iproute_list_or_flush(argv+1, 0); + case 8: /* prepend */ + flags = NLM_F_CREATE; + break; + case 9: /* replace */ + flags = NLM_F_CREATE|NLM_F_REPLACE; + break; + case 10: /* test */ + flags = NLM_F_EXCL; + break; + case 11: /* flush */ + return iproute_list_or_flush(argv+1, 1); + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + + return iproute_modify(cmd, flags, argv+1); +} diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c new file mode 100644 index 0000000..ca22546 --- /dev/null +++ b/networking/libiproute/iprule.c @@ -0,0 +1,334 @@ +/* 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 + * initially integrated into busybox by Bernhard Reutner-Fischer + */ + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> + +#include "ip_common.h" /* #include "libbb.h" is inside */ +#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(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *n, void *arg UNUSED_PARAM) +{ + 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]) + printf("%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY])); + else + printf("0:\t"); + + printf("from "); + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + printf("%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 { + fputs(format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), stdout); + } + } else if (r->rtm_src_len) { + printf("0/%d", r->rtm_src_len); + } else { + printf("all"); + } + bb_putchar(' '); + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + printf("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 { + printf("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) { + printf("to 0/%d ", r->rtm_dst_len); + } + + if (r->rtm_tos) { + printf("tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1))); + } + if (tb[RTA_PROTOINFO]) { + printf("fwmark %#x ", *(uint32_t*)RTA_DATA(tb[RTA_PROTOINFO])); + } + + if (tb[RTA_IIF]) { + printf("iif %s ", (char*)RTA_DATA(tb[RTA_IIF])); + } + + if (r->rtm_table) + printf("lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1))); + + if (tb[RTA_FLOW]) { + uint32_t to = *(uint32_t*)RTA_DATA(tb[RTA_FLOW]); + uint32_t from = to>>16; + to &= 0xFFFF; + if (from) { + printf("realms %s/", + rtnl_rtrealm_n2a(from, b1, sizeof(b1))); + } + printf("%s ", + rtnl_rtrealm_n2a(to, b1, sizeof(b1))); + } + + if (r->rtm_type == RTN_NAT) { + if (tb[RTA_GATEWAY]) { + printf("map-to %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } else + printf("masquerade"); + } else if (r->rtm_type != RTN_UNICAST) + fputs(rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)), stdout); + + bb_putchar('\n'); + /*fflush(stdout);*/ + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int iprule_list(char **argv) +{ + struct rtnl_handle rth; + int af = preferred_family; + + if (af == AF_UNSPEC) + af = AF_INET; + + if (*argv) { + //bb_error_msg("\"rule show\" needs no arguments"); + bb_warn_ignoring_args(1); + return -1; + } + + xrtnl_open(&rth); + + xrtnl_wilddump_request(&rth, af, RTM_GETRULE); + xrtnl_dump_filter(&rth, print_rule, NULL); + + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int iprule_modify(int cmd, char **argv) +{ + static const char keywords[] ALIGN1 = + "from\0""to\0""preference\0""order\0""priority\0" + "tos\0""fwmark\0""realms\0""table\0""lookup\0""dev\0" + "iif\0""nat\0""map-to\0""type\0""help\0"; + enum { + ARG_from = 1, ARG_to, ARG_preference, ARG_order, ARG_priority, + ARG_tos, ARG_fwmark, ARG_realms, ARG_table, ARG_lookup, ARG_dev, + ARG_iif, ARG_nat, ARG_map_to, ARG_type, ARG_help + }; + bool table_ok = 0; + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + smalluint key; + + 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 (*argv) { + key = index_in_substrings(keywords, *argv) + 1; + if (key == 0) /* no match found in keywords array, bail out. */ + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name); + if (key == ARG_from) { + 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 (key == ARG_to) { + 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 (key == ARG_preference || + key == ARG_order || + key == ARG_priority) { + uint32_t pref; + NEXT_ARG(); + if (get_u32(&pref, *argv, 0)) + invarg(*argv, "preference"); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref); + } else if (key == ARG_tos) { + uint32_t tos; + NEXT_ARG(); + if (rtnl_dsfield_a2n(&tos, *argv)) + invarg(*argv, "TOS"); + req.r.rtm_tos = tos; + } else if (key == ARG_fwmark) { + uint32_t fwmark; + NEXT_ARG(); + if (get_u32(&fwmark, *argv, 0)) + invarg(*argv, "fwmark"); + addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark); + } else if (key == ARG_realms) { + uint32_t realm; + NEXT_ARG(); + if (get_rt_realms(&realm, *argv)) + invarg(*argv, "realms"); + addattr32(&req.n, sizeof(req), RTA_FLOW, realm); + } else if (key == ARG_table || + key == ARG_lookup) { + uint32_t tid; + NEXT_ARG(); + if (rtnl_rttable_a2n(&tid, *argv)) + invarg(*argv, "table ID"); + req.r.rtm_table = tid; + table_ok = 1; + } else if (key == ARG_dev || + key == ARG_iif) { + NEXT_ARG(); + addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1); + } else if (key == ARG_nat || + key == ARG_map_to) { + NEXT_ARG(); + addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv)); + req.r.rtm_type = RTN_NAT; + } else { + int type; + + if (key == ARG_type) { + NEXT_ARG(); + } + if (key == ARG_help) + bb_show_usage(); + if (rtnl_rtntype_a2n(&type, *argv)) + invarg(*argv, "type"); + req.r.rtm_type = type; + } + 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; + + xrtnl_open(&rth); + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + return 2; + + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int do_iprule(char **argv) +{ + static const char ip_rule_commands[] ALIGN1 = + "add\0""delete\0""list\0""show\0"; + int cmd = 2; /* list */ + + if (!*argv) + return iprule_list(argv); + + cmd = index_in_substrings(ip_rule_commands, *argv); + switch (cmd) { + case 0: /* add */ + cmd = RTM_NEWRULE; + break; + case 1: /* delete */ + cmd = RTM_DELRULE; + break; + case 2: /* list */ + case 3: /* show */ + return iprule_list(argv+1); + break; + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + return iprule_modify(cmd, argv+1); +} diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c new file mode 100644 index 0000000..14fc6bb --- /dev/null +++ b/networking/libiproute/iptunnel.c @@ -0,0 +1,580 @@ +/* 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 <netinet/ip.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <asm/types.h> + +#ifndef __constant_htons +#define __constant_htons htons +#endif + +// FYI: #define SIOCDEVPRIVATE 0x89F0 + +/* From linux/if_tunnel.h. #including it proved troublesome + * (redefiniton errors due to name collisions in linux/ and net[inet]/) */ +#define SIOCGETTUNNEL (SIOCDEVPRIVATE + 0) +#define SIOCADDTUNNEL (SIOCDEVPRIVATE + 1) +#define SIOCDELTUNNEL (SIOCDEVPRIVATE + 2) +#define SIOCCHGTUNNEL (SIOCDEVPRIVATE + 3) +//#define SIOCGETPRL (SIOCDEVPRIVATE + 4) +//#define SIOCADDPRL (SIOCDEVPRIVATE + 5) +//#define SIOCDELPRL (SIOCDEVPRIVATE + 6) +//#define SIOCCHGPRL (SIOCDEVPRIVATE + 7) +#define GRE_CSUM __constant_htons(0x8000) +//#define GRE_ROUTING __constant_htons(0x4000) +#define GRE_KEY __constant_htons(0x2000) +#define GRE_SEQ __constant_htons(0x1000) +//#define GRE_STRICT __constant_htons(0x0800) +//#define GRE_REC __constant_htons(0x0700) +//#define GRE_FLAGS __constant_htons(0x00F8) +//#define GRE_VERSION __constant_htons(0x0007) +struct ip_tunnel_parm { + char name[IFNAMSIZ]; + int link; + uint16_t i_flags; + uint16_t o_flags; + uint32_t i_key; + uint32_t o_key; + struct iphdr iph; +}; +/* SIT-mode i_flags */ +//#define SIT_ISATAP 0x0001 +//struct ip_tunnel_prl { +// uint32_t addr; +// uint16_t flags; +// uint16_t __reserved; +// uint32_t datalen; +// uint32_t __reserved2; +// /* data follows */ +//}; +///* PRL flags */ +//#define PRL_DEFAULT 0x0001 + +#include "ip_common.h" /* #include "libbb.h" is inside */ +#include "rt_names.h" +#include "utils.h" + + +/* Dies on error */ +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); + xioctl(fd, SIOCGIFINDEX, &ifr); + close(fd); + return ifr.ifr_ifindex; +} + +static int do_ioctl_get_iftype(char *dev) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr); + close(fd); + return err ? -1 : ifr.ifr_addr.sa_family; +} + +static char *do_ioctl_get_ifname(int idx) +{ + struct ifreq ifr; + int fd; + int err; + + ifr.ifr_ifindex = idx; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl_or_warn(fd, SIOCGIFNAME, &ifr); + close(fd); + return err ? NULL : xstrndup(ifr.ifr_name, sizeof(ifr.ifr_name)); +} + +static int do_get_ioctl(const 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_or_warn(fd, SIOCGETTUNNEL, &ifr); + close(fd); + return err; +} + +/* Dies on error, otherwise returns 0 */ +static int do_add_ioctl(int cmd, const char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + + 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); +#if ENABLE_IOCTL_HEX2STR_ERROR + /* #define magic will turn ioctl# into string */ + if (cmd == SIOCCHGTUNNEL) + xioctl(fd, SIOCCHGTUNNEL, &ifr); + else + xioctl(fd, SIOCADDTUNNEL, &ifr); +#else + xioctl(fd, cmd, &ifr); +#endif + close(fd); + return 0; +} + +/* Dies on error, otherwise returns 0 */ +static int do_del_ioctl(const char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + + 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); + xioctl(fd, SIOCDELTUNNEL, &ifr); + close(fd); + return 0; +} + +/* Dies on error */ +static void parse_args(char **argv, int cmd, struct ip_tunnel_parm *p) +{ + static const char keywords[] ALIGN1 = + "mode\0""ipip\0""ip/ip\0""gre\0""gre/ip\0""sit\0""ipv6/ip\0" + "key\0""ikey\0""okey\0""seq\0""iseq\0""oseq\0" + "csum\0""icsum\0""ocsum\0""nopmtudisc\0""pmtudisc\0" + "remote\0""any\0""local\0""dev\0" + "ttl\0""inherit\0""tos\0""dsfield\0" + "name\0"; + enum { + ARG_mode, ARG_ipip, ARG_ip_ip, ARG_gre, ARG_gre_ip, ARG_sit, ARG_ip6_ip, + ARG_key, ARG_ikey, ARG_okey, ARG_seq, ARG_iseq, ARG_oseq, + ARG_csum, ARG_icsum, ARG_ocsum, ARG_nopmtudisc, ARG_pmtudisc, + ARG_remote, ARG_any, ARG_local, ARG_dev, + ARG_ttl, ARG_inherit, ARG_tos, ARG_dsfield, + ARG_name + }; + int count = 0; + char medium[IFNAMSIZ]; + int key; + + 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 (*argv) { + key = index_in_strings(keywords, *argv); + if (key == ARG_mode) { + NEXT_ARG(); + key = index_in_strings(keywords, *argv); + if (key == ARG_ipip || + key == ARG_ip_ip) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) { + bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one"); + } + p->iph.protocol = IPPROTO_IPIP; + } else if (key == ARG_gre || + key == ARG_gre_ip) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) { + bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one"); + } + p->iph.protocol = IPPROTO_GRE; + } else if (key == ARG_sit || + key == ARG_ip6_ip) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) { + bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one"); + } + p->iph.protocol = IPPROTO_IPV6; + } else { + bb_error_msg_and_die("%s tunnel mode", "cannot guess"); + } + } else if (key == ARG_key) { + 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) { + invarg(*argv, "key"); + } + p->i_key = p->o_key = htonl(uval); + } + } else if (key == ARG_ikey) { + 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) { + invarg(*argv, "ikey"); + } + p->i_key = htonl(uval); + } + } else if (key == ARG_okey) { + 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) { + invarg(*argv, "okey"); + } + p->o_key = htonl(uval); + } + } else if (key == ARG_seq) { + p->i_flags |= GRE_SEQ; + p->o_flags |= GRE_SEQ; + } else if (key == ARG_iseq) { + p->i_flags |= GRE_SEQ; + } else if (key == ARG_oseq) { + p->o_flags |= GRE_SEQ; + } else if (key == ARG_csum) { + p->i_flags |= GRE_CSUM; + p->o_flags |= GRE_CSUM; + } else if (key == ARG_icsum) { + p->i_flags |= GRE_CSUM; + } else if (key == ARG_ocsum) { + p->o_flags |= GRE_CSUM; + } else if (key == ARG_nopmtudisc) { + p->iph.frag_off = 0; + } else if (key == ARG_pmtudisc) { + p->iph.frag_off = htons(IP_DF); + } else if (key == ARG_remote) { + NEXT_ARG(); + key = index_in_strings(keywords, *argv); + if (key != ARG_any) + p->iph.daddr = get_addr32(*argv); + } else if (key == ARG_local) { + NEXT_ARG(); + key = index_in_strings(keywords, *argv); + if (key != ARG_any) + p->iph.saddr = get_addr32(*argv); + } else if (key == ARG_dev) { + NEXT_ARG(); + strncpy(medium, *argv, IFNAMSIZ-1); + } else if (key == ARG_ttl) { + unsigned uval; + NEXT_ARG(); + key = index_in_strings(keywords, *argv); + if (key != ARG_inherit) { + if (get_unsigned(&uval, *argv, 0)) + invarg(*argv, "TTL"); + if (uval > 255) + invarg(*argv, "TTL must be <=255"); + p->iph.ttl = uval; + } + } else if (key == ARG_tos || + key == ARG_dsfield) { + uint32_t uval; + NEXT_ARG(); + key = index_in_strings(keywords, *argv); + if (key != ARG_inherit) { + if (rtnl_dsfield_a2n(&uval, *argv)) + invarg(*argv, "TOS"); + p->iph.tos = uval; + } else + p->iph.tos = 1; + } else { + if (key == ARG_name) { + 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)) + exit(EXIT_FAILURE); + *p = old_p; + } + } + count++; + 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_and_die("keys are not allowed with ipip and sit"); + } + } + + if (medium[0]) { + p->link = do_ioctl_get_ifindex(medium); + } + + 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_and_die("broadcast tunnel requires a source address"); + } +} + + +/* Return value becomes exitcode. It's okay to not return at all */ +static int do_add(int cmd, char **argv) +{ + struct ip_tunnel_parm p; + + parse_args(argv, cmd, &p); + + if (p.iph.ttl && p.iph.frag_off == 0) { + bb_error_msg_and_die("ttl != 0 and noptmudisc are incompatible"); + } + + 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_and_die("cannot determine tunnel mode (ipip, gre or sit)"); + } +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int do_del(char **argv) +{ + struct ip_tunnel_parm p; + + parse_args(argv, SIOCDELTUNNEL, &p); + + 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); + } +} + +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); + free(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("%c Drop packets out of sequence.\n", _SL_); + if (p->i_flags & GRE_CSUM) + printf("%c Checksum in received packet is required.", _SL_); + if (p->o_flags & GRE_SEQ) + printf("%c Sequence packets on output.", _SL_); + if (p->o_flags & GRE_CSUM) + printf("%c Checksum output packets.", _SL_); +} + +static void 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_or_warn("/proc/net/dev", "r"); + + if (fp == NULL) { + return; + } + /* skip headers */ + fgets(buf, sizeof(buf), fp); + fgets(buf, sizeof(buf), fp); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *ptr; + + /*buf[sizeof(buf) - 1] = 0; - fgets is safe anyway */ + ptr = strchr(buf, ':'); + if (ptr == NULL || + (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) { + bb_error_msg("wrong format of /proc/net/dev"); + return; + } + 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("cannot 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); + bb_putchar('\n'); + } +} + +/* Return value becomes exitcode. It's okay to not return at all */ +static int do_show(char **argv) +{ + int err; + struct ip_tunnel_parm p; + + parse_args(argv, SIOCGETTUNNEL, &p); + + 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); + bb_putchar('\n'); + return 0; +} + +/* Return value becomes exitcode. It's okay to not return at all */ +int do_iptunnel(char **argv) +{ + static const char keywords[] ALIGN1 = + "add\0""change\0""delete\0""show\0""list\0""lst\0"; + enum { ARG_add = 0, ARG_change, ARG_del, ARG_show, ARG_list, ARG_lst }; + int key; + + if (*argv) { + key = index_in_substrings(keywords, *argv); + if (key < 0) + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name); + argv++; + if (key == ARG_add) + return do_add(SIOCADDTUNNEL, argv); + if (key == ARG_change) + return do_add(SIOCCHGTUNNEL, argv); + if (key == ARG_del) + return do_del(argv); + } + return do_show(argv); +} diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c new file mode 100644 index 0000000..01454fb --- /dev/null +++ b/networking/libiproute/libnetlink.c @@ -0,0 +1,409 @@ +/* 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 <sys/socket.h> +#include <sys/uio.h> + +#include "libbb.h" +#include "libnetlink.h" + +void FAST_FUNC rtnl_close(struct rtnl_handle *rth) +{ + close(rth->fd); +} + +int FAST_FUNC xrtnl_open(struct rtnl_handle *rth/*, unsigned subscriptions*/) +{ + socklen_t addr_len; + + memset(rth, 0, sizeof(rth)); + + rth->fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + /*rth->local.nl_groups = subscriptions;*/ + + xbind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)); + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) + bb_perror_msg_and_die("getsockname"); + if (addr_len != sizeof(rth->local)) + bb_error_msg_and_die("wrong address length %d", addr_len); + if (rth->local.nl_family != AF_NETLINK) + bb_error_msg_and_die("wrong address family %d", rth->local.nl_family); + rth->seq = time(NULL); + return 0; +} + +int FAST_FUNC xrtnl_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 xsendto(rth->fd, (void*)&req, sizeof(req), + (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int FAST_FUNC 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 xsendto(rth->fd, buf, len, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int FAST_FUNC 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); +} + +static int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg1/*, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2*/) +{ + int retval = -1; + char *buf = xmalloc(8*1024); /* avoid big stack buffer */ + struct sockaddr_nl nladdr; + struct iovec iov = { buf, 8*1024 }; + + 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"); + goto ret; + } + 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) { +// retval = err; +// goto ret; +// } +// } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + goto ret_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"); + } + goto ret; + } + err = filter(&nladdr, h, arg1); + if (err < 0) { + retval = err; + goto ret; + } + + 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); + } + } /* while (1) */ + ret_0: + retval++; /* = 0 */ + ret: + free(buf); + return retval; +} + +int FAST_FUNC xrtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *, void *), + void *arg1) +{ + int ret = rtnl_dump_filter(rth, filter, arg1/*, NULL, NULL*/); + if (ret < 0) + bb_error_msg_and_die("dump terminated"); + return ret; +} + +int FAST_FUNC rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, + pid_t peer, unsigned groups, + struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *, void *), + void *jarg) +{ +/* bbox doesn't use parameters no. 3, 4, 6, 7, they are stubbed out */ +#define peer 0 +#define groups 0 +#define junk NULL +#define jarg NULL + int retval = -1; + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { (void*)n, n->nlmsg_len }; + char *buf = xmalloc(8*1024); /* avoid big stack buffer */ + 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"); + goto ret; + } + + iov.iov_base = buf; + + while (1) { + iov.iov_len = 8*1024; + 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"); + goto ret; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + for (h = (struct nlmsghdr*)buf; status >= (int)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"); + goto ret; + } + 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) { +// retval = l_err; +// goto ret; +// } +// } + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (l < (int)sizeof(struct nlmsgerr)) { + bb_error_msg("ERROR truncated"); + } else { + errno = - err->error; + if (errno == 0) { + if (answer) { + memcpy(answer, h, h->nlmsg_len); + } + goto ret_0; + } + bb_perror_msg("RTNETLINK answers"); + } + goto ret; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + goto ret_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); + } + } /* while (1) */ + ret_0: + retval++; /* = 0 */ + ret: + free(buf); + return retval; +} + +int FAST_FUNC addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if ((int)(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 FAST_FUNC addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if ((int)(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 FAST_FUNC rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t 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 FAST_FUNC 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 FAST_FUNC 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..079153b --- /dev/null +++ b/networking/libiproute/libnetlink.h @@ -0,0 +1,55 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include <linux/types.h> +/* We need linux/types.h because older kernels use __u32 etc + * in linux/[rt]netlink.h. 2.6.19 seems to be ok, though */ +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + uint32_t seq; + uint32_t dump; +}; + +extern int xrtnl_open(struct rtnl_handle *rth) FAST_FUNC; +extern void rtnl_close(struct rtnl_handle *rth) FAST_FUNC; +extern int xrtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type) FAST_FUNC; +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) FAST_FUNC; +extern int xrtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(const struct sockaddr_nl*, struct nlmsghdr *n, void*), + void *arg1) FAST_FUNC; + +/* bbox doesn't use parameters no. 3, 4, 6, 7, stub them out */ +#define rtnl_talk(rtnl, n, peer, groups, answer, junk, jarg) \ + rtnl_talk(rtnl, n, answer) +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) FAST_FUNC; + +extern int rtnl_send(struct rtnl_handle *rth, char *buf, int) FAST_FUNC; + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data) FAST_FUNC; +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) FAST_FUNC; +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t data) FAST_FUNC; +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) FAST_FUNC; + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) FAST_FUNC; + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif /* __LIBNETLINK_H__ */ diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c new file mode 100644 index 0000000..e732efd --- /dev/null +++ b/networking/libiproute/ll_addr.c @@ -0,0 +1,79 @@ +/* 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 <net/if_arp.h> + +#include "libbb.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"+1, 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 || (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..eeae4e2 --- /dev/null +++ b/networking/libiproute/ll_map.c @@ -0,0 +1,200 @@ +/* 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 <net/if.h> /* struct ifreq and co. */ + +#include "libbb.h" +#include "libnetlink.h" +#include "ll_map.h" + +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]; + +static struct idxmap *find_by_index(int idx) +{ + struct idxmap *im; + + for (im = idxmap[idx & 0xF]; im; im = im->next) + if (im->index == idx) + return im; + return NULL; +} + +int ll_remember_index(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *n, + void *arg UNUSED_PARAM) +{ + 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) + goto found; + + im = xmalloc(sizeof(*im)); + im->next = *imp; + im->index = ifi->ifi_index; + *imp = im; + found: + 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 > (int)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 "*"; + im = find_by_index(idx); + if (im) + 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); +} + +#ifdef UNUSED +int ll_index_to_type(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return -1; + im = find_by_index(idx); + if (im) + return im->type; + return -1; +} +#endif + +unsigned ll_index_to_flags(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return 0; + im = find_by_index(idx); + if (im) + return im->flags; + return 0; +} + +int xll_name_to_index(const char *const name) +{ + int ret = 0; + int sock_fd; + +/* caching is not warranted - no users which repeatedly call it */ +#ifdef UNUSED + static char ncache[16]; + static int icache; + + struct idxmap *im; + int i; + + if (name == NULL) + goto out; + if (icache && strcmp(name, ncache) == 0) { + ret = icache; + goto out; + } + 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); + ret = im->index; + goto out; + } + } + } + /* 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 */ +#endif + + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd) { + struct ifreq ifr; + int tmp; + + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifindex = -1; + tmp = ioctl(sock_fd, SIOCGIFINDEX, &ifr); + close(sock_fd); + if (tmp >= 0) + /* In theory, we should redump the interface list + * to update our cache, this is left as an exercise + * to the reader... Jean II */ + ret = ifr.ifr_ifindex; + } +/* out:*/ + if (ret <= 0) + bb_error_msg_and_die("cannot find device \"%s\"", name); + return ret; +} + +int ll_init_map(struct rtnl_handle *rth) +{ + xrtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK); + xrtnl_dump_filter(rth, ll_remember_index, &idxmap); + return 0; +} diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h new file mode 100644 index 0000000..6d64ac1 --- /dev/null +++ b/networking/libiproute/ll_map.h @@ -0,0 +1,21 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LL_MAP_H__ +#define __LL_MAP_H__ 1 + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +int ll_remember_index(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +int ll_init_map(struct rtnl_handle *rth); +int xll_name_to_index(const char *const name); +const char *ll_index_to_name(int idx); +const char *ll_idx_n2a(int idx, char *buf); +/* int ll_index_to_type(int idx); */ +unsigned ll_index_to_flags(int idx); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif /* __LL_MAP_H__ */ diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c new file mode 100644 index 0000000..b826873 --- /dev/null +++ b/networking/libiproute/ll_proto.c @@ -0,0 +1,127 @@ +/* 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 "rt_names.h" +#include "utils.h" + +#if defined(__GLIBC__) && __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include <net/ethernet.h> +#else +#include <linux/if_ether.h> +#endif + +#if !ENABLE_WERROR +#warning de-bloat +#endif +/* Before re-enabling this, please (1) conditionalize exotic protocols + * on CONFIG_something, and (2) decouple strings and numbers + * (use llproto_ids[] = n,n,n..; and llproto_names[] = "loop\0" "pup\0" ...;) + */ + +#define __PF(f,n) { ETH_P_##f, #n }, +static struct { + int id; + const 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) +{ + unsigned i; + id = ntohs(id); + for (i = 0; i < ARRAY_SIZE(llproto_names); 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) +{ + unsigned i; + for (i = 0; i < ARRAY_SIZE(llproto_names); 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..d5d2a1f --- /dev/null +++ b/networking/libiproute/ll_types.c @@ -0,0 +1,205 @@ +/* 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 <arpa/inet.h> +#include <linux/if_arp.h> + +#include "libbb.h" +#include "rt_names.h" + +const char *ll_type_n2a(int type, char *buf, int len) +{ + static const char arphrd_name[] = + /* 0, */ "generic" "\0" + /* ARPHRD_LOOPBACK, */ "loopback" "\0" + /* ARPHRD_ETHER, */ "ether" "\0" +#ifdef ARPHRD_INFINIBAND + /* ARPHRD_INFINIBAND, */ "infiniband" "\0" +#endif +#ifdef ARPHRD_IEEE802_TR + /* ARPHRD_IEEE802, */ "ieee802" "\0" + /* ARPHRD_IEEE802_TR, */ "tr" "\0" +#else + /* ARPHRD_IEEE802, */ "tr" "\0" +#endif +#ifdef ARPHRD_IEEE80211 + /* ARPHRD_IEEE80211, */ "ieee802.11" "\0" +#endif +#ifdef ARPHRD_IEEE1394 + /* ARPHRD_IEEE1394, */ "ieee1394" "\0" +#endif + /* ARPHRD_IRDA, */ "irda" "\0" + /* ARPHRD_SLIP, */ "slip" "\0" + /* ARPHRD_CSLIP, */ "cslip" "\0" + /* ARPHRD_SLIP6, */ "slip6" "\0" + /* ARPHRD_CSLIP6, */ "cslip6" "\0" + /* ARPHRD_PPP, */ "ppp" "\0" + /* ARPHRD_TUNNEL, */ "ipip" "\0" + /* ARPHRD_TUNNEL6, */ "tunnel6" "\0" + /* ARPHRD_SIT, */ "sit" "\0" + /* ARPHRD_IPGRE, */ "gre" "\0" +#ifdef ARPHRD_VOID + /* ARPHRD_VOID, */ "void" "\0" +#endif + +#if ENABLE_FEATURE_IP_RARE_PROTOCOLS + /* ARPHRD_EETHER, */ "eether" "\0" + /* ARPHRD_AX25, */ "ax25" "\0" + /* ARPHRD_PRONET, */ "pronet" "\0" + /* ARPHRD_CHAOS, */ "chaos" "\0" + /* ARPHRD_ARCNET, */ "arcnet" "\0" + /* ARPHRD_APPLETLK, */ "atalk" "\0" + /* ARPHRD_DLCI, */ "dlci" "\0" +#ifdef ARPHRD_ATM + /* ARPHRD_ATM, */ "atm" "\0" +#endif + /* ARPHRD_METRICOM, */ "metricom" "\0" + /* ARPHRD_RSRVD, */ "rsrvd" "\0" + /* ARPHRD_ADAPT, */ "adapt" "\0" + /* ARPHRD_ROSE, */ "rose" "\0" + /* ARPHRD_X25, */ "x25" "\0" +#ifdef ARPHRD_HWX25 + /* ARPHRD_HWX25, */ "hwx25" "\0" +#endif + /* ARPHRD_HDLC, */ "hdlc" "\0" + /* ARPHRD_LAPB, */ "lapb" "\0" +#ifdef ARPHRD_DDCMP + /* ARPHRD_DDCMP, */ "ddcmp" "\0" + /* ARPHRD_RAWHDLC, */ "rawhdlc" "\0" +#endif + /* ARPHRD_FRAD, */ "frad" "\0" + /* ARPHRD_SKIP, */ "skip" "\0" + /* ARPHRD_LOCALTLK, */ "ltalk" "\0" + /* ARPHRD_FDDI, */ "fddi" "\0" + /* ARPHRD_BIF, */ "bif" "\0" + /* ARPHRD_IPDDP, */ "ip/ddp" "\0" + /* ARPHRD_PIMREG, */ "pimreg" "\0" + /* ARPHRD_HIPPI, */ "hippi" "\0" + /* ARPHRD_ASH, */ "ash" "\0" + /* ARPHRD_ECONET, */ "econet" "\0" + /* ARPHRD_FCPP, */ "fcpp" "\0" + /* ARPHRD_FCAL, */ "fcal" "\0" + /* ARPHRD_FCPL, */ "fcpl" "\0" + /* ARPHRD_FCFABRIC, */ "fcfb0" "\0" + /* ARPHRD_FCFABRIC+1, */ "fcfb1" "\0" + /* ARPHRD_FCFABRIC+2, */ "fcfb2" "\0" + /* ARPHRD_FCFABRIC+3, */ "fcfb3" "\0" + /* ARPHRD_FCFABRIC+4, */ "fcfb4" "\0" + /* ARPHRD_FCFABRIC+5, */ "fcfb5" "\0" + /* ARPHRD_FCFABRIC+6, */ "fcfb6" "\0" + /* ARPHRD_FCFABRIC+7, */ "fcfb7" "\0" + /* ARPHRD_FCFABRIC+8, */ "fcfb8" "\0" + /* ARPHRD_FCFABRIC+9, */ "fcfb9" "\0" + /* ARPHRD_FCFABRIC+10, */ "fcfb10" "\0" + /* ARPHRD_FCFABRIC+11, */ "fcfb11" "\0" + /* ARPHRD_FCFABRIC+12, */ "fcfb12" "\0" +#endif /* FEATURE_IP_RARE_PROTOCOLS */ + ; + + /* Keep these arrays in sync! */ + + static const uint16_t arphrd_type[] = { + 0, /* "generic" "\0" */ + ARPHRD_LOOPBACK, /* "loopback" "\0" */ + ARPHRD_ETHER, /* "ether" "\0" */ +#ifdef ARPHRD_INFINIBAND + ARPHRD_INFINIBAND, /* "infiniband" "\0" */ +#endif +#ifdef ARPHRD_IEEE802_TR + ARPHRD_IEEE802, /* "ieee802" "\0" */ + ARPHRD_IEEE802_TR, /* "tr" "\0" */ +#else + ARPHRD_IEEE802, /* "tr" "\0" */ +#endif +#ifdef ARPHRD_IEEE80211 + ARPHRD_IEEE80211, /* "ieee802.11" "\0" */ +#endif +#ifdef ARPHRD_IEEE1394 + ARPHRD_IEEE1394, /* "ieee1394" "\0" */ +#endif + ARPHRD_IRDA, /* "irda" "\0" */ + ARPHRD_SLIP, /* "slip" "\0" */ + ARPHRD_CSLIP, /* "cslip" "\0" */ + ARPHRD_SLIP6, /* "slip6" "\0" */ + ARPHRD_CSLIP6, /* "cslip6" "\0" */ + ARPHRD_PPP, /* "ppp" "\0" */ + ARPHRD_TUNNEL, /* "ipip" "\0" */ + ARPHRD_TUNNEL6, /* "tunnel6" "\0" */ + ARPHRD_SIT, /* "sit" "\0" */ + ARPHRD_IPGRE, /* "gre" "\0" */ +#ifdef ARPHRD_VOID + ARPHRD_VOID, /* "void" "\0" */ +#endif + +#if ENABLE_FEATURE_IP_RARE_PROTOCOLS + ARPHRD_EETHER, /* "eether" "\0" */ + ARPHRD_AX25, /* "ax25" "\0" */ + ARPHRD_PRONET, /* "pronet" "\0" */ + ARPHRD_CHAOS, /* "chaos" "\0" */ + ARPHRD_ARCNET, /* "arcnet" "\0" */ + ARPHRD_APPLETLK, /* "atalk" "\0" */ + ARPHRD_DLCI, /* "dlci" "\0" */ +#ifdef ARPHRD_ATM + ARPHRD_ATM, /* "atm" "\0" */ +#endif + ARPHRD_METRICOM, /* "metricom" "\0" */ + ARPHRD_RSRVD, /* "rsrvd" "\0" */ + ARPHRD_ADAPT, /* "adapt" "\0" */ + ARPHRD_ROSE, /* "rose" "\0" */ + ARPHRD_X25, /* "x25" "\0" */ +#ifdef ARPHRD_HWX25 + ARPHRD_HWX25, /* "hwx25" "\0" */ +#endif + ARPHRD_HDLC, /* "hdlc" "\0" */ + ARPHRD_LAPB, /* "lapb" "\0" */ +#ifdef ARPHRD_DDCMP + ARPHRD_DDCMP, /* "ddcmp" "\0" */ + ARPHRD_RAWHDLC, /* "rawhdlc" "\0" */ +#endif + ARPHRD_FRAD, /* "frad" "\0" */ + ARPHRD_SKIP, /* "skip" "\0" */ + ARPHRD_LOCALTLK, /* "ltalk" "\0" */ + ARPHRD_FDDI, /* "fddi" "\0" */ + ARPHRD_BIF, /* "bif" "\0" */ + ARPHRD_IPDDP, /* "ip/ddp" "\0" */ + ARPHRD_PIMREG, /* "pimreg" "\0" */ + ARPHRD_HIPPI, /* "hippi" "\0" */ + ARPHRD_ASH, /* "ash" "\0" */ + ARPHRD_ECONET, /* "econet" "\0" */ + ARPHRD_FCPP, /* "fcpp" "\0" */ + ARPHRD_FCAL, /* "fcal" "\0" */ + ARPHRD_FCPL, /* "fcpl" "\0" */ + ARPHRD_FCFABRIC, /* "fcfb0" "\0" */ + ARPHRD_FCFABRIC+1, /* "fcfb1" "\0" */ + ARPHRD_FCFABRIC+2, /* "fcfb2" "\0" */ + ARPHRD_FCFABRIC+3, /* "fcfb3" "\0" */ + ARPHRD_FCFABRIC+4, /* "fcfb4" "\0" */ + ARPHRD_FCFABRIC+5, /* "fcfb5" "\0" */ + ARPHRD_FCFABRIC+6, /* "fcfb6" "\0" */ + ARPHRD_FCFABRIC+7, /* "fcfb7" "\0" */ + ARPHRD_FCFABRIC+8, /* "fcfb8" "\0" */ + ARPHRD_FCFABRIC+9, /* "fcfb9" "\0" */ + ARPHRD_FCFABRIC+10, /* "fcfb10" "\0" */ + ARPHRD_FCFABRIC+11, /* "fcfb11" "\0" */ + ARPHRD_FCFABRIC+12, /* "fcfb12" "\0" */ +#endif /* FEATURE_IP_RARE_PROTOCOLS */ + }; + + unsigned i; + const char *aname = arphrd_name; + for (i = 0; i < ARRAY_SIZE(arphrd_type); i++) { + if (arphrd_type[i] == type) + return aname; + aname += strlen(aname) + 1; + } + 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..e4d1061 --- /dev/null +++ b/networking/libiproute/rt_names.c @@ -0,0 +1,349 @@ +/* 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 "libbb.h" +#include "rt_names.h" + +/* so far all callers have size == 256 */ +#define rtnl_tab_initialize(file, tab, size) rtnl_tab_initialize(file, tab) +#define size 256 +static void rtnl_tab_initialize(const char *file, const char **tab, int size) +{ + char *token[2]; + parser_t *parser = config_open2(file, fopen_for_read); + while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) { + int id = bb_strtou(token[0], NULL, 0); + if (id < 0 || id > size) { + bb_error_msg("database %s is corrupted at line %d", + file, parser->lineno); + break; + } + tab[id] = xstrdup(token[1]); + } + config_close(parser); +} +#undef size + +static const char **rtnl_rtprot_tab; /* [256] */ + +static void rtnl_rtprot_initialize(void) +{ + static const char *const init_tab[] = { + "none", + "redirect", + "kernel", + "boot", + "static", + NULL, + NULL, + NULL, + "gated", + "ra", + "mrt", + "zebra", + "bird", + }; + if (rtnl_rtprot_tab) return; + rtnl_rtprot_tab = xzalloc(256 * sizeof(rtnl_rtprot_tab[0])); + memcpy(rtnl_rtprot_tab, init_tab, sizeof(init_tab)); + 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; + } + + 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; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + 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 = bb_strtoul(arg, NULL, 0); + if (errno || res > 255) + return -1; + *id = res; + return 0; +} + + +static const char **rtnl_rtscope_tab; /* [256] */ + +static void rtnl_rtscope_initialize(void) +{ + if (rtnl_rtscope_tab) return; + rtnl_rtscope_tab = xzalloc(256 * sizeof(rtnl_rtscope_tab[0])); + rtnl_rtscope_tab[0] = "global"; + 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; + } + + 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; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + 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 = bb_strtoul(arg, NULL, 0); + if (errno || res > 255) + return -1; + *id = res; + return 0; +} + + +static const char **rtnl_rtrealm_tab; /* [256] */ + +static void rtnl_rtrealm_initialize(void) +{ + if (rtnl_rtrealm_tab) return; + rtnl_rtrealm_tab = xzalloc(256 * sizeof(rtnl_rtrealm_tab[0])); + rtnl_rtrealm_tab[0] = "unknown"; + 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; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + 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 = bb_strtoul(arg, NULL, 0); + if (errno || 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; + } + + 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] */ + +static void rtnl_rtdsfield_initialize(void) +{ + if (rtnl_rtdsfield_tab) return; + rtnl_rtdsfield_tab = xzalloc(256 * sizeof(rtnl_rtdsfield_tab[0])); + rtnl_rtdsfield_tab[0] = "0"; + 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; + } + + 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; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + 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 = bb_strtoul(arg, NULL, 16); + if (errno || res > 255) + return -1; + *id = res; + return 0; +} + + +#if ENABLE_FEATURE_IP_RULE +static const char **rtnl_rttable_tab; /* [256] */ + +static void rtnl_rttable_initialize(void) +{ + if (rtnl_rtdsfield_tab) return; + rtnl_rttable_tab = xzalloc(256 * sizeof(rtnl_rttable_tab[0])); + rtnl_rttable_tab[0] = "unspec"; + 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; + } + + 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; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + 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 = bb_strtoul(arg, NULL, 0); + if (errno || 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..3d68b67 --- /dev/null +++ b/networking/libiproute/rt_names.h @@ -0,0 +1,35 @@ +/* vi: set sw=4 ts=4: */ +#ifndef RT_NAMES_H_ +#define RT_NAMES_H_ 1 + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +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); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c new file mode 100644 index 0000000..ca2f443 --- /dev/null +++ b/networking/libiproute/rtm_map.c @@ -0,0 +1,118 @@ +/* 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 "libbb.h" +#include "rt_names.h" +#include "utils.h" + +const 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) +{ + static const char keywords[] ALIGN1 = + "local\0""nat\0""broadcast\0""brd\0""anycast\0" + "multicast\0""prohibit\0""unreachable\0""blackhole\0" + "xresolve\0""unicast\0""throw\0"; + enum { + ARG_local = 1, ARG_nat, ARG_broadcast, ARG_brd, ARG_anycast, + ARG_multicast, ARG_prohibit, ARG_unreachable, ARG_blackhole, + ARG_xresolve, ARG_unicast, ARG_throw + }; + const smalluint key = index_in_substrings(keywords, arg) + 1; + char *end; + unsigned long res; + + if (key == ARG_local) + res = RTN_LOCAL; + else if (key == ARG_nat) + res = RTN_NAT; + else if (key == ARG_broadcast || key == ARG_brd) + res = RTN_BROADCAST; + else if (key == ARG_anycast) + res = RTN_ANYCAST; + else if (key == ARG_multicast) + res = RTN_MULTICAST; + else if (key == ARG_prohibit) + res = RTN_PROHIBIT; + else if (key == ARG_unreachable) + res = RTN_UNREACHABLE; + else if (key == ARG_blackhole) + res = RTN_BLACKHOLE; + else if (key == ARG_xresolve) + res = RTN_XRESOLVE; + else if (key == ARG_unicast) + res = RTN_UNICAST; + else if (key == ARG_throw) + 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(uint32_t *realms, char *arg) +{ + uint32_t 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..02fa77e --- /dev/null +++ b/networking/libiproute/rtm_map.h @@ -0,0 +1,18 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __RTM_MAP_H__ +#define __RTM_MAP_H__ 1 + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +const char *rtnl_rtntype_n2a(int id, char *buf, int len); +int rtnl_rtntype_a2n(int *id, char *arg); + +int get_rt_realms(uint32_t *realms, char *arg); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif /* __RTM_MAP_H__ */ diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c new file mode 100644 index 0000000..cd101f1 --- /dev/null +++ b/networking/libiproute/utils.c @@ -0,0 +1,324 @@ +/* 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 "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; +} +//XXX: FIXME: use some libbb function instead +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(uint32_t * 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(uint16_t * 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(uint8_t * 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(int16_t * 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(int8_t * 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) +{ + 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; + if (inet_pton(AF_INET, name, addr->data) <= 0) + return -1; + addr->bytelen = 4; + addr->bitlen = -1; + return 0; +} + +int get_prefix_1(inet_prefix * dst, char *arg, int family) +{ + int err; + unsigned 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) { + dst->bitlen = (dst->family == AF_INET6) ? 128 : 32; + if (slash) { + inet_prefix netmask_pfx; + + netmask_pfx.family = AF_UNSPEC; + if ((get_unsigned(&plen, slash + 1, 0) || plen > dst->bitlen) + && (get_addr_1(&netmask_pfx, slash + 1, family))) + err = -1; + else if (netmask_pfx.family == AF_INET) { + /* fill in prefix length of dotted quad */ + uint32_t mask = ntohl(netmask_pfx.data[0]); + uint32_t host = ~mask; + + /* a valid netmask must be 2^n - 1 */ + if (!(host & (host + 1))) { + for (plen = 0; mask; mask <<= 1) + ++plen; + if (plen >= 0 && plen <= dst->bitlen) { + dst->bitlen = plen; + /* dst->flags |= PREFIXLEN_SPECIFIED; */ + } else + err = -1; + } else + err = -1; + } else { + /* plain prefix */ + dst->bitlen = plen; + } + } + } + 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 %s, but it is not allowed in this context", arg, "address"); + } + if (get_addr_1(dst, arg, family)) { + bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "address", 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 %s, but it is not allowed in this context", arg, "prefix"); + } + if (get_prefix_1(dst, arg, family)) { + bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "prefix", arg); + } + return 0; +} + +uint32_t get_addr32(char *name) +{ + inet_prefix addr; + + if (get_addr_1(&addr, name, AF_INET)) { + bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "IP", "address", name); + } + return addr.data[0]; +} + +void incomplete_command(void) +{ + bb_error_msg_and_die("command line is not complete, try option \"help\""); +} + +void invarg(const char *arg, const char *opt) +{ + bb_error_msg_and_die(bb_msg_invalid_arg, arg, opt); +} + +void duparg(const char *key, const char *arg) +{ + bb_error_msg_and_die("duplicate \"%s\": \"%s\" is the second value", key, arg); +} + +void duparg2(const char *key, const char *arg) +{ + bb_error_msg_and_die("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg); +} + +int inet_addr_match(inet_prefix * a, inet_prefix * b, int bits) +{ + uint32_t *a1 = a->data; + uint32_t *a2 = b->data; + int words = bits >> 0x05; + + bits &= 0x1f; + + if (words) + if (memcmp(a1, a2, words << 2)) + return -1; + + if (bits) { + uint32_t w1, w2; + uint32_t mask; + + w1 = a1[words]; + w2 = a2[words]; + + mask = htonl((0xffffffff) << (0x20 - bits)); + + if ((w1 ^ w2) & mask) + return 1; + } + + return 0; +} + +const char *rt_addr_n2a(int af, int UNUSED_PARAM 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); + if (h_ent != NULL) { + safe_strncpy(buf, h_ent->h_name, buflen); + 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..1af39ff --- /dev/null +++ b/networking/libiproute/utils.h @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UTILS_H__ +#define __UTILS_H__ 1 + +#include "libnetlink.h" +#include "ll_map.h" +#include "rtm_map.h" + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +extern family_t preferred_family; +extern smallint show_stats; /* UNUSED */ +extern smallint show_details; /* UNUSED */ +extern smallint show_raw; /* UNUSED */ +extern smallint resolve_hosts; /* UNUSED */ +extern smallint 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) NORETURN; + +#define NEXT_ARG() do { if (!*++argv) incomplete_command(); } while (0) + +typedef struct { + uint8_t family; + uint8_t bytelen; + int16_t bitlen; + uint32_t data[4]; +} inet_prefix; + +#define PREFIXLEN_SPECIFIED 1 + +#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 uint32_t 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(uint32_t *val, char *arg, int base); +extern int get_u16(uint16_t *val, char *arg, int base); +extern int get_s16(int16_t *val, char *arg, int base); +extern int get_u8(uint8_t *val, char *arg, int base); +extern int get_s8(int8_t *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 char *) NORETURN; +void duparg(const char *, const char *) NORETURN; +void duparg2(const char *, const char *) NORETURN; +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); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif /* __UTILS_H__ */ diff --git a/networking/nameif.c b/networking/nameif.c new file mode 100644 index 0000000..75829fa --- /dev/null +++ b/networking/nameif.c @@ -0,0 +1,232 @@ +/* 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 + * Extended matching support 2008 by Nico Erfurth <masta@perlgolf.de> + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include <syslog.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <linux/sockios.h> + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +/* Taken 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 ethtable_s { + struct ethtable_s *next; + struct ethtable_s *prev; + char *ifname; + struct ether_addr *mac; +#if ENABLE_FEATURE_NAMEIF_EXTENDED + char *bus_info; + char *driver; +#endif +} ethtable_t; + +#if ENABLE_FEATURE_NAMEIF_EXTENDED +/* Cut'n'paste from ethtool.h */ +#define ETHTOOL_BUSINFO_LEN 32 +/* these strings are set to whatever the driver author decides... */ +struct ethtool_drvinfo { + uint32_t cmd; + char driver[32]; /* driver short name, "tulip", "eepro100" */ + char version[32]; /* driver version string */ + char fw_version[32]; /* firmware version string, if applicable */ + char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */ + /* For PCI devices, use pci_dev->slot_name. */ + char reserved1[32]; + char reserved2[16]; + uint32_t n_stats; /* number of u64's from ETHTOOL_GSTATS */ + uint32_t testinfo_len; + uint32_t eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */ + uint32_t regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */ +}; +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#endif + + +static void nameif_parse_selector(ethtable_t *ch, char *selector) +{ + struct ether_addr *lmac; +#if ENABLE_FEATURE_NAMEIF_EXTENDED + int found_selector = 0; + + while (*selector) { + char *next; +#endif + selector = skip_whitespace(selector); +#if ENABLE_FEATURE_NAMEIF_EXTENDED + if (*selector == '\0') + break; + /* Search for the end .... */ + next = skip_non_whitespace(selector); + if (*next) + *next++ = '\0'; + /* Check for selectors, mac= is assumed */ + if (strncmp(selector, "bus=", 4) == 0) { + ch->bus_info = xstrdup(selector + 4); + found_selector++; + } else if (strncmp(selector, "driver=", 7) == 0) { + ch->driver = xstrdup(selector + 7); + found_selector++; + } else { +#endif + lmac = xmalloc(ETH_ALEN); + ch->mac = ether_aton_r(selector + (strncmp(selector, "mac=", 4) ? 0 : 4), lmac); + if (ch->mac == NULL) + bb_error_msg_and_die("cannot parse %s", selector); +#if ENABLE_FEATURE_NAMEIF_EXTENDED + found_selector++; + }; + selector = next; + } + if (found_selector == 0) + bb_error_msg_and_die("no selectors found for %s", ch->ifname); +#endif +} + +static void prepend_new_eth_table(ethtable_t **clist, char *ifname, char *selector) +{ + ethtable_t *ch; + if (strlen(ifname) >= IFNAMSIZ) + bb_error_msg_and_die("interface name '%s' too long", ifname); + ch = xzalloc(sizeof(*ch)); + ch->ifname = xstrdup(ifname); + nameif_parse_selector(ch, selector); + ch->next = *clist; + if (*clist) + (*clist)->prev = ch; + *clist = ch; +} + +#if ENABLE_FEATURE_CLEAN_UP +static void delete_eth_table(ethtable_t *ch) +{ + free(ch->ifname); +#if ENABLE_FEATURE_NAMEIF_EXTENDED + free(ch->bus_info); + free(ch->driver); +#endif + free(ch->mac); + free(ch); +}; +#else +void delete_eth_table(ethtable_t *ch); +#endif + +int nameif_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nameif_main(int argc, char **argv) +{ + ethtable_t *clist = NULL; + const char *fname = "/etc/mactab"; + int ctl_sk; + ethtable_t *ch; + parser_t *parser; + char *token[2]; + + if (1 & getopt32(argv, "sc:", &fname)) { + openlog(applet_name, 0, LOG_LOCAL0); + logmode = LOGMODE_SYSLOG; + } + argc -= optind; + argv += optind; + + if (argc & 1) + bb_show_usage(); + + if (argc) { + while (*argv) { + char *ifname = xstrdup(*argv++); + prepend_new_eth_table(&clist, ifname, *argv++); + } + } else { + parser = config_open(fname); + while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) + prepend_new_eth_table(&clist, token[0], token[1]); + config_close(parser); + } + + ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0); + parser = config_open2("/proc/net/dev", xfopen_for_read); + + while (clist && config_read(parser, token, 2, 2, "\0: \t", PARSE_NORMAL)) { + struct ifreq ifr; +#if ENABLE_FEATURE_NAMEIF_EXTENDED + struct ethtool_drvinfo drvinfo; +#endif + if (parser->lineno < 2) + continue; /* Skip the first two lines */ + + /* Find the current interface name and copy it to ifr.ifr_name */ + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, token[0], sizeof(ifr.ifr_name)); + +#if ENABLE_FEATURE_NAMEIF_EXTENDED + /* Check for driver etc. */ + memset(&drvinfo, 0, sizeof(struct ethtool_drvinfo)); + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t) &drvinfo; + /* Get driver and businfo first, so we have it in drvinfo */ + ioctl(ctl_sk, SIOCETHTOOL, &ifr); +#endif + ioctl(ctl_sk, SIOCGIFHWADDR, &ifr); + + /* Search the list for a matching device */ + for (ch = clist; ch; ch = ch->next) { +#if ENABLE_FEATURE_NAMEIF_EXTENDED + if (ch->bus_info && strcmp(ch->bus_info, drvinfo.bus_info) != 0) + continue; + if (ch->driver && strcmp(ch->driver, drvinfo.driver) != 0) + continue; +#endif + if (ch->mac && memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN) != 0) + continue; + /* if we came here, all selectors have matched */ + break; + } + /* Nothing found for current interface */ + if (!ch) + continue; + + if (strcmp(ifr.ifr_name, ch->ifname) != 0) { + strcpy(ifr.ifr_newname, ch->ifname); + ioctl_or_perror_and_die(ctl_sk, SIOCSIFNAME, &ifr, + "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) + delete_eth_table(ch); + } + if (ENABLE_FEATURE_CLEAN_UP) { + for (ch = clist; ch; ch = ch->next) + delete_eth_table(ch); + config_close(parser); + }; + + return 0; +} diff --git a/networking/nc.c b/networking/nc.c new file mode 100644 index 0000000..fe845f5 --- /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 "libbb.h" + +#if ENABLE_DESKTOP +#include "nc_bloaty.c" +#else + +/* Lots of small differences in features + * when compared to "standard" nc + */ + +static void timeout(int signum UNUSED_PARAM) +{ + bb_error_msg_and_die("timed out"); +} + +int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nc_main(int argc, char **argv) +{ + /* sfd sits _here_ only because of "repeat" option (-l -l). */ + int sfd = sfd; /* for gcc */ + int cfd = 0; + unsigned lport = 0; + SKIP_NC_SERVER(const) unsigned do_listen = 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;) + len_and_sockaddr *lsa; + fd_set readfds, testfds; + int opt; /* must be signed (getopt returns -1) */ + + 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 + if (do_listen || cfd) { + if (argc) bb_show_usage(); + } else { + if (!argc || argc > 2) bb_show_usage(); + } + } else { + if (argc != 3) bb_show_usage(); + argc--; + argv++; + } + + if (wsecs) { + signal(SIGALRM, timeout); + alarm(wsecs); + } + + if (!cfd) { + if (do_listen) { + /* create_and_bind_stream_or_die(NULL, lport) + * would've work wonderfully, but we need + * to know lsa */ + sfd = xsocket_stream(&lsa); + if (lport) + set_nport(lsa, htons(lport)); + setsockopt_reuseaddr(sfd); + xbind(sfd, &lsa->u.sa, lsa->len); + xlisten(sfd, do_listen); /* can be > 1 */ + /* If we didn't specify a port number, + * query and print it after listen() */ + if (!lport) { + socklen_t addrlen = lsa->len; + getsockname(sfd, &lsa->u.sa, &addrlen); + lport = get_nport(&lsa->u.sa); + fdprintf(2, "%d\n", ntohs(lport)); + } + close_on_exec_on(sfd); + accept_again: + cfd = accept(sfd, NULL, 0); + if (cfd < 0) + bb_perror_msg_and_die("accept"); + if (!execparam) + close(sfd); + } else { + cfd = create_and_connect_stream_or_die(argv[0], + argv[1] ? bb_lookup_port(argv[1], "tcp", 0) : 0); + } + } + + if (wsecs) { + alarm(0); + /* Non-ignored siganls revert to SIG_DFL on exec anyway */ + /*signal(SIGALRM, SIG_DFL);*/ + } + + /* -e given? */ + if (execparam) { + signal(SIGCHLD, SIG_IGN); + // With more than one -l, repeatedly act as server. + if (do_listen > 1 && vfork()) { + /* parent */ + // 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 accept_again; + } + /* child (or main thread if no multiple -l) */ + xmove_fd(cfd, 0); + xdup2(0, 1); + xdup2(0, 2); + USE_NC_EXTRA(BB_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"); + +#define iobuf bb_common_bufsiz1 + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &testfds)) { + nread = safe_read(fd, iobuf, sizeof(iobuf)); + if (fd == cfd) { + if (nread < 1) + exit(EXIT_SUCCESS); + 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, iobuf, nread); + if (delay > 0) sleep(delay); + } + } + } +} +#endif diff --git a/networking/nc_bloaty.c b/networking/nc_bloaty.c new file mode 100644 index 0000000..41db945 --- /dev/null +++ b/networking/nc_bloaty.c @@ -0,0 +1,832 @@ +/* Based on netcat 1.10 RELEASE 960320 written by hobbit@avian.org. + * Released into public domain by the author. + * + * Copyright (C) 2007 Denys Vlasenko. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* Author's comments from nc 1.10: + * ===================== + * Netcat is entirely my own creation, although plenty of other code was used as + * examples. It is freely given away to the Internet community in the hope that + * it will be useful, with no restrictions except giving credit where it is due. + * No GPLs, Berkeley copyrights or any of that nonsense. The author assumes NO + * responsibility for how anyone uses it. If netcat makes you rich somehow and + * you're feeling generous, mail me a check. If you are affiliated in any way + * with Microsoft Network, get a life. Always ski in control. Comments, + * questions, and patches to hobbit@avian.org. + * ... + * Netcat and the associated package is a product of Avian Research, and is freely + * available in full source form with no restrictions save an obligation to give + * credit where due. + * ... + * A damn useful little "backend" utility begun 950915 or thereabouts, + * as *Hobbit*'s first real stab at some sockets programming. Something that + * should have and indeed may have existed ten years ago, but never became a + * standard Unix utility. IMHO, "nc" could take its place right next to cat, + * cp, rm, mv, dd, ls, and all those other cryptic and Unix-like things. + * ===================== + * + * Much of author's comments are still retained in the code. + * + * Functionality removed (rationale): + * - miltiple-port ranges, randomized port scanning (use nmap) + * - telnet support (use telnet) + * - source routing + * - multiple DNS checks + * Functionalty which is different from nc 1.10: + * - Prog in '-e prog' can have prog's parameters and options. + * Because of this -e option must be last. + * - nc doesn't redirect stderr to the network socket for the -e prog. + * - numeric addresses are printed in (), not [] (IPv6 looks better), + * port numbers are inside (): (1.2.3.4:5678) + * - network read errors are reported on verbose levels > 1 + * (nc 1.10 treats them as EOF) + * - TCP connects from wrong ip/ports (if peer ip:port is specified + * on the command line, but accept() says that it came from different addr) + * are closed, but nc doesn't exit - continues to listen/accept. + */ + +/* done in nc.c: #include "libbb.h" */ + +enum { + SLEAZE_PORT = 31337, /* for UDP-scan RTT trick, change if ya want */ + BIGSIZ = 8192, /* big buffers */ + + netfd = 3, + ofd = 4, +}; + +struct globals { + /* global cmd flags: */ + unsigned o_verbose; + unsigned o_wait; +#if ENABLE_NC_EXTRA + unsigned o_interval; +#endif + + /*int netfd;*/ + /*int ofd;*/ /* hexdump output fd */ +#if ENABLE_LFS +#define SENT_N_RECV_M "sent %llu, rcvd %llu\n" + unsigned long long wrote_out; /* total stdout bytes */ + unsigned long long wrote_net; /* total net bytes */ +#else +#define SENT_N_RECV_M "sent %u, rcvd %u\n" + unsigned wrote_out; /* total stdout bytes */ + unsigned wrote_net; /* total net bytes */ +#endif + /* ouraddr is never NULL and goes through three states as we progress: + 1 - local address before bind (IP/port possibly zero) + 2 - local address after bind (port is nonzero) + 3 - local address after connect??/recv/accept (IP and port are nonzero) */ + struct len_and_sockaddr *ouraddr; + /* themaddr is NULL if no peer hostname[:port] specified on command line */ + struct len_and_sockaddr *themaddr; + /* remend is set after connect/recv/accept to the actual ip:port of peer */ + struct len_and_sockaddr remend; + + jmp_buf jbuf; /* timer crud */ + + /* will malloc up the following globals: */ + fd_set ding1; /* for select loop */ + fd_set ding2; + char bigbuf_in[BIGSIZ]; /* data buffers */ + char bigbuf_net[BIGSIZ]; +}; + +#define G (*ptr_to_globals) +#define wrote_out (G.wrote_out ) +#define wrote_net (G.wrote_net ) +#define ouraddr (G.ouraddr ) +#define themaddr (G.themaddr ) +#define remend (G.remend ) +#define jbuf (G.jbuf ) +#define ding1 (G.ding1 ) +#define ding2 (G.ding2 ) +#define bigbuf_in (G.bigbuf_in ) +#define bigbuf_net (G.bigbuf_net) +#define o_verbose (G.o_verbose ) +#define o_wait (G.o_wait ) +#if ENABLE_NC_EXTRA +#define o_interval (G.o_interval) +#else +#define o_interval 0 +#endif +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} while (0) + + +/* Must match getopt32 call! */ +enum { + OPT_h = (1 << 0), + OPT_n = (1 << 1), + OPT_p = (1 << 2), + OPT_s = (1 << 3), + OPT_u = (1 << 4), + OPT_v = (1 << 5), + OPT_w = (1 << 6), + OPT_l = (1 << 7) * ENABLE_NC_SERVER, + OPT_i = (1 << (7+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA, + OPT_o = (1 << (8+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA, + OPT_z = (1 << (9+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA, +}; + +#define o_nflag (option_mask32 & OPT_n) +#define o_udpmode (option_mask32 & OPT_u) +#if ENABLE_NC_SERVER +#define o_listen (option_mask32 & OPT_l) +#else +#define o_listen 0 +#endif +#if ENABLE_NC_EXTRA +#define o_ofile (option_mask32 & OPT_o) +#define o_zero (option_mask32 & OPT_z) +#else +#define o_ofile 0 +#define o_zero 0 +#endif + +/* Debug: squirt whatever message and sleep a bit so we can see it go by. */ +/* Beware: writes to stdOUT... */ +#if 0 +#define Debug(...) do { printf(__VA_ARGS__); printf("\n"); fflush(stdout); sleep(1); } while (0) +#else +#define Debug(...) do { } while (0) +#endif + +#define holler_error(...) do { if (o_verbose) bb_error_msg(__VA_ARGS__); } while (0) +#define holler_perror(...) do { if (o_verbose) bb_perror_msg(__VA_ARGS__); } while (0) + +/* catch: no-brainer interrupt handler */ +static void catch(int sig) +{ + if (o_verbose > 1) /* normally we don't care */ + fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out); + fprintf(stderr, "punt!\n"); + kill_myself_with_sig(sig); +} + +/* unarm */ +static void unarm(void) +{ + signal(SIGALRM, SIG_IGN); + alarm(0); +} + +/* timeout and other signal handling cruft */ +static void tmtravel(int sig UNUSED_PARAM) +{ + unarm(); + longjmp(jbuf, 1); +} + +/* arm: set the timer. */ +static void arm(unsigned secs) +{ + signal(SIGALRM, tmtravel); + alarm(secs); +} + +/* findline: + find the next newline in a buffer; return inclusive size of that "line", + or the entire buffer size, so the caller knows how much to then write(). + Not distinguishing \n vs \r\n for the nonce; it just works as is... */ +static unsigned findline(char *buf, unsigned siz) +{ + char * p; + int x; + if (!buf) /* various sanity checks... */ + return 0; + if (siz > BIGSIZ) + return 0; + x = siz; + for (p = buf; x > 0; x--) { + if (*p == '\n') { + x = (int) (p - buf); + x++; /* 'sokay if it points just past the end! */ +Debug("findline returning %d", x); + return x; + } + p++; + } /* for */ +Debug("findline returning whole thing: %d", siz); + return siz; +} /* findline */ + +/* doexec: + fiddle all the file descriptors around, and hand off to another prog. Sort + of like a one-off "poor man's inetd". This is the only section of code + that would be security-critical, which is why it's ifdefed out by default. + Use at your own hairy risk; if you leave shells lying around behind open + listening ports you deserve to lose!! */ +static int doexec(char **proggie) NORETURN; +static int doexec(char **proggie) +{ + xmove_fd(netfd, 0); + dup2(0, 1); + /* dup2(0, 2); - do we *really* want this? NO! + * exec'ed prog can do it yourself, if needed */ + execvp(proggie[0], proggie); + bb_perror_msg_and_die("exec"); +} + +/* connect_w_timeout: + return an fd for one of + an open outbound TCP connection, a UDP stub-socket thingie, or + an unconnected TCP or UDP socket to listen on. + Examines various global o_blah flags to figure out what to do. + lad can be NULL, then socket is not bound to any local ip[:port] */ +static int connect_w_timeout(int fd) +{ + int rr; + + /* wrap connect inside a timer, and hit it */ + arm(o_wait); + if (setjmp(jbuf) == 0) { + rr = connect(fd, &themaddr->u.sa, themaddr->len); + unarm(); + } else { /* setjmp: connect failed... */ + rr = -1; + errno = ETIMEDOUT; /* fake it */ + } + return rr; +} + +/* dolisten: + listens for + incoming and returns an open connection *from* someplace. If we were + given host/port args, any connections from elsewhere are rejected. This + in conjunction with local-address binding should limit things nicely... */ +static void dolisten(void) +{ + int rr; + + if (!o_udpmode) + xlisten(netfd, 1); /* TCP: gotta listen() before we can get */ + + /* Various things that follow temporarily trash bigbuf_net, which might contain + a copy of any recvfrom()ed packet, but we'll read() another copy later. */ + + /* I can't believe I have to do all this to get my own goddamn bound address + and port number. It should just get filled in during bind() or something. + All this is only useful if we didn't say -p for listening, since if we + said -p we *know* what port we're listening on. At any rate we won't bother + with it all unless we wanted to see it, although listening quietly on a + random unknown port is probably not very useful without "netstat". */ + if (o_verbose) { + char *addr; + rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len); + if (rr < 0) + bb_perror_msg_and_die("getsockname after bind"); + addr = xmalloc_sockaddr2dotted(&ouraddr->u.sa); + fprintf(stderr, "listening on %s ...\n", addr); + free(addr); + } + + if (o_udpmode) { + /* UDP is a speeeeecial case -- we have to do I/O *and* get the calling + party's particulars all at once, listen() and accept() don't apply. + At least in the BSD universe, however, recvfrom/PEEK is enough to tell + us something came in, and we can set things up so straight read/write + actually does work after all. Yow. YMMV on strange platforms! */ + + /* I'm not completely clear on how this works -- BSD seems to make UDP + just magically work in a connect()ed context, but we'll undoubtedly run + into systems this deal doesn't work on. For now, we apparently have to + issue a connect() on our just-tickled socket so we can write() back. + Again, why the fuck doesn't it just get filled in and taken care of?! + This hack is anything but optimal. Basically, if you want your listener + to also be able to send data back, you need this connect() line, which + also has the side effect that now anything from a different source or even a + different port on the other end won't show up and will cause ICMP errors. + I guess that's what they meant by "connect". + Let's try to remember what the "U" is *really* for, eh? */ + + /* If peer address is specified, connect to it */ + remend.len = LSA_SIZEOF_SA; + if (themaddr) { + remend = *themaddr; + xconnect(netfd, &themaddr->u.sa, themaddr->len); + } + /* peek first packet and remember peer addr */ + arm(o_wait); /* might as well timeout this, too */ + if (setjmp(jbuf) == 0) { /* do timeout for initial connect */ + /* (*ouraddr) is prefilled with "default" address */ + /* and here we block... */ + rr = recv_from_to(netfd, NULL, 0, MSG_PEEK, /*was bigbuf_net, BIGSIZ*/ + &remend.u.sa, &ouraddr->u.sa, ouraddr->len); + if (rr < 0) + bb_perror_msg_and_die("recvfrom"); + unarm(); + } else + bb_error_msg_and_die("timeout"); +/* Now we learned *to which IP* peer has connected, and we want to anchor +our socket on it, so that our outbound packets will have correct local IP. +Unfortunately, bind() on already bound socket will fail now (EINVAL): + xbind(netfd, &ouraddr->u.sa, ouraddr->len); +Need to read the packet, save data, close this socket and +create new one, and bind() it. TODO */ + if (!themaddr) + xconnect(netfd, &remend.u.sa, ouraddr->len); + } else { + /* TCP */ + arm(o_wait); /* wrap this in a timer, too; 0 = forever */ + if (setjmp(jbuf) == 0) { + again: + remend.len = LSA_SIZEOF_SA; + rr = accept(netfd, &remend.u.sa, &remend.len); + if (rr < 0) + bb_perror_msg_and_die("accept"); + if (themaddr && memcmp(&remend.u.sa, &themaddr->u.sa, remend.len) != 0) { + /* nc 1.10 bails out instead, and its error message + * is not suppressed by o_verbose */ + if (o_verbose) { + char *remaddr = xmalloc_sockaddr2dotted(&remend.u.sa); + bb_error_msg("connect from wrong ip/port %s ignored", remaddr); + free(remaddr); + } + close(rr); + goto again; + } + unarm(); + } else + bb_error_msg_and_die("timeout"); + xmove_fd(rr, netfd); /* dump the old socket, here's our new one */ + /* find out what address the connection was *to* on our end, in case we're + doing a listen-on-any on a multihomed machine. This allows one to + offer different services via different alias addresses, such as the + "virtual web site" hack. */ + rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len); + if (rr < 0) + bb_perror_msg_and_die("getsockname after accept"); + } + + if (o_verbose) { + char *lcladdr, *remaddr, *remhostname; + +#if ENABLE_NC_EXTRA && defined(IP_OPTIONS) + /* If we can, look for any IP options. Useful for testing the receiving end of + such things, and is a good exercise in dealing with it. We do this before + the connect message, to ensure that the connect msg is uniformly the LAST + thing to emerge after all the intervening crud. Doesn't work for UDP on + any machines I've tested, but feel free to surprise me. */ + char optbuf[40]; + socklen_t x = sizeof(optbuf); + + rr = getsockopt(netfd, IPPROTO_IP, IP_OPTIONS, optbuf, &x); + if (rr < 0) + bb_perror_msg("getsockopt failed"); + else if (x) { /* we've got options, lessee em... */ + bin2hex(bigbuf_net, optbuf, x); + bigbuf_net[2*x] = '\0'; + fprintf(stderr, "IP options: %s\n", bigbuf_net); + } +#endif + + /* now check out who it is. We don't care about mismatched DNS names here, + but any ADDR and PORT we specified had better fucking well match the caller. + Converting from addr to inet_ntoa and back again is a bit of a kludge, but + gethostpoop wants a string and there's much gnarlier code out there already, + so I don't feel bad. + The *real* question is why BFD sockets wasn't designed to allow listens for + connections *from* specific hosts/ports, instead of requiring the caller to + accept the connection and then reject undesireable ones by closing. + In other words, we need a TCP MSG_PEEK. */ + /* bbox: removed most of it */ + lcladdr = xmalloc_sockaddr2dotted(&ouraddr->u.sa); + remaddr = xmalloc_sockaddr2dotted(&remend.u.sa); + remhostname = o_nflag ? remaddr : xmalloc_sockaddr2host(&remend.u.sa); + fprintf(stderr, "connect to %s from %s (%s)\n", + lcladdr, remhostname, remaddr); + free(lcladdr); + free(remaddr); + if (!o_nflag) + free(remhostname); + } +} + +/* udptest: + fire a couple of packets at a UDP target port, just to see if it's really + there. On BSD kernels, ICMP host/port-unreachable errors get delivered to + our socket as ECONNREFUSED write errors. On SV kernels, we lose; we'll have + to collect and analyze raw ICMP ourselves a la satan's probe_udp_ports + backend. Guess where one could swipe the appropriate code from... + + Use the time delay between writes if given, otherwise use the "tcp ping" + trick for getting the RTT. [I got that idea from pluvius, and warped it.] + Return either the original fd, or clean up and return -1. */ +#if ENABLE_NC_EXTRA +static int udptest(void) +{ + int rr; + + rr = write(netfd, bigbuf_in, 1); + if (rr != 1) + bb_perror_msg("udptest first write"); + + if (o_wait) + sleep(o_wait); // can be interrupted! while (t) nanosleep(&t)? + else { + /* use the tcp-ping trick: try connecting to a normally refused port, which + causes us to block for the time that SYN gets there and RST gets back. + Not completely reliable, but it *does* mostly work. */ + /* Set a temporary connect timeout, so packet filtration doesnt cause + us to hang forever, and hit it */ + o_wait = 5; /* enough that we'll notice?? */ + rr = xsocket(ouraddr->u.sa.sa_family, SOCK_STREAM, 0); + set_nport(themaddr, htons(SLEAZE_PORT)); + connect_w_timeout(rr); + /* don't need to restore themaddr's port, it's not used anymore */ + close(rr); + o_wait = 0; /* restore */ + } + + rr = write(netfd, bigbuf_in, 1); + return (rr != 1); /* if rr == 1, return 0 (success) */ +} +#else +int udptest(void); +#endif + +/* oprint: + Hexdump bytes shoveled either way to a running logfile, in the format: + D offset - - - - --- 16 bytes --- - - - - # .... ascii ..... + where "which" sets the direction indicator, D: + 0 -- sent to network, or ">" + 1 -- rcvd and printed to stdout, or "<" + and "buf" and "n" are data-block and length. If the current block generates + a partial line, so be it; we *want* that lockstep indication of who sent + what when. Adapted from dgaudet's original example -- but must be ripping + *fast*, since we don't want to be too disk-bound... */ +#if ENABLE_NC_EXTRA +static void oprint(int direction, unsigned char *p, unsigned bc) +{ + unsigned obc; /* current "global" offset */ + unsigned x; + unsigned char *op; /* out hexdump ptr */ + unsigned char *ap; /* out asc-dump ptr */ + unsigned char stage[100]; + + if (bc == 0) + return; + + obc = wrote_net; /* use the globals! */ + if (direction == '<') + obc = wrote_out; + stage[0] = direction; + stage[59] = '#'; /* preload separator */ + stage[60] = ' '; + + do { /* for chunk-o-data ... */ + x = 16; + if (bc < 16) { + /* memset(&stage[bc*3 + 11], ' ', 16*3 - bc*3); */ + memset(&stage[11], ' ', 16*3); + x = bc; + } + sprintf((char *)&stage[1], " %8.8x ", obc); /* xxx: still slow? */ + bc -= x; /* fix current count */ + obc += x; /* fix current offset */ + op = &stage[11]; /* where hex starts */ + ap = &stage[61]; /* where ascii starts */ + + do { /* for line of dump, however long ... */ + *op++ = 0x20 | bb_hexdigits_upcase[*p >> 4]; + *op++ = 0x20 | bb_hexdigits_upcase[*p & 0x0f]; + *op++ = ' '; + if ((*p > 31) && (*p < 127)) + *ap = *p; /* printing */ + else + *ap = '.'; /* nonprinting, loose def */ + ap++; + p++; + } while (--x); + *ap++ = '\n'; /* finish the line */ + xwrite(ofd, stage, ap - stage); + } while (bc); +} +#else +void oprint(int direction, unsigned char *p, unsigned bc); +#endif + +/* readwrite: + handle stdin/stdout/network I/O. Bwahaha!! -- the select loop from hell. + In this instance, return what might become our exit status. */ +static int readwrite(void) +{ + int rr; + char *zp = zp; /* gcc */ /* stdin buf ptr */ + char *np = np; /* net-in buf ptr */ + unsigned rzleft; + unsigned rnleft; + unsigned netretry; /* net-read retry counter */ + unsigned wretry; /* net-write sanity counter */ + unsigned wfirst; /* one-shot flag to skip first net read */ + + /* if you don't have all this FD_* macro hair in sys/types.h, you'll have to + either find it or do your own bit-bashing: *ding1 |= (1 << fd), etc... */ + FD_SET(netfd, &ding1); /* global: the net is open */ + netretry = 2; + wfirst = 0; + rzleft = rnleft = 0; + if (o_interval) + sleep(o_interval); /* pause *before* sending stuff, too */ + + errno = 0; /* clear from sleep, close, whatever */ + /* and now the big ol' select shoveling loop ... */ + while (FD_ISSET(netfd, &ding1)) { /* i.e. till the *net* closes! */ + wretry = 8200; /* more than we'll ever hafta write */ + if (wfirst) { /* any saved stdin buffer? */ + wfirst = 0; /* clear flag for the duration */ + goto shovel; /* and go handle it first */ + } + ding2 = ding1; /* FD_COPY ain't portable... */ + /* some systems, notably linux, crap into their select timers on return, so + we create a expendable copy and give *that* to select. */ + if (o_wait) { + struct timeval tmp_timer; + tmp_timer.tv_sec = o_wait; + tmp_timer.tv_usec = 0; + /* highest possible fd is netfd (3) */ + rr = select(netfd+1, &ding2, NULL, NULL, &tmp_timer); + } else + rr = select(netfd+1, &ding2, NULL, NULL, NULL); + if (rr < 0 && errno != EINTR) { /* might have gotten ^Zed, etc */ + holler_perror("select"); + close(netfd); + return 1; + } + /* if we have a timeout AND stdin is closed AND we haven't heard anything + from the net during that time, assume it's dead and close it too. */ + if (rr == 0) { + if (!FD_ISSET(STDIN_FILENO, &ding1)) + netretry--; /* we actually try a coupla times. */ + if (!netretry) { + if (o_verbose > 1) /* normally we don't care */ + fprintf(stderr, "net timeout\n"); + close(netfd); + return 0; /* not an error! */ + } + } /* select timeout */ + /* xxx: should we check the exception fds too? The read fds seem to give + us the right info, and none of the examples I found bothered. */ + + /* Ding!! Something arrived, go check all the incoming hoppers, net first */ + if (FD_ISSET(netfd, &ding2)) { /* net: ding! */ + rr = read(netfd, bigbuf_net, BIGSIZ); + if (rr <= 0) { + if (rr < 0 && o_verbose > 1) { + /* nc 1.10 doesn't do this */ + bb_perror_msg("net read"); + } + FD_CLR(netfd, &ding1); /* net closed, we'll finish up... */ + rzleft = 0; /* can't write anymore: broken pipe */ + } else { + rnleft = rr; + np = bigbuf_net; + } +Debug("got %d from the net, errno %d", rr, errno); + } /* net:ding */ + + /* if we're in "slowly" mode there's probably still stuff in the stdin + buffer, so don't read unless we really need MORE INPUT! MORE INPUT! */ + if (rzleft) + goto shovel; + + /* okay, suck more stdin */ + if (FD_ISSET(STDIN_FILENO, &ding2)) { /* stdin: ding! */ + rr = read(STDIN_FILENO, bigbuf_in, BIGSIZ); + /* Considered making reads here smaller for UDP mode, but 8192-byte + mobygrams are kinda fun and exercise the reassembler. */ + if (rr <= 0) { /* at end, or fukt, or ... */ + FD_CLR(STDIN_FILENO, &ding1); /* disable and close stdin */ + close(0); + } else { + rzleft = rr; + zp = bigbuf_in; + } + } /* stdin:ding */ + shovel: + /* now that we've dingdonged all our thingdings, send off the results. + Geez, why does this look an awful lot like the big loop in "rsh"? ... + not sure if the order of this matters, but write net -> stdout first. */ + + /* sanity check. Works because they're both unsigned... */ + if ((rzleft > 8200) || (rnleft > 8200)) { + holler_error("bogus buffers: %u, %u", rzleft, rnleft); + rzleft = rnleft = 0; + } + /* net write retries sometimes happen on UDP connections */ + if (!wretry) { /* is something hung? */ + holler_error("too many output retries"); + return 1; + } + if (rnleft) { + rr = write(STDOUT_FILENO, np, rnleft); + if (rr > 0) { + if (o_ofile) /* log the stdout */ + oprint('<', (unsigned char *)np, rr); + np += rr; /* fix up ptrs and whatnot */ + rnleft -= rr; /* will get sanity-checked above */ + wrote_out += rr; /* global count */ + } +Debug("wrote %d to stdout, errno %d", rr, errno); + } /* rnleft */ + if (rzleft) { + if (o_interval) /* in "slowly" mode ?? */ + rr = findline(zp, rzleft); + else + rr = rzleft; + rr = write(netfd, zp, rr); /* one line, or the whole buffer */ + if (rr > 0) { + if (o_ofile) /* log what got sent */ + oprint('>', (unsigned char *)zp, rr); + zp += rr; + rzleft -= rr; + wrote_net += rr; /* global count */ + } +Debug("wrote %d to net, errno %d", rr, errno); + } /* rzleft */ + if (o_interval) { /* cycle between slow lines, or ... */ + sleep(o_interval); + errno = 0; /* clear from sleep */ + continue; /* ...with hairy select loop... */ + } + if ((rzleft) || (rnleft)) { /* shovel that shit till they ain't */ + wretry--; /* none left, and get another load */ + goto shovel; + } + } /* while ding1:netfd is open */ + + /* XXX: maybe want a more graceful shutdown() here, or screw around with + linger times?? I suspect that I don't need to since I'm always doing + blocking reads and writes and my own manual "last ditch" efforts to read + the net again after a timeout. I haven't seen any screwups yet, but it's + not like my test network is particularly busy... */ + close(netfd); + return 0; +} /* readwrite */ + +/* main: now we pull it all together... */ +int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nc_main(int argc, char **argv) +{ + char *str_p, *str_s; + USE_NC_EXTRA(char *str_i, *str_o;) + char *themdotted = themdotted; /* gcc */ + char **proggie; + int x; + unsigned o_lport = 0; + + INIT_G(); + + /* catch a signal or two for cleanup */ + bb_signals(0 + + (1 << SIGINT) + + (1 << SIGQUIT) + + (1 << SIGTERM) + , catch); + /* and suppress others... */ + bb_signals(0 +#ifdef SIGURG + + (1 << SIGURG) +#endif + + (1 << SIGPIPE) /* important! */ + , SIG_IGN); + + proggie = argv; + while (*++proggie) { + if (strcmp(*proggie, "-e") == 0) { + *proggie = NULL; + argc = proggie - argv; + proggie++; + goto e_found; + } + } + proggie = NULL; + e_found: + + // -g -G -t -r deleted, unimplemented -a deleted too + opt_complementary = "?2:vv:w+"; /* max 2 params; -v is a counter; -w N */ + getopt32(argv, "hnp:s:uvw:" USE_NC_SERVER("l") + USE_NC_EXTRA("i:o:z"), + &str_p, &str_s, &o_wait + USE_NC_EXTRA(, &str_i, &str_o, &o_verbose)); + argv += optind; +#if ENABLE_NC_EXTRA + if (option_mask32 & OPT_i) /* line-interval time */ + o_interval = xatou_range(str_i, 1, 0xffff); +#endif + //if (option_mask32 & OPT_l) /* listen mode */ + //if (option_mask32 & OPT_n) /* numeric-only, no DNS lookups */ + //if (option_mask32 & OPT_o) /* hexdump log */ + if (option_mask32 & OPT_p) { /* local source port */ + o_lport = bb_lookup_port(str_p, o_udpmode ? "udp" : "tcp", 0); + if (!o_lport) + bb_error_msg_and_die("bad local port '%s'", str_p); + } + //if (option_mask32 & OPT_r) /* randomize various things */ + //if (option_mask32 & OPT_u) /* use UDP */ + //if (option_mask32 & OPT_v) /* verbose */ + //if (option_mask32 & OPT_w) /* wait time */ + //if (option_mask32 & OPT_z) /* little or no data xfer */ + + /* We manage our fd's so that they are never 0,1,2 */ + /*bb_sanitize_stdio(); - not needed */ + + if (argv[0]) { + themaddr = xhost2sockaddr(argv[0], + argv[1] + ? bb_lookup_port(argv[1], o_udpmode ? "udp" : "tcp", 0) + : 0); + } + + /* create & bind network socket */ + x = (o_udpmode ? SOCK_DGRAM : SOCK_STREAM); + if (option_mask32 & OPT_s) { /* local address */ + /* if o_lport is still 0, then we will use random port */ + ouraddr = xhost2sockaddr(str_s, o_lport); +#ifdef BLOAT + /* prevent spurious "UDP listen needs !0 port" */ + o_lport = get_nport(ouraddr); + o_lport = ntohs(o_lport); +#endif + x = xsocket(ouraddr->u.sa.sa_family, x, 0); + } else { + /* We try IPv6, then IPv4, unless addr family is + * implicitly set by way of remote addr/port spec */ + x = xsocket_type(&ouraddr, + (themaddr ? themaddr->u.sa.sa_family : AF_UNSPEC), + x); + if (o_lport) + set_nport(ouraddr, htons(o_lport)); + } + xmove_fd(x, netfd); + setsockopt_reuseaddr(netfd); + if (o_udpmode) + socket_want_pktinfo(netfd); + xbind(netfd, &ouraddr->u.sa, ouraddr->len); +#if 0 + setsockopt(netfd, SOL_SOCKET, SO_RCVBUF, &o_rcvbuf, sizeof o_rcvbuf); + setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, &o_sndbuf, sizeof o_sndbuf); +#endif + +#ifdef BLOAT + if (OPT_l && (option_mask32 & (OPT_u|OPT_l)) == (OPT_u|OPT_l)) { + /* apparently UDP can listen ON "port 0", + but that's not useful */ + if (!o_lport) + bb_error_msg_and_die("UDP listen needs nonzero -p port"); + } +#endif + + FD_SET(STDIN_FILENO, &ding1); /* stdin *is* initially open */ + if (proggie) { + close(0); /* won't need stdin */ + option_mask32 &= ~OPT_o; /* -o with -e is meaningless! */ + } +#if ENABLE_NC_EXTRA + if (o_ofile) + xmove_fd(xopen(str_o, O_WRONLY|O_CREAT|O_TRUNC), ofd); +#endif + + if (o_listen) { + dolisten(); + /* dolisten does its own connect reporting */ + if (proggie) /* -e given? */ + doexec(proggie); + x = readwrite(); /* it even works with UDP! */ + } else { + /* Outbound connects. Now we're more picky about args... */ + if (!themaddr) + bb_error_msg_and_die("no destination"); + + remend = *themaddr; + if (o_verbose) + themdotted = xmalloc_sockaddr2dotted(&themaddr->u.sa); + + x = connect_w_timeout(netfd); + if (o_zero && x == 0 && o_udpmode) /* if UDP scanning... */ + x = udptest(); + if (x == 0) { /* Yow, are we OPEN YET?! */ + if (o_verbose) + fprintf(stderr, "%s (%s) open\n", argv[0], themdotted); + if (proggie) /* exec is valid for outbound, too */ + doexec(proggie); + if (!o_zero) + x = readwrite(); + } else { /* connect or udptest wasn't successful */ + x = 1; /* exit status */ + /* if we're scanning at a "one -v" verbosity level, don't print refusals. + Give it another -v if you want to see everything. */ + if (o_verbose > 1 || (o_verbose && errno != ECONNREFUSED)) + bb_perror_msg("%s (%s)", argv[0], themdotted); + } + } + if (o_verbose > 1) /* normally we don't care */ + fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out); + return x; +} diff --git a/networking/netstat.c b/networking/netstat.c new file mode 100644 index 0000000..b246280 --- /dev/null +++ b/networking/netstat.c @@ -0,0 +1,712 @@ +/* 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> + * + * 2008-07-10 + * optional '-p' flag support ported from net-tools by G. Somlo <somlo@cmu.edu> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "inet_common.h" + +#define NETSTAT_OPTS "laentuwx" \ + USE_ROUTE( "r") \ + USE_FEATURE_NETSTAT_WIDE("W") \ + USE_FEATURE_NETSTAT_PRG( "p") + +enum { + OPTBIT_KEEP_OLD = 7, + USE_ROUTE( OPTBIT_ROUTE,) + USE_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE ,) + USE_FEATURE_NETSTAT_PRG( OPTBIT_PRG ,) + OPT_sock_listen = 1 << 0, // l + OPT_sock_all = 1 << 1, // a + OPT_extended = 1 << 2, // e + OPT_noresolve = 1 << 3, // n + OPT_sock_tcp = 1 << 4, // t + OPT_sock_udp = 1 << 5, // u + OPT_sock_raw = 1 << 6, // w + OPT_sock_unix = 1 << 7, // x + OPT_route = USE_ROUTE( (1 << OPTBIT_ROUTE)) + 0, // r + OPT_wide = USE_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE )) + 0, // W + OPT_prg = USE_FEATURE_NETSTAT_PRG( (1 << OPTBIT_PRG )) + 0, // p +}; + +#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) + + +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 */ + +/* Standard printout size */ +#define PRINT_IP_MAX_SIZE 23 +#define PRINT_NET_CONN "%s %6ld %6ld %-23s %-23s %-12s" +#define PRINT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-23s %-23s State " + +/* When there are IPv6 connections the IPv6 addresses will be + * truncated to none-recognition. The '-W' option makes the + * address columns wide enough to accomodate for longest possible + * IPv6 addresses, i.e. addresses of the form + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd + */ +#define PRINT_IP_MAX_SIZE_WIDE 51 /* INET6_ADDRSTRLEN + 5 for the port number */ +#define PRINT_NET_CONN_WIDE "%s %6ld %6ld %-51s %-51s %-12s" +#define PRINT_NET_CONN_HEADER_WIDE "\nProto Recv-Q Send-Q %-51s %-51s State " + + +#define PROGNAME_WIDTH 20 +#define PROGNAME_WIDTH_STR "20" +/* PROGNAME_WIDTH chars: 12345678901234567890 */ +#define PROGNAME_BANNER "PID/Program name " + +struct prg_node { + struct prg_node *next; + long inode; + char name[PROGNAME_WIDTH]; +}; + +#define PRG_HASH_SIZE 211 + + +struct globals { + const char *net_conn_line; + smallint flags; +#if ENABLE_FEATURE_NETSTAT_PRG + smallint prg_cache_loaded; + struct prg_node *prg_hash[PRG_HASH_SIZE]; +#endif +}; +#define G (*ptr_to_globals) +#define flags (G.flags ) +#define net_conn_line (G.net_conn_line ) +#define prg_hash (G.prg_hash ) +#define prg_cache_loaded (G.prg_cache_loaded) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \ + net_conn_line = PRINT_NET_CONN; \ +} while (0) + + +#if ENABLE_FEATURE_NETSTAT_PRG + +/* Deliberately truncating long to unsigned *int* */ +#define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE) + +#define print_progname_banner() do { \ + if (option_mask32 & OPT_prg) printf(PROGNAME_BANNER); \ +} while (0) + +static void prg_cache_add(long inode, char *name) +{ + unsigned hi = PRG_HASHIT(inode); + struct prg_node **pnp, *pn; + + prg_cache_loaded = 2; + for (pnp = prg_hash + hi; (pn = *pnp) != NULL; pnp = &pn->next) { + if (pn->inode == inode) { + /* Some warning should be appropriate here + as we got multiple processes for one i-node */ + return; + } + } + *pnp = xzalloc(sizeof(struct prg_node)); + pn = *pnp; + pn->inode = inode; + safe_strncpy(pn->name, name, PROGNAME_WIDTH); +} + +static const char *prg_cache_get(long inode) +{ + unsigned hi = PRG_HASHIT(inode); + struct prg_node *pn; + + for (pn = prg_hash[hi]; pn; pn = pn->next) + if (pn->inode == inode) + return pn->name; + return "-"; +} + +#if ENABLE_FEATURE_CLEAN_UP +static void prg_cache_clear(void) +{ + struct prg_node **pnp, *pn; + + for (pnp = prg_hash; pnp < prg_hash + PRG_HASH_SIZE; pnp++) { + while ((pn = *pnp) != NULL) { + *pnp = pn->next; + free(pn); + } + } +} +#else +#define prg_cache_clear() ((void)0) +#endif + +static long extract_socket_inode(const char *lname) +{ + long inode = -1; + + if (strncmp(lname, "socket:[", sizeof("socket:[")-1) == 0) { + /* "socket:[12345]", extract the "12345" as inode */ + inode = bb_strtol(lname + sizeof("socket:[")-1, (char**)&lname, 0); + if (*lname != ']') + inode = -1; + } else if (strncmp(lname, "[0000]:", sizeof("[0000]:")-1) == 0) { + /* "[0000]:12345", extract the "12345" as inode */ + inode = bb_strtol(lname + sizeof("[0000]:")-1, NULL, 0); + if (errno) /* not NUL terminated? */ + inode = -1; + } + +#if 0 /* bb_strtol returns all-ones bit pattern on ERANGE anyway */ + if (errno == ERANGE) + inode = -1; +#endif + return inode; +} + +static int FAST_FUNC file_act(const char *fileName, + struct stat *statbuf UNUSED_PARAM, + void *userData, + int depth UNUSED_PARAM) +{ + char *linkname; + long inode; + + linkname = xmalloc_readlink(fileName); + if (linkname != NULL) { + inode = extract_socket_inode(linkname); + free(linkname); + if (inode >= 0) + prg_cache_add(inode, (char *)userData); + } + return TRUE; +} + +static int FAST_FUNC dir_act(const char *fileName, + struct stat *statbuf UNUSED_PARAM, + void *userData UNUSED_PARAM, + int depth) +{ + const char *shortName; + char *p, *q; + char cmdline_buf[512]; + int i; + + if (depth == 0) /* "/proc" itself */ + return TRUE; /* continue looking one level below /proc */ + + shortName = fileName + sizeof("/proc/")-1; /* point after "/proc/" */ + if (!isdigit(shortName[0])) /* skip /proc entries whic aren't processes */ + return SKIP; + + p = concat_path_file(fileName, "cmdline"); /* "/proc/PID/cmdline" */ + i = open_read_close(p, cmdline_buf, sizeof(cmdline_buf) - 1); + free(p); + if (i < 0) + return FALSE; + cmdline_buf[i] = '\0'; + q = concat_path_file(shortName, bb_basename(cmdline_buf)); /* "PID/argv0" */ + + /* go through all files in /proc/PID/fd */ + p = concat_path_file(fileName, "fd"); + i = recursive_action(p, ACTION_RECURSE | ACTION_QUIET, + file_act, NULL, (void *)q, 0); + + free(p); + free(q); + + if (!i) + return FALSE; /* signal permissions error to caller */ + + return SKIP; /* caller should not recurse further into this dir. */ +} + +static void prg_cache_load(void) +{ + int load_ok; + + prg_cache_loaded = 1; + load_ok = recursive_action("/proc", ACTION_RECURSE | ACTION_QUIET, + NULL, dir_act, NULL, 0); + if (load_ok) + return; + + if (prg_cache_loaded == 1) + bb_error_msg("can't scan /proc - are you root?"); + else + bb_error_msg("showing only processes with your user ID"); +} + +#else + +#define prg_cache_clear() ((void)0) +#define print_progname_banner() ((void)0) + +#endif //ENABLE_FEATURE_NETSTAT_PRG + + +#if ENABLE_FEATURE_IPV6 +static void build_ipv6_addr(char* local_addr, struct sockaddr_in6* localaddr) +{ + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; + + 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); + + localaddr->sin6_family = AF_INET6; +} +#endif + +#if ENABLE_FEATURE_IPV6 +static void build_ipv4_addr(char* local_addr, struct sockaddr_in6* localaddr) +#else +static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr) +#endif +{ + sscanf(local_addr, "%X", + &((struct sockaddr_in *) localaddr)->sin_addr.s_addr); + ((struct sockaddr *) localaddr)->sa_family = AF_INET; +} + +static const char *get_sname(int port, const char *proto, int numeric) +{ + if (!port) + return "*"; + if (!numeric) { + struct servent *se = getservbyport(port, proto); + if (se) + return se->s_name; + } + /* hummm, we may return static buffer here!! */ + return itoa(ntohs(port)); +} + +static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric) +{ + char *host, *host_port; + + /* Code which used "*" for INADDR_ANY is removed: it's ambiguous + * in IPv6, while "0.0.0.0" is not. */ + + host = numeric ? xmalloc_sockaddr2dotted_noport(addr) + : xmalloc_sockaddr2host_noport(addr); + + host_port = xasprintf("%s:%s", host, get_sname(htons(port), proto, numeric)); + free(host); + return host_port; +} + +struct inet_params { + int local_port, rem_port, state, uid; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, inode; +}; + +static int scan_inet_proc_line(struct inet_params *param, char *line) +{ + int num; + char local_addr[64], rem_addr[64]; + + num = sscanf(line, + "%*d: %64[0-9A-Fa-f]:%X " + "%64[0-9A-Fa-f]:%X %X " + "%lX:%lX %*X:%*X " + "%*X %d %*d %ld ", + local_addr, ¶m->local_port, + rem_addr, ¶m->rem_port, ¶m->state, + ¶m->txq, ¶m->rxq, + ¶m->uid, ¶m->inode); + if (num < 9) { + return 1; /* error */ + } + + if (strlen(local_addr) > 8) { +#if ENABLE_FEATURE_IPV6 + build_ipv6_addr(local_addr, ¶m->localaddr); + build_ipv6_addr(rem_addr, ¶m->remaddr); +#endif + } else { + build_ipv4_addr(local_addr, ¶m->localaddr); + build_ipv4_addr(rem_addr, ¶m->remaddr); + } + return 0; +} + +static void print_inet_line(struct inet_params *param, + const char *state_str, const char *proto, int is_connected) +{ + if ((is_connected && (flags & NETSTAT_CONNECTED)) + || (!is_connected && (flags & NETSTAT_LISTENING)) + ) { + char *l = ip_port_str( + (struct sockaddr *) ¶m->localaddr, param->local_port, + proto, flags & NETSTAT_NUMERIC); + char *r = ip_port_str( + (struct sockaddr *) ¶m->remaddr, param->rem_port, + proto, flags & NETSTAT_NUMERIC); + printf(net_conn_line, + proto, param->rxq, param->txq, l, r, state_str); +#if ENABLE_FEATURE_NETSTAT_PRG + if (option_mask32 & OPT_prg) + printf("%."PROGNAME_WIDTH_STR"s", prg_cache_get(param->inode)); +#endif + bb_putchar('\n'); + free(l); + free(r); + } +} + +static int FAST_FUNC tcp_do_one(char *line) +{ + struct inet_params param; + + if (scan_inet_proc_line(¶m, line)) + return 1; + + print_inet_line(¶m, tcp_state[param.state], "tcp", param.rem_port); + return 0; +} + +#if ENABLE_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 + +static int FAST_FUNC udp_do_one(char *line) +{ + int have_remaddr; + const char *state_str; + struct inet_params param; + + if (scan_inet_proc_line(¶m, line)) + return 1; + + state_str = "UNKNOWN"; + switch (param.state) { + case TCP_ESTABLISHED: + state_str = "ESTABLISHED"; + break; + case TCP_CLOSE: + state_str = ""; + break; + } + + have_remaddr = notnull(param.remaddr); + print_inet_line(¶m, state_str, "udp", have_remaddr); + return 0; +} + +static int FAST_FUNC raw_do_one(char *line) +{ + int have_remaddr; + struct inet_params param; + + if (scan_inet_proc_line(¶m, line)) + return 1; + + have_remaddr = notnull(param.remaddr); + print_inet_line(¶m, itoa(param.state), "raw", have_remaddr); + return 0; +} + +static int FAST_FUNC unix_do_one(char *line) +{ + unsigned long refcnt, proto, unix_flags; + unsigned long inode; + int type, state; + int num, path_ofs; + const char *ss_proto, *ss_state, *ss_type; + char ss_flags[32]; + + /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..." + * Other users report long lines filled by NUL bytes. + * (those ^@ are NUL bytes too). We see them as empty lines. */ + if (!line[0]) + return 0; + + path_ofs = 0; /* paranoia */ + num = sscanf(line, "%*p: %lX %lX %lX %X %X %lu %n", + &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs); + if (num < 6) { + return 1; /* error */ + } + if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) != (NETSTAT_LISTENING|NETSTAT_CONNECTED)) { + if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) { + if (!(flags & NETSTAT_LISTENING)) + return 0; + } else { + if (!(flags & NETSTAT_CONNECTED)) + return 0; + } + } + + 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 %6lu ", + ss_proto, refcnt, ss_flags, ss_type, ss_state, inode + ); + +#if ENABLE_FEATURE_NETSTAT_PRG + if (option_mask32 & OPT_prg) + printf("%-"PROGNAME_WIDTH_STR"s", prg_cache_get(inode)); +#endif + + /* TODO: currently we stop at first NUL byte. Is it a problem? */ + line += path_ofs; + *strchrnul(line, '\n') = '\0'; + while (*line) + fputc_printable(*line++, stdout); + bb_putchar('\n'); + return 0; +} + +static void do_info(const char *file, int FAST_FUNC (*proc)(char *)) +{ + int lnr; + FILE *procinfo; + char *buffer; + + /* _stdin is just to save "r" param */ + procinfo = fopen_or_warn_stdin(file); + if (procinfo == NULL) { + return; + } + lnr = 0; + /* Why xmalloc_fgets_str? because it doesn't stop on NULs */ + while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) { + /* line 0 is skipped */ + if (lnr && proc(buffer)) + bb_error_msg("%s: bogus data on line %d", file, lnr + 1); + lnr++; + free(buffer); + } + fclose(procinfo); +} + +int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int netstat_main(int argc UNUSED_PARAM, char **argv) +{ + const char *net_conn_line_header = PRINT_NET_CONN_HEADER; + unsigned opt; + + INIT_G(); + + /* Option string must match NETSTAT_xxx constants */ + opt = getopt32(argv, NETSTAT_OPTS); + 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_route) { // -r +#if ENABLE_ROUTE + bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended)); + return 0; +#else + bb_show_usage(); +#endif + } + if (opt & OPT_wide) { // -W + net_conn_line = PRINT_NET_CONN_WIDE; + net_conn_line_header = PRINT_NET_CONN_HEADER_WIDE; + } +#if ENABLE_FEATURE_NETSTAT_PRG + if (opt & OPT_prg) { // -p + prg_cache_load(); + } +#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(net_conn_line_header, "Local Address", "Foreign Address"); + print_progname_banner(); + bb_putchar('\n'); + } + if (flags & NETSTAT_TCP) { + do_info("/proc/net/tcp", tcp_do_one); +#if ENABLE_FEATURE_IPV6 + do_info("/proc/net/tcp6", tcp_do_one); +#endif + } + if (flags & NETSTAT_UDP) { + do_info("/proc/net/udp", udp_do_one); +#if ENABLE_FEATURE_IPV6 + do_info("/proc/net/udp6", udp_do_one); +#endif + } + if (flags & NETSTAT_RAW) { + do_info("/proc/net/raw", raw_do_one); +#if ENABLE_FEATURE_IPV6 + do_info("/proc/net/raw6", 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 "); + print_progname_banner(); + printf("Path\n"); + do_info("/proc/net/unix", unix_do_one); + } + prg_cache_clear(); + return 0; +} diff --git a/networking/nslookup.c b/networking/nslookup.c new file mode 100644 index 0000000..73ccb0d --- /dev/null +++ b/networking/nslookup.c @@ -0,0 +1,155 @@ +/* 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 "libbb.h" + +/* + * I'm only implementing non-interactive mode; + * I totally forgot nslookup even had an interactive mode. + * + * This applet is the only user of res_init(). Without it, + * you may avoid pulling in _res global from libc. + */ + +/* 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 print_host(const char *hostname, const char *header) +{ + /* We can't use xhost2sockaddr() - we want to get ALL addresses, + * not just one */ + 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; + unsigned cnt = 0; + + printf("%-10s %s\n", header, hostname); + // puts(cur->ai_canonname); ? + while (cur) { + char *dotted, *revhost; + dotted = xmalloc_sockaddr2dotted_noport(cur->ai_addr); + revhost = xmalloc_sockaddr2hostonly_noport(cur->ai_addr); + + printf("Address %u: %s%c", ++cnt, dotted, revhost ? ' ' : '\n'); + if (revhost) { + puts(revhost); + if (ENABLE_FEATURE_CLEAN_UP) + free(revhost); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(dotted); + cur = cur->ai_next; + } + } else { +#if ENABLE_VERBOSE_RESOLUTION_ERRORS + bb_error_msg("can't resolve '%s': %s", hostname, gai_strerror(rc)); +#else + bb_error_msg("can't resolve '%s'", hostname); +#endif + } + if (ENABLE_FEATURE_CLEAN_UP) + freeaddrinfo(result); + return (rc != 0); +} + +/* lookup the default nameserver and display it */ +static void server_print(void) +{ + char *server; + + server = xmalloc_sockaddr2dotted_noport((struct sockaddr*)&_res.nsaddr_list[0]); + /* I honestly don't know what to do if DNS server has _IPv6 address_. + * Probably it is listed in + * _res._u._ext_.nsaddrs[MAXNS] (of type "struct sockaddr_in6*" each) + * but how to find out whether resolver uses + * _res.nsaddr_list[] or _res._u._ext_.nsaddrs[], or both? + * Looks like classic design from hell, BIND-grade. Hard to surpass. */ + print_host(server, "Server:"); + if (ENABLE_FEATURE_CLEAN_UP) + free(server); + bb_putchar('\n'); +} + +/* alter the global _res nameserver structure to use + an explicit dns server instead of what is in /etc/resolv.conf */ +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; + } +} + +int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nslookup_main(int argc, char **argv) +{ + /* 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 (!argv[1] || argv[1][0] == '-' || argc > 3) + bb_show_usage(); + + /* initialize DNS structure _res used in printing the default + * name server and in the explicit name server option feature. */ + res_init(); + /* rfc2133 says this enables IPv6 lookups */ + /* (but it also says "may be enabled in /etc/resolv.conf") */ + /*_res.options |= RES_USE_INET6;*/ + + if (argv[2]) + set_default_dns(argv[2]); + + server_print(); + return print_host(argv[1], "Name:"); +} diff --git a/networking/ping.c b/networking/ping.c new file mode 100644 index 0000000..f2a612f --- /dev/null +++ b/networking/ping.c @@ -0,0 +1,812 @@ +/* vi: set sw=4 ts=4: */ +/* + * 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. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +/* from ping6.c: + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * This version of ping is adapted from the ping in netkit-base 0.10, + * which is: + * + * 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 <net/if.h> +#include <netinet/ip_icmp.h> +#include "libbb.h" + +#if ENABLE_PING6 +#include <netinet/icmp6.h> +/* I see RENUMBERED constants in bits/in.h - !!? + * What a fuck is going on with libc? Is it a glibc joke? */ +#ifdef IPV6_2292HOPLIMIT +#undef IPV6_HOPLIMIT +#define IPV6_HOPLIMIT IPV6_2292HOPLIMIT +#endif +#endif + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1, /* 1 second */ +}; + +/* 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; +} + +#if !ENABLE_FEATURE_FANCY_PING + +/* simple version */ + +static char *hostname; + +static void noresp(int ign UNUSED_PARAM) +{ + printf("No response from %s\n", hostname); + exit(EXIT_FAILURE); +} + +static void ping4(len_and_sockaddr *lsa) +{ + struct sockaddr_in pingaddr; + struct icmp *pkt; + int pingsock, c; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp_socket(); + pingaddr = lsa->u.sin; + + 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 = xsendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN, + (struct sockaddr *) &pingaddr, sizeof(pingaddr)); + + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen); + if (c < 0) { + if (errno != EINTR) + 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); +} + +#if ENABLE_PING6 +static void ping6(len_and_sockaddr *lsa) +{ + struct sockaddr_in6 pingaddr; + struct icmp6_hdr *pkt; + int pingsock, c; + int sockopt; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp6_socket(); + pingaddr = lsa->u.sin6; + + 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, &sockopt, sizeof(sockopt)); + + c = xsendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr), + (struct sockaddr *) &pingaddr, sizeof(pingaddr)); + + /* listen for replies */ + while (1) { + struct sockaddr_in6 from; + socklen_t fromlen = sizeof(from); + + c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen); + if (c < 0) { + if (errno != EINTR) + 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); +} +#endif + +int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ping_main(int argc UNUSED_PARAM, char **argv) +{ + len_and_sockaddr *lsa; +#if ENABLE_PING6 + sa_family_t af = AF_UNSPEC; + + while ((++argv)[0] && argv[0][0] == '-') { + if (argv[0][1] == '4') { + af = AF_INET; + continue; + } + if (argv[0][1] == '6') { + af = AF_INET6; + continue; + } + bb_show_usage(); + } +#else + argv++; +#endif + + hostname = *argv; + if (!hostname) + bb_show_usage(); + +#if ENABLE_PING6 + lsa = xhost_and_af2sockaddr(hostname, 0, af); +#else + lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET); +#endif + /* Set timer _after_ DNS resolution */ + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + +#if ENABLE_PING6 + if (lsa->u.sa.sa_family == AF_INET6) + ping6(lsa); + else +#endif + ping4(lsa); + printf("%s is alive!\n", hostname); + return EXIT_SUCCESS; +} + + +#else /* FEATURE_FANCY_PING */ + + +/* full(er) version */ + +#define OPT_STRING ("qvc:s:w:W:I:4" USE_PING6("6")) +enum { + OPT_QUIET = 1 << 0, + OPT_VERBOSE = 1 << 1, + OPT_c = 1 << 2, + OPT_s = 1 << 3, + OPT_w = 1 << 4, + OPT_W = 1 << 5, + OPT_I = 1 << 6, + OPT_IPV4 = 1 << 7, + OPT_IPV6 = (1 << 8) * ENABLE_PING6, +}; + + +struct globals { + int pingsock; + int if_index; + char *str_I; + len_and_sockaddr *source_lsa; + unsigned datalen; + unsigned pingcount; /* must be int-sized */ + unsigned long ntransmitted, nreceived, nrepeats; + uint16_t myid; + unsigned tmin, tmax; /* in us */ + unsigned long long tsum; /* in us, sum of all times */ + unsigned deadline; + unsigned timeout; + unsigned total_secs; + const char *hostname; + const char *dotted; + union { + struct sockaddr sa; + struct sockaddr_in sin; +#if ENABLE_PING6 + struct sockaddr_in6 sin6; +#endif + } pingaddr; + char rcvd_tbl[MAX_DUP_CHK / 8]; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define pingsock (G.pingsock ) +#define if_index (G.if_index ) +#define source_lsa (G.source_lsa ) +#define str_I (G.str_I ) +#define datalen (G.datalen ) +#define ntransmitted (G.ntransmitted) +#define nreceived (G.nreceived ) +#define nrepeats (G.nrepeats ) +#define pingcount (G.pingcount ) +#define myid (G.myid ) +#define tmin (G.tmin ) +#define tmax (G.tmax ) +#define tsum (G.tsum ) +#define deadline (G.deadline ) +#define timeout (G.timeout ) +#define total_secs (G.total_secs ) +#define hostname (G.hostname ) +#define dotted (G.dotted ) +#define pingaddr (G.pingaddr ) +#define rcvd_tbl (G.rcvd_tbl ) +void BUG_ping_globals_too_big(void); +#define INIT_G() do { \ + if (sizeof(G) > COMMON_BUFSIZE) \ + BUG_ping_globals_too_big(); \ + pingsock = -1; \ + datalen = DEFDATALEN; \ + timeout = MAXWAIT; \ + tmin = UINT_MAX; \ +} while (0) + + +#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 print_stats_and_exit(int junk) NORETURN; +static void print_stats_and_exit(int junk UNUSED_PARAM) +{ + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostname); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + ntransmitted = (ntransmitted - nreceived) * 100 / ntransmitted; + printf("%lu%% packet loss\n", ntransmitted); + if (tmin != UINT_MAX) { + unsigned tavg = tsum / (nreceived + nrepeats); + printf("round-trip min/avg/max = %u.%03u/%u.%03u/%u.%03u ms\n", + tmin / 1000, tmin % 1000, + tavg / 1000, tavg % 1000, + tmax / 1000, tmax % 1000); + } + /* if condition is true, exit with 1 -- 'failure' */ + exit(nreceived == 0 || (deadline && nreceived < pingcount)); +} + +static void sendping_tail(void (*sp)(int), const void *pkt, int size_pkt) +{ + int sz; + + CLR((uint16_t)ntransmitted % MAX_DUP_CHK); + ntransmitted++; + + /* sizeof(pingaddr) can be larger than real sa size, but I think + * it doesn't matter */ + sz = xsendto(pingsock, pkt, size_pkt, &pingaddr.sa, sizeof(pingaddr)); + if (sz != size_pkt) + bb_error_msg_and_die(bb_msg_write_error); + + if (pingcount == 0 || deadline || ntransmitted < pingcount) { + /* Didn't send all pings yet - schedule next in 1s */ + signal(SIGALRM, sp); + if (deadline) { + total_secs += PINGINTERVAL; + if (total_secs >= deadline) + signal(SIGALRM, print_stats_and_exit); + } + alarm(PINGINTERVAL); + } else { /* -c NN, and all NN are sent (and no deadline) */ + /* Wait for the last ping to come back. + * -W timeout: wait for a response in seconds. + * Affects only timeout in absense of any responses, + * otherwise ping waits for two RTTs. */ + unsigned expire = timeout; + + if (nreceived) { + /* approx. 2*tmax, in seconds (2 RTT) */ + expire = tmax / (512*1024); + if (expire == 0) + expire = 1; + } + signal(SIGALRM, print_stats_and_exit); + alarm(expire); + } +} + +static void sendping4(int junk UNUSED_PARAM) +{ + /* +4 reserves a place for timestamp, which may end up sitting + * *after* packet. Saves one if() */ + struct icmp *pkt = alloca(datalen + ICMP_MINLEN + 4); + + memset(pkt, 0, datalen + ICMP_MINLEN + 4); + pkt->icmp_type = ICMP_ECHO; + /*pkt->icmp_code = 0;*/ + /*pkt->icmp_cksum = 0;*/ + pkt->icmp_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */ + pkt->icmp_id = myid; + + /* We don't do hton, because we will read it back on the same machine */ + /*if (datalen >= 4)*/ + *(uint32_t*)&pkt->icmp_dun = monotonic_us(); + + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, datalen + ICMP_MINLEN); + + sendping_tail(sendping4, pkt, datalen + ICMP_MINLEN); +} +#if ENABLE_PING6 +static void sendping6(int junk UNUSED_PARAM) +{ + struct icmp6_hdr *pkt = alloca(datalen + sizeof(struct icmp6_hdr) + 4); + + memset(pkt, 0, datalen + sizeof(struct icmp6_hdr) + 4); + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + /*pkt->icmp6_code = 0;*/ + /*pkt->icmp6_cksum = 0;*/ + pkt->icmp6_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */ + pkt->icmp6_id = myid; + + /*if (datalen >= 4)*/ + *(uint32_t*)(&pkt->icmp6_data8[4]) = monotonic_us(); + + sendping_tail(sendping6, pkt, datalen + sizeof(struct icmp6_hdr)); +} +#endif + +static const 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"; + } +} +#if ENABLE_PING6 +/* 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 const 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"; + } +} +#endif + +static void unpack_tail(int sz, uint32_t *tp, + const char *from_str, + uint16_t recv_seq, int ttl) +{ + const char *dupmsg = " (DUP!)"; + unsigned triptime = triptime; /* for gcc */ + + ++nreceived; + + if (tp) { + /* (int32_t) cast is for hypothetical 64-bit unsigned */ + /* (doesn't hurt 32-bit real-world anyway) */ + triptime = (int32_t) ((uint32_t)monotonic_us() - *tp); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + } + + if (TST(recv_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + } else { + SET(recv_seq % MAX_DUP_CHK); + dupmsg += 7; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: seq=%u ttl=%d", sz, + from_str, recv_seq, ttl); + if (tp) + printf(" time=%u.%03u ms", triptime / 1000, triptime % 1000); + puts(dupmsg); + fflush(stdout); +} +static void unpack4(char *buf, int sz, struct sockaddr_in *from) +{ + struct icmp *icmppkt; + struct iphdr *iphdr; + int hlen; + + /* 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) { + uint16_t recv_seq = ntohs(icmppkt->icmp_seq); + uint32_t *tp = NULL; + + if (sz >= ICMP_MINLEN + sizeof(uint32_t)) + tp = (uint32_t *) icmppkt->icmp_data; + unpack_tail(sz, tp, + inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), + recv_seq, iphdr->ttl); + } else if (icmppkt->icmp_type != ICMP_ECHO) { + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp_type, + icmp_type_name(icmppkt->icmp_type)); + } +} +#if ENABLE_PING6 +static void unpack6(char *packet, int sz, /*struct sockaddr_in6 *from,*/ int hoplimit) +{ + struct icmp6_hdr *icmppkt; + char buf[INET6_ADDRSTRLEN]; + + /* 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) { + uint16_t recv_seq = ntohs(icmppkt->icmp6_seq); + uint32_t *tp = NULL; + + if (sz >= sizeof(struct icmp6_hdr) + sizeof(uint32_t)) + tp = (uint32_t *) &icmppkt->icmp6_data8[4]; + unpack_tail(sz, tp, + inet_ntop(AF_INET6, &pingaddr.sin6.sin6_addr, + buf, sizeof(buf)), + recv_seq, hoplimit); + } 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)); + } +} +#endif + +static void ping4(len_and_sockaddr *lsa) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + int sockopt; + + pingsock = create_icmp_socket(); + pingaddr.sin = lsa->u.sin; + if (source_lsa) { + if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF, + &source_lsa->u.sa, source_lsa->len)) + bb_error_msg_and_die("can't set multicast source interface"); + xbind(pingsock, &source_lsa->u.sa, source_lsa->len); + } + if (str_I) + setsockopt_bindtodevice(pingsock, str_I); + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf (needed if we can get lots of responses: flood ping, + * broadcast ping etc) */ + sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */ + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt)); + + signal(SIGINT, print_stats_and_exit); + + /* start the ping's going ... */ + sendping4(0); + + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = (socklen_t) sizeof(from); + int c; + + c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen); + if (c < 0) { + if (errno != EINTR) + bb_perror_msg("recvfrom"); + continue; + } + unpack4(packet, c, &from); + if (pingcount && nreceived >= pingcount) + break; + } +} +#if ENABLE_PING6 +extern int BUG_bad_offsetof_icmp6_cksum(void); +static void ping6(len_and_sockaddr *lsa) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + int sockopt; + struct msghdr msg; + struct sockaddr_in6 from; + struct iovec iov; + char control_buf[CMSG_SPACE(36)]; + + pingsock = create_icmp6_socket(); + pingaddr.sin6 = lsa->u.sin6; + /* untested whether "-I addr" really works for IPv6: */ + if (source_lsa) + xbind(pingsock, &source_lsa->u.sa, source_lsa->len); + if (str_I) + setsockopt_bindtodevice(pingsock, str_I); + +#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 (needed if we can get lots of responses: flood ping, + * broadcast ping etc) */ + sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */ + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt)); + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + if (offsetof(struct icmp6_hdr, icmp6_cksum) != 2) + BUG_bad_offsetof_icmp6_cksum(); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt)); + + /* request ttl info to be returned in ancillary data */ + setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, &const_int_1, sizeof(const_int_1)); + + if (if_index) + pingaddr.sin6.sin6_scope_id = if_index; + + signal(SIGINT, print_stats_and_exit); + + /* start the ping's going ... */ + sendping6(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 *mp; + int hoplimit = -1; + msg.msg_controllen = sizeof(control_buf); + + c = recvmsg(pingsock, &msg, 0); + if (c < 0) { + if (errno != EINTR) + bb_perror_msg("recvfrom"); + continue; + } + for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) { + if (mp->cmsg_level == SOL_IPV6 + && mp->cmsg_type == IPV6_HOPLIMIT + /* don't check len - we trust the kernel: */ + /* && mp->cmsg_len >= CMSG_LEN(sizeof(int)) */ + ) { + hoplimit = *(int*)CMSG_DATA(mp); + } + } + unpack6(packet, c, /*&from,*/ hoplimit); + if (pingcount && nreceived >= pingcount) + break; + } +} +#endif + +static void ping(len_and_sockaddr *lsa) +{ + printf("PING %s (%s)", hostname, dotted); + if (source_lsa) { + printf(" from %s", + xmalloc_sockaddr2dotted_noport(&source_lsa->u.sa)); + } + printf(": %d data bytes\n", datalen); + +#if ENABLE_PING6 + if (lsa->u.sa.sa_family == AF_INET6) + ping6(lsa); + else +#endif + ping4(lsa); +} + +int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ping_main(int argc UNUSED_PARAM, char **argv) +{ + len_and_sockaddr *lsa; + char *str_s; + int opt; + + INIT_G(); + + /* exactly one argument needed; -v and -q don't mix; -c NUM, -w NUM, -W NUM */ + opt_complementary = "=1:q--v:v--q:c+:w+:W+"; + opt = getopt32(argv, OPT_STRING, &pingcount, &str_s, &deadline, &timeout, &str_I); + if (opt & OPT_s) + datalen = xatou16(str_s); // -s + if (opt & OPT_I) { // -I + if_index = if_nametoindex(str_I); + if (!if_index) { + /* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */ + source_lsa = xdotted2sockaddr(str_I, 0); + str_I = NULL; /* don't try to bind to device later */ + } + } + myid = (uint16_t) getpid(); + hostname = argv[optind]; +#if ENABLE_PING6 + { + sa_family_t af = AF_UNSPEC; + if (opt & OPT_IPV4) + af = AF_INET; + if (opt & OPT_IPV6) + af = AF_INET6; + lsa = xhost_and_af2sockaddr(hostname, 0, af); + } +#else + lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET); +#endif + + if (source_lsa && source_lsa->u.sa.sa_family != lsa->u.sa.sa_family) + /* leaking it here... */ + source_lsa = NULL; + + dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa); + ping(lsa); + print_stats_and_exit(EXIT_SUCCESS); + /*return EXIT_SUCCESS;*/ +} +#endif /* FEATURE_FANCY_PING */ + + +#if ENABLE_PING6 +int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ping6_main(int argc UNUSED_PARAM, char **argv) +{ + argv[0] = (char*)"-6"; + return ping_main(0 /* argc+1 - but it's unused anyway */, + argv - 1); +} +#endif + +/* from ping6.c: + * 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/pscan.c b/networking/pscan.c new file mode 100644 index 0000000..5fb6af0 --- /dev/null +++ b/networking/pscan.c @@ -0,0 +1,154 @@ +/* + * Pscan is a mini port scanner implementation for busybox + * + * Copyright 2007 Tito Ragusa <farmatito@tiscali.it> + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* debugging */ +#ifdef DEBUG_PSCAN +#define DMSG(...) bb_error_msg(__VA_ARGS__) +#define DERR(...) bb_perror_msg(__VA_ARGS__) +#else +#define DMSG(...) ((void)0) +#define DERR(...) ((void)0) +#endif + +static const char *port_name(unsigned port) +{ + struct servent *server; + + server = getservbyport(htons(port), NULL); + if (server) + return server->s_name; + return "unknown"; +} + +/* We don't expect to see 1000+ seconds delay, unsigned is enough */ +#define MONOTONIC_US() ((unsigned)monotonic_us()) + +int pscan_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int pscan_main(int argc UNUSED_PARAM, char **argv) +{ + const char *opt_max_port = "1024"; /* -P: default max port */ + const char *opt_min_port = "1"; /* -p: default min port */ + const char *opt_timeout = "5000"; /* -t: default timeout in msec */ + /* We estimate rtt and wait rtt*4 before concluding that port is + * totally blocked. min rtt of 5 ms may be too low if you are + * scanning an Internet host behind saturated/traffic shaped link. + * Rule of thumb: with min_rtt of N msec, scanning 1000 ports + * will take N seconds at absolute minimum */ + const char *opt_min_rtt = "5"; /* -T: default min rtt in msec */ + const char *result_str; + len_and_sockaddr *lsap; + int s; + unsigned opt; + unsigned port, max_port, nports; + unsigned closed_ports = 0; + unsigned open_ports = 0; + /* all in usec */ + unsigned timeout; + unsigned min_rtt; + unsigned rtt_4; + unsigned start, diff; + + opt_complementary = "=1"; /* exactly one non-option */ + opt = getopt32(argv, "cbp:P:t:T:", &opt_min_port, &opt_max_port, &opt_timeout, &opt_min_rtt); + argv += optind; + max_port = xatou_range(opt_max_port, 1, 65535); + port = xatou_range(opt_min_port, 1, max_port); + nports = max_port - port + 1; + min_rtt = xatou_range(opt_min_rtt, 1, INT_MAX/1000 / 4) * 1000; + timeout = xatou_range(opt_timeout, 1, INT_MAX/1000 / 4) * 1000; + /* Initial rtt is BIG: */ + rtt_4 = timeout; + + DMSG("min_rtt %u timeout %u", min_rtt, timeout); + + lsap = xhost2sockaddr(*argv, port); + printf("Scanning %s ports %u to %u\n Port\tProto\tState\tService\n", + *argv, port, max_port); + + for (; port <= max_port; port++) { + DMSG("rtt %u", rtt_4); + + /* The SOCK_STREAM socket type is implemented on the TCP/IP protocol. */ + set_nport(lsap, htons(port)); + s = xsocket(lsap->u.sa.sa_family, SOCK_STREAM, 0); + /* We need unblocking socket so we don't need to wait for ETIMEOUT. */ + /* Nonblocking connect typically "fails" with errno == EINPROGRESS */ + ndelay_on(s); + + DMSG("connect to port %u", port); + result_str = NULL; + start = MONOTONIC_US(); + if (connect(s, &lsap->u.sa, lsap->len) == 0) { + /* Unlikely, for me even localhost fails :) */ + DMSG("connect succeeded"); + goto open; + } + /* Check for untypical errors... */ + if (errno != EAGAIN && errno != EINPROGRESS + && errno != ECONNREFUSED + ) { + bb_perror_nomsg_and_die(); + } + + diff = 0; + while (1) { + if (errno == ECONNREFUSED) { + if (opt & 1) /* -c: show closed too */ + result_str = "closed"; + closed_ports++; + break; + } + DERR("port %u errno %d @%u", port, errno, diff); + + if (diff > rtt_4) { + if (opt & 2) /* -b: show blocked too */ + result_str = "blocked"; + break; + } + /* Can sleep (much) longer than specified delay. + * We check rtt BEFORE we usleep, otherwise + * on localhost we'll have no writes done (!) + * before we exceed (rather small) rtt */ + usleep(rtt_4/8); + open: + diff = MONOTONIC_US() - start; + DMSG("write to port %u @%u", port, diff - start); + if (write(s, " ", 1) >= 0) { /* We were able to write to the socket */ + open_ports++; + result_str = "open"; + break; + } + } + DMSG("out of loop @%u", diff); + if (result_str) + printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n", + port, result_str, port_name(port)); + + /* Estimate new rtt - we don't want to wait entire timeout + * for each port. *4 allows for rise in net delay. + * We increase rtt quickly (rtt_4*4), decrease slowly + * (diff is at least rtt_4/8, *4 == rtt_4/2) + * because we don't want to accidentally miss ports. */ + rtt_4 = diff * 4; + if (rtt_4 < min_rtt) + rtt_4 = min_rtt; + if (rtt_4 > timeout) + rtt_4 = timeout; + /* Clean up */ + close(s); + } + if (ENABLE_FEATURE_CLEAN_UP) free(lsap); + + printf("%d closed, %d open, %d timed out (or blocked) ports\n", + closed_ports, + open_ports, + nports - (closed_ports + open_ports)); + return EXIT_SUCCESS; +} diff --git a/networking/route.c b/networking/route.c new file mode 100644 index 0000000..8778ecd --- /dev/null +++ b/networking/route.c @@ -0,0 +1,699 @@ +/* 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. + * + * + * 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 <net/route.h> +#include <net/if.h> + +#include "libbb.h" +#include "inet_common.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[] ALIGN1 = + "\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[] ALIGN1 = + /* 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(&rt, 0, sizeof(rt)); + + { + 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 = (char*)"lo"; + } +#endif + + /* sanity checks.. */ + if (mask_in_addr(rt)) { + uint32_t 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 & ~(uint32_t)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 (action == RTACTION_ADD) + xioctl(skfd, SIOCADDRT, &rt); + else + xioctl(skfd, SIOCDELRT, &rt); + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} + +#if ENABLE_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; + cp = strchr(target, '/'); /* Yes... const to non is ok. */ + if (cp) { + *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(&rt, 0, sizeof(rt)); + + 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)); + xioctl(skfd, SIOGIFINDEX, &ifr); + rt.rtmsg_ifindex = ifr.ifr_ifindex; + } + + /* Tell the kernel to accept this route. */ + if (action == RTACTION_ADD) + xioctl(skfd, SIOCADDRT, &rt); + else + xioctl(skfd, SIOCDELRT, &rt); + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} +#endif + +static const unsigned flagvals[] = { /* Must agree with flagchars[]. */ + RTF_GATEWAY, + RTF_HOST, + RTF_REINSTATE, + RTF_DYNAMIC, + RTF_MODIFIED, +#if ENABLE_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) + +/* Must agree with flagvals[]. */ +static const char flagchars[] ALIGN1 = + "GHRDM" +#if ENABLE_FEATURE_IPV6 + "DAC" +#endif +; + +static 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 FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt) +{ + char devname[64], flags[16], *sdest, *sgw; + unsigned long d, g, m; + int flgs, ref, use, metric, mtu, win, ir; + struct sockaddr_in s_addr; + struct in_addr mask; + + FILE *fp = xfopen_for_read("/proc/net/route"); + + 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; + sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */ + s_addr.sin_addr.s_addr = g; + sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */ + mask.s_addr = m; + /* "%15.15s" truncates hostnames, do we really want that? */ + printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags); + free(sdest); + free(sgw); + if (netstatfmt) { + printf("%5d %-5d %6d %s\n", mtu, win, ir, devname); + } else { + printf("%-6d %-2d %7d %s\n", metric, ref, use, devname); + } + } +} + +#if ENABLE_FEATURE_IPV6 + +static void INET6_displayroutes(void) +{ + char addr6[128], *naddr6; + /* 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_for_read("/proc/net/ipv6_route"); + + 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; + naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6, + 0x0fff /* Apparently, upstream never resolves. */ + ); + + if (!r) { /* 1st pass */ + snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len); + r += 40; + free(naddr6); + } else { /* 2nd pass */ + /* Print the info. */ + printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n", + addr6, naddr6, flags, metric, refcnt, use, iface); + free(naddr6); + 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. */ +/* 2nd byte matches RTACTION_* code */ +static const char tbl_verb[] ALIGN1 = + "\006\001add\0" + "\006\002del\0" +/* "\011\002delete\0" */ + "\010\002delete" /* Since it's last, we can save a byte. */ +; + +int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int route_main(int argc UNUSED_PARAM, 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(argv, "A:ne", &family); + + if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) { +#if ENABLE_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; +#if ENABLE_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_displayroutes(); + else +#endif + bb_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(); + } + +#if ENABLE_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_setroute(what, argv); + else +#endif + INET_setroute(what, argv); + + return EXIT_SUCCESS; +} diff --git a/networking/slattach.c b/networking/slattach.c new file mode 100644 index 0000000..d3212bb --- /dev/null +++ b/networking/slattach.c @@ -0,0 +1,245 @@ +/* vi: set sw=4 ts=4: */ +/* + * Stripped down version of net-tools for busybox. + * + * Author: Ignacio Garcia Perez (iggarpe at gmail dot com) + * + * License: GPLv2 or later, see LICENSE file in this tarball. + * + * There are some differences from the standard net-tools slattach: + * + * - The -l option is not supported. + * + * - The -F options allows disabling of RTS/CTS flow control. + */ + +#include "libbb.h" +#include "libiproute/utils.h" /* invarg() */ + +struct globals { + int handle; + int saved_disc; + struct termios saved_state; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define handle (G.handle ) +#define saved_disc (G.saved_disc ) +#define saved_state (G.saved_state ) +#define INIT_G() do { } while (0) + + +/* + * Save tty state and line discipline + * + * It is fine here to bail out on errors, since we haven modified anything yet + */ +static void save_state(void) +{ + /* Save line status */ + if (tcgetattr(handle, &saved_state) < 0) + bb_perror_msg_and_die("get state"); + + /* Save line discipline */ + xioctl(handle, TIOCGETD, &saved_disc); +} + +static int set_termios_state_or_warn(struct termios *state) +{ + int ret; + + ret = tcsetattr(handle, TCSANOW, state); + if (ret < 0) { + bb_perror_msg("set state"); + return 1; /* used as exitcode */ + } + return 0; +} + +/* + * Restore state and line discipline for ALL managed ttys + * + * Restoring ALL managed ttys is the only way to have a single + * hangup delay. + * + * Go on after errors: we want to restore as many controlled ttys + * as possible. + */ +static void restore_state_and_exit(int exitcode) NORETURN; +static void restore_state_and_exit(int exitcode) +{ + struct termios state; + + /* Restore line discipline */ + if (ioctl_or_warn(handle, TIOCSETD, &saved_disc) < 0) { + exitcode = 1; + } + + /* Hangup */ + memcpy(&state, &saved_state, sizeof(state)); + cfsetispeed(&state, B0); + cfsetospeed(&state, B0); + if (set_termios_state_or_warn(&state)) + exitcode = 1; + sleep(1); + + /* Restore line status */ + if (set_termios_state_or_warn(&saved_state)) + exit(EXIT_FAILURE); + if (ENABLE_FEATURE_CLEAN_UP) + close(handle); + + exit(exitcode); +} + +/* + * Set tty state, line discipline and encapsulation + */ +static void set_state(struct termios *state, int encap) +{ + int disc; + + /* Set line status */ + if (set_termios_state_or_warn(state)) + goto bad; + /* Set line discliple (N_SLIP always) */ + disc = N_SLIP; + if (ioctl_or_warn(handle, TIOCSETD, &disc) < 0) { + goto bad; + } + + /* Set encapsulation (SLIP, CSLIP, etc) */ + if (ioctl_or_warn(handle, SIOCSIFENCAP, &encap) < 0) { + bad: + restore_state_and_exit(EXIT_FAILURE); + } +} + +static void sig_handler(int signo UNUSED_PARAM) +{ + restore_state_and_exit(EXIT_SUCCESS); +} + +int slattach_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int slattach_main(int argc UNUSED_PARAM, char **argv) +{ + /* Line discipline code table */ + static const char proto_names[] ALIGN1 = + "slip\0" /* 0 */ + "cslip\0" /* 1 */ + "slip6\0" /* 2 */ + "cslip6\0" /* 3 */ + "adaptive\0" /* 8 */ + ; + + int i, encap, opt; + struct termios state; + const char *proto = "cslip"; + const char *extcmd; /* Command to execute after hangup */ + const char *baud_str; + int baud_code = -1; /* Line baud rate (system code) */ + + enum { + OPT_p_proto = 1 << 0, + OPT_s_baud = 1 << 1, + OPT_c_extcmd = 1 << 2, + OPT_e_quit = 1 << 3, + OPT_h_watch = 1 << 4, + OPT_m_nonraw = 1 << 5, + OPT_L_local = 1 << 6, + OPT_F_noflow = 1 << 7 + }; + + INIT_G(); + + /* Parse command line options */ + opt = getopt32(argv, "p:s:c:ehmLF", &proto, &baud_str, &extcmd); + /*argc -= optind;*/ + argv += optind; + + if (!*argv) + bb_show_usage(); + + encap = index_in_strings(proto_names, proto); + + if (encap < 0) + invarg(proto, "protocol"); + if (encap > 3) + encap = 8; + + /* We want to know if the baud rate is valid before we start touching the ttys */ + if (opt & OPT_s_baud) { + baud_code = tty_value_to_baud(xatoi(baud_str)); + if (baud_code < 0) + invarg(baud_str, "baud rate"); + } + + /* Trap signals in order to restore tty states upon exit */ + if (!(opt & OPT_e_quit)) { + bb_signals(0 + + (1 << SIGHUP) + + (1 << SIGINT) + + (1 << SIGQUIT) + + (1 << SIGTERM) + , sig_handler); + } + + /* Open tty */ + handle = open(*argv, O_RDWR | O_NDELAY); + if (handle < 0) { + char *buf = concat_path_file("/dev", *argv); + handle = xopen(buf, O_RDWR | O_NDELAY); + /* maybe if (ENABLE_FEATURE_CLEAN_UP) ?? */ + free(buf); + } + + /* Save current tty state */ + save_state(); + + /* Configure tty */ + memcpy(&state, &saved_state, sizeof(state)); + if (!(opt & OPT_m_nonraw)) { /* raw not suppressed */ + memset(&state.c_cc, 0, sizeof(state.c_cc)); + state.c_cc[VMIN] = 1; + state.c_iflag = IGNBRK | IGNPAR; + state.c_oflag = 0; + state.c_lflag = 0; + state.c_cflag = CS8 | HUPCL | CREAD + | ((opt & OPT_L_local) ? CLOCAL : 0) + | ((opt & OPT_F_noflow) ? 0 : CRTSCTS); + cfsetispeed(&state, cfgetispeed(&saved_state)); + cfsetospeed(&state, cfgetospeed(&saved_state)); + } + + if (opt & OPT_s_baud) { + cfsetispeed(&state, baud_code); + cfsetospeed(&state, baud_code); + } + + set_state(&state, encap); + + /* Exit now if option -e was passed */ + if (opt & OPT_e_quit) + return 0; + + /* If we're not requested to watch, just keep descriptor open + * until we are killed */ + if (!(opt & OPT_h_watch)) + while (1) + sleep(24*60*60); + + /* Watch line for hangup */ + while (1) { + if (ioctl(handle, TIOCMGET, &i) < 0 || !(i & TIOCM_CAR)) + goto no_carrier; + sleep(15); + } + + no_carrier: + + /* Execute command on hangup */ + if (opt & OPT_c_extcmd) + system(extcmd); + + /* Restore states and exit */ + restore_state_and_exit(EXIT_SUCCESS); +} diff --git a/networking/tc.c b/networking/tc.c new file mode 100644 index 0000000..6e31074 --- /dev/null +++ b/networking/tc.c @@ -0,0 +1,545 @@ +/* vi: set sw=4 ts=4: */ +/* + * tc.c "tc" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + * Bernhard Reutner-Fischer adjusted for busybox + */ + +#include "libbb.h" + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" +#include "libiproute/rt_names.h" +#include <linux/pkt_sched.h> /* for the TC_H_* macros */ + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +/* nullifies tb on error */ +#define __parse_rtattr_nested_compat(tb, max, rta, len) \ + ({if ((RTA_PAYLOAD(rta) >= len) && \ + (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr))) { \ + rta = RTA_DATA(rta) + RTA_ALIGN(len); \ + parse_rtattr_nested(tb, max, rta); \ + } else \ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); \ + }) + +#define parse_rtattr_nested_compat(tb, max, rta, data, len) \ + ({data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \ + __parse_rtattr_nested_compat(tb, max, rta, len); }) + +#define show_details (0) /* not implemented. Does anyone need it? */ +#define use_iec (0) /* not currently documented in the upstream manpage */ + + +struct globals { + int filter_ifindex; + __u32 filter_qdisc; + __u32 filter_parent; + __u32 filter_prio; + __u32 filter_proto; +}; + +#define G (*(struct globals*)&bb_common_bufsiz1) +#define filter_ifindex (G.filter_ifindex) +#define filter_qdisc (G.filter_qdisc) +#define filter_parent (G.filter_parent) +#define filter_prio (G.filter_prio) +#define filter_proto (G.filter_proto) + +void BUG_tc_globals_too_big(void); +#define INIT_G() do { \ + if (sizeof(G) > COMMON_BUFSIZE) \ + BUG_tc_globals_too_big(); \ +} while (0) + +/* Allocates a buffer containing the name of a class id. + * The caller must free the returned memory. */ +static char* print_tc_classid(uint32_t cid) +{ +#if 0 /* IMPOSSIBLE */ + if (cid == TC_H_ROOT) + return xasprintf("root"); + else +#endif + if (cid == TC_H_UNSPEC) + return xasprintf("none"); + else if (TC_H_MAJ(cid) == 0) + return xasprintf(":%x", TC_H_MIN(cid)); + else if (TC_H_MIN(cid) == 0) + return xasprintf("%x:", TC_H_MAJ(cid)>>16); + else + return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid)); +} + +/* Get a qdisc handle. Return 0 on success, !0 otherwise. */ +static int get_qdisc_handle(__u32 *h, const char *str) { + __u32 maj; + char *p; + + maj = TC_H_UNSPEC; + if (!strcmp(str, "none")) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) + return 1; + maj <<= 16; + if (*p != ':' && *p!=0) + return 1; + ok: + *h = maj; + return 0; +} + +/* Get class ID. Return 0 on success, !0 otherwise. */ +static int get_tc_classid(__u32 *h, const char *str) { + __u32 maj, min; + char *p; + + maj = TC_H_ROOT; + if (!strcmp(str, "root")) + goto ok; + maj = TC_H_UNSPEC; + if (!strcmp(str, "none")) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) { + if (*p != ':') + return 1; + maj = 0; + } + if (*p == ':') { + if (maj >= (1<<16)) + return 1; + maj <<= 16; + str = p + 1; + min = strtoul(str, &p, 16); + if (*p != 0 || min >= (1<<16)) + return 1; + maj |= min; + } else if (*p != 0) + return 1; + ok: + *h = maj; + return 0; +} + +static void print_rate(char *buf, int len, uint32_t rate) +{ + double tmp = (double)rate*8; + + if (use_iec) { + if (tmp >= 1000.0*1024.0*1024.0) + snprintf(buf, len, "%.0fMibit", tmp/1024.0*1024.0); + else if (tmp >= 1000.0*1024) + snprintf(buf, len, "%.0fKibit", tmp/1024); + else + snprintf(buf, len, "%.0fbit", tmp); + } else { + if (tmp >= 1000.0*1000000.0) + snprintf(buf, len, "%.0fMbit", tmp/1000000.0); + else if (tmp >= 1000.0 * 1000.0) + snprintf(buf, len, "%.0fKbit", tmp/1000.0); + else + snprintf(buf, len, "%.0fbit", tmp); + } +} + +/* This is "pfifo_fast". */ +static int prio_parse_opt(int argc, char **argv, struct nlmsghdr *n) +{ + return 0; +} +static int prio_print_opt(struct rtattr *opt) +{ + int i; + struct tc_prio_qopt *qopt; + struct rtattr *tb[TCA_PRIO_MAX+1]; + + if (opt == NULL) + return 0; + parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt, sizeof(*qopt)); + if (tb == NULL) + return 0; + printf("bands %u priomap ", qopt->bands); + for (i=0; i<=TC_PRIO_MAX; i++) + printf(" %d", qopt->priomap[i]); + + if (tb[TCA_PRIO_MQ]) + printf(" multiqueue: o%s ", + *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "n" : "ff"); + + return 0; +} + +/* Class Based Queue */ +static int cbq_parse_opt(int argc, char **argv, struct nlmsghdr *n) +{ + return 0; +} +static int cbq_print_opt(struct rtattr *opt) +{ + struct rtattr *tb[TCA_CBQ_MAX+1]; + struct tc_ratespec *r = NULL; + struct tc_cbq_lssopt *lss = NULL; + struct tc_cbq_wrropt *wrr = NULL; + struct tc_cbq_fopt *fopt = NULL; + struct tc_cbq_ovl *ovl = NULL; + const char * const error = "CBQ: too short %s opt"; + RESERVE_CONFIG_BUFFER(buf, 64); + + if (opt == NULL) + goto done; + parse_rtattr_nested(tb, TCA_CBQ_MAX, opt); + + if (tb[TCA_CBQ_RATE]) { + if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r)) + bb_error_msg(error, "rate"); + else + r = RTA_DATA(tb[TCA_CBQ_RATE]); + } + if (tb[TCA_CBQ_LSSOPT]) { + if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss)) + bb_error_msg(error, "lss"); + else + lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]); + } + if (tb[TCA_CBQ_WRROPT]) { + if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr)) + bb_error_msg(error, "wrr"); + else + wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]); + } + if (tb[TCA_CBQ_FOPT]) { + if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt)) + bb_error_msg(error, "fopt"); + else + fopt = RTA_DATA(tb[TCA_CBQ_FOPT]); + } + if (tb[TCA_CBQ_OVL_STRATEGY]) { + if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl)) + bb_error_msg("CBQ: too short overlimit strategy %u/%u", + (unsigned) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]), + (unsigned) sizeof(*ovl)); + else + ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]); + } + + if (r) { + print_rate(buf, sizeof(buf), r->rate); + printf("rate %s ", buf); + if (show_details) { + printf("cell %ub ", 1<<r->cell_log); + if (r->mpu) + printf("mpu %ub ", r->mpu); + if (r->overhead) + printf("overhead %ub ", r->overhead); + } + } + if (lss && lss->flags) { + bool comma = false; + bb_putchar('('); + if (lss->flags&TCF_CBQ_LSS_BOUNDED) { + printf("bounded"); + comma = true; + } + if (lss->flags&TCF_CBQ_LSS_ISOLATED) { + if (comma) + bb_putchar(','); + printf("isolated"); + } + printf(") "); + } + if (wrr) { + if (wrr->priority != TC_CBQ_MAXPRIO) + printf("prio %u", wrr->priority); + else + printf("prio no-transmit"); + if (show_details) { + printf("/%u ", wrr->cpriority); + if (wrr->weight != 1) { + print_rate(buf, sizeof(buf), wrr->weight); + printf("weight %s ", buf); + } + if (wrr->allot) + printf("allot %ub ", wrr->allot); + } + } + done: + RELEASE_CONFIG_BUFFER(buf); + return 0; +} + +static int print_qdisc(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *hdr, void *arg UNUSED_PARAM) +{ + struct tcmsg *msg = NLMSG_DATA(hdr); + int len = hdr->nlmsg_len; + struct rtattr * tb[TCA_MAX+1]; + char *name; + + if (hdr->nlmsg_type != RTM_NEWQDISC && hdr->nlmsg_type != RTM_DELQDISC) { + /* bb_error_msg("Not a qdisc"); */ + return 0; /* ??? mimic upstream; should perhaps return -1 */ + } + len -= NLMSG_LENGTH(sizeof(*msg)); + if (len < 0) { + /* bb_error_msg("Wrong len %d", len); */ + return -1; + } + /* not the desired interface? */ + if (filter_ifindex && filter_ifindex != msg->tcm_ifindex) + return 0; + memset (tb, 0, sizeof(tb)); + parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len); + if (tb[TCA_KIND] == NULL) { + /* bb_error_msg("%s: NULL kind", "qdisc"); */ + return -1; + } + if (hdr->nlmsg_type == RTM_DELQDISC) + printf("deleted "); + name = (char*)RTA_DATA(tb[TCA_KIND]); + printf("qdisc %s %x: ", name, msg->tcm_handle>>16); + if (filter_ifindex == 0) + printf("dev %s ", ll_index_to_name(msg->tcm_ifindex)); + if (msg->tcm_parent == TC_H_ROOT) + printf("root "); + else if (msg->tcm_parent) { + char *classid = print_tc_classid(msg->tcm_parent); + printf("parent %s ", classid); + if (ENABLE_FEATURE_CLEAN_UP) + free(classid); + } + if (msg->tcm_info != 1) + printf("refcnt %d ", msg->tcm_info); + if (tb[TCA_OPTIONS]) { + static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0"; + int qqq = index_in_strings(_q_, name); + if (qqq == 0) { /* pfifo_fast aka prio */ + prio_print_opt(tb[TCA_OPTIONS]); + } else if (qqq == 1) { /* class based queueing */ + cbq_print_opt(tb[TCA_OPTIONS]); + } else + bb_error_msg("unknown %s", name); + } + bb_putchar('\n'); + return 0; +} + +static int print_class(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *hdr, void *arg UNUSED_PARAM) +{ + struct tcmsg *msg = NLMSG_DATA(hdr); + int len = hdr->nlmsg_len; + struct rtattr * tb[TCA_MAX+1]; + char *name, *classid; + + /*XXX Eventually factor out common code */ + + if (hdr->nlmsg_type != RTM_NEWTCLASS && hdr->nlmsg_type != RTM_DELTCLASS) { + /* bb_error_msg("Not a class"); */ + return 0; /* ??? mimic upstream; should perhaps return -1 */ + } + len -= NLMSG_LENGTH(sizeof(*msg)); + if (len < 0) { + /* bb_error_msg("Wrong len %d", len); */ + return -1; + } + /* not the desired interface? */ + if (filter_qdisc && TC_H_MAJ(msg->tcm_handle^filter_qdisc)) + return 0; + memset (tb, 0, sizeof(tb)); + parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len); + if (tb[TCA_KIND] == NULL) { + /* bb_error_msg("%s: NULL kind", "class"); */ + return -1; + } + if (hdr->nlmsg_type == RTM_DELTCLASS) + printf("deleted "); + + name = (char*)RTA_DATA(tb[TCA_KIND]); + classid = !msg->tcm_handle ? NULL : print_tc_classid( + filter_qdisc ? TC_H_MIN(msg->tcm_parent) : msg->tcm_parent); + printf ("class %s %s", name, classid); + if (ENABLE_FEATURE_CLEAN_UP) + free(classid); + + if (filter_ifindex == 0) + printf("dev %s ", ll_index_to_name(msg->tcm_ifindex)); + if (msg->tcm_parent == TC_H_ROOT) + printf("root "); + else if (msg->tcm_parent) { + classid = print_tc_classid(filter_qdisc ? + TC_H_MIN(msg->tcm_parent) : msg->tcm_parent); + printf("parent %s ", classid); + if (ENABLE_FEATURE_CLEAN_UP) + free(classid); + } + if (msg->tcm_info) + printf("leaf %x ", msg->tcm_info >> 16); + /* Do that get_qdisc_kind(RTA_DATA(tb[TCA_KIND])). */ + if (tb[TCA_OPTIONS]) { + static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0"; + int qqq = index_in_strings(_q_, name); + if (qqq == 0) { /* pfifo_fast aka prio */ + /* nothing. */ /*prio_print_opt(tb[TCA_OPTIONS]);*/ + } else if (qqq == 1) { /* class based queueing */ + /* cbq_print_copt() is identical to cbq_print_opt(). */ + cbq_print_opt(tb[TCA_OPTIONS]); + } else + bb_error_msg("unknown %s", name); + } + bb_putchar('\n'); + + return 0; +} + +static int print_filter(const struct sockaddr_nl *who UNUSED_PARAM, + struct nlmsghdr *hdr, void *arg UNUSED_PARAM) +{ + struct tcmsg *msg = NLMSG_DATA(hdr); + int len = hdr->nlmsg_len; + struct rtattr * tb[TCA_MAX+1]; + return 0; +} + +int tc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int tc_main(int argc UNUSED_PARAM, char **argv) +{ + static const char objects[] ALIGN1 = + "qdisc\0""class\0""filter\0" + ; + enum { OBJ_qdisc = 0, OBJ_class, OBJ_filter }; + static const char commands[] ALIGN1 = + "add\0""delete\0""change\0" + "link\0" /* only qdisc */ + "replace\0" + "show\0""list\0" + ; + static const char args[] ALIGN1 = + "dev\0" /* qdisc, class, filter */ + "root\0" /* class, filter */ + "parent\0" /* class, filter */ + "qdisc\0" /* class */ + "handle\0" /* change: qdisc, class(classid) list: filter */ + "classid\0" /* change: for class use "handle" */ + "preference\0""priority\0""protocol\0" /* filter */ + ; + enum { CMD_add = 0, CMD_del, CMD_change, CMD_link, CMD_replace, CMD_show }; + enum { ARG_dev = 0, ARG_root, ARG_parent, ARG_qdisc, + ARG_handle, ARG_classid, ARG_pref, ARG_prio, ARG_proto}; + struct rtnl_handle rth; + struct tcmsg msg; + int ret, obj, cmd, arg; + char *dev = NULL; + + INIT_G(); + + if (!*++argv) + bb_show_usage(); + xrtnl_open(&rth); + ret = EXIT_SUCCESS; + + obj = index_in_substrings(objects, *argv++); + + if (obj < OBJ_qdisc) + bb_show_usage(); + if (!*argv) + cmd = CMD_show; /* list is the default */ + else { + cmd = index_in_substrings(commands, *argv); + if (cmd < 0) + bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name); + argv++; + } + memset(&msg, 0, sizeof(msg)); + msg.tcm_family = AF_UNSPEC; + ll_init_map(&rth); + while (*argv) { + arg = index_in_substrings(args, *argv); + if (arg == ARG_dev) { + NEXT_ARG(); + if (dev) + duparg2("dev", *argv); + dev = *argv++; + msg.tcm_ifindex = xll_name_to_index(dev); + if (cmd >= CMD_show) + filter_ifindex = msg.tcm_ifindex; + } else if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show) + || (arg == ARG_handle && obj == OBJ_qdisc + && cmd == CMD_change)) { + NEXT_ARG(); + /* We don't care about duparg2("qdisc handle",*argv) for now */ + if (get_qdisc_handle(&filter_qdisc, *argv)) + invarg(*argv, "qdisc"); + } else if (obj != OBJ_qdisc && + (arg == ARG_root + || arg == ARG_parent + || (obj == OBJ_filter && arg >= ARG_pref))) { + } else { + invarg(*argv, "command"); + } + NEXT_ARG(); + if (arg == ARG_root) { + if (msg.tcm_parent) + duparg("parent", *argv); + msg.tcm_parent = TC_H_ROOT; + if (obj == OBJ_filter) + filter_parent = TC_H_ROOT; + } else if (arg == ARG_parent) { + __u32 handle; + if (msg.tcm_parent) + duparg(*argv, "parent"); + if (get_tc_classid(&handle, *argv)) + invarg(*argv, "parent"); + msg.tcm_parent = handle; + if (obj == OBJ_filter) + filter_parent = handle; + } else if (arg == ARG_handle) { /* filter::list */ + if (msg.tcm_handle) + duparg(*argv, "handle"); + /* reject LONG_MIN || LONG_MAX */ + /* TODO: for fw + if ((slash = strchr(handle, '/')) != NULL) + *slash = '\0'; + */ + if (get_u32(&msg.tcm_handle, *argv, 0)) + invarg(*argv, "handle"); + /* if (slash) {if (get_u32(__u32 &mask, slash+1,0)) inv mask;addattr32(n, MAX_MSG, TCA_FW_MASK, mask); */ + } else if (arg == ARG_classid && obj == OBJ_class && cmd == CMD_change){ + } else if (arg == ARG_pref || arg == ARG_prio) { /* filter::list */ + if (filter_prio) + duparg(*argv, "priority"); + if (get_u32(&filter_prio, *argv, 0)) + invarg(*argv, "priority"); + } else if (arg == ARG_proto) { /* filter::list */ + __u16 tmp; + if (filter_proto) + duparg(*argv, "protocol"); + if (ll_proto_a2n(&tmp, *argv)) + invarg(*argv, "protocol"); + filter_proto = tmp; + } + } + if (cmd >= CMD_show) { /* show or list */ + if (obj == OBJ_filter) + msg.tcm_info = TC_H_MAKE(filter_prio<<16, filter_proto); + if (rtnl_dump_request(&rth, obj == OBJ_qdisc ? RTM_GETQDISC : + obj == OBJ_class ? RTM_GETTCLASS : RTM_GETTFILTER, + &msg, sizeof(msg)) < 0) + bb_simple_perror_msg_and_die("cannot send dump request"); + + xrtnl_dump_filter(&rth, obj == OBJ_qdisc ? print_qdisc : + obj == OBJ_class ? print_class : print_filter, + NULL); + } + if (ENABLE_FEATURE_CLEAN_UP) { + rtnl_close(&rth); + } + return ret; +} diff --git a/networking/tcpudp.c b/networking/tcpudp.c new file mode 100644 index 0000000..3b73f21 --- /dev/null +++ b/networking/tcpudp.c @@ -0,0 +1,608 @@ +/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org> + * which are released into public domain by the author. + * Homepage: http://smarden.sunsite.dk/ipsvd/ + * + * Copyright (C) 2007 Denys Vlasenko. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* Based on ipsvd-0.12.1. This tcpsvd accepts all options + * which are supported by one from ipsvd-0.12.1, but not all are + * functional. See help text at the end of this file for details. + * + * Code inside "#ifdef SSLSVD" is for sslsvd and is currently unused. + * + * Busybox version exports TCPLOCALADDR instead of + * TCPLOCALIP + TCPLOCALPORT pair. ADDR more closely matches reality + * (which is "struct sockaddr_XXX". Port is not a separate entity, + * it's just a part of (AF_INET[6]) sockaddr!). + * + * TCPORIGDSTADDR is Busybox-specific addition. + * + * udp server is hacked up by reusing TCP code. It has the following + * limitation inherent in Unix DGRAM sockets implementation: + * - local IP address is retrieved (using recvmsg voodoo) but + * child's socket is not bound to it (bind cannot be called on + * already bound socket). Thus it still can emit outgoing packets + * with wrong source IP... + * - don't know how to retrieve ORIGDST for udp. + */ + +#include "libbb.h" +/* Wants <limits.h> etc, thus included after libbb.h: */ +#include <linux/types.h> /* for __be32 etc */ +#include <linux/netfilter_ipv4.h> + +// TODO: move into this file: +#include "tcpudp_perhost.h" + +#ifdef SSLSVD +#include "matrixSsl.h" +#include "ssl_io.h" +#endif + +struct globals { + unsigned verbose; + unsigned max_per_host; + unsigned cur_per_host; + unsigned cnum; + unsigned cmax; + char **env_cur; + char *env_var[1]; /* actually bigger */ +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define verbose (G.verbose ) +#define max_per_host (G.max_per_host) +#define cur_per_host (G.cur_per_host) +#define cnum (G.cnum ) +#define cmax (G.cmax ) +#define env_cur (G.env_cur ) +#define env_var (G.env_var ) +#define INIT_G() do { \ + cmax = 30; \ + env_cur = &env_var[0]; \ +} while (0) + + +/* We have to be careful about leaking memory in repeated setenv's */ +static void xsetenv_plain(const char *n, const char *v) +{ + char *var = xasprintf("%s=%s", n, v); + *env_cur++ = var; + putenv(var); +} + +static void xsetenv_proto(const char *proto, const char *n, const char *v) +{ + char *var = xasprintf("%s%s=%s", proto, n, v); + *env_cur++ = var; + putenv(var); +} + +static void undo_xsetenv(void) +{ + char **pp = env_cur = &env_var[0]; + while (*pp) { + char *var = *pp; + *strchrnul(var, '=') = '\0'; + unsetenv(var); + free(var); + *pp++ = NULL; + } +} + +static void sig_term_handler(int sig) +{ + if (verbose) + bb_error_msg("got signal %u, exit", sig); + kill_myself_with_sig(sig); +} + +/* Little bloated, but tries to give accurate info how child exited. + * Makes easier to spot segfaulting children etc... */ +static void print_waitstat(unsigned pid, int wstat) +{ + unsigned e = 0; + const char *cause = "?exit"; + + if (WIFEXITED(wstat)) { + cause++; + e = WEXITSTATUS(wstat); + } else if (WIFSIGNALED(wstat)) { + cause = "signal"; + e = WTERMSIG(wstat); + } + bb_error_msg("end %d %s %d", pid, cause, e); +} + +/* Must match getopt32 in main! */ +enum { + OPT_c = (1 << 0), + OPT_C = (1 << 1), + OPT_i = (1 << 2), + OPT_x = (1 << 3), + OPT_u = (1 << 4), + OPT_l = (1 << 5), + OPT_E = (1 << 6), + OPT_b = (1 << 7), + OPT_h = (1 << 8), + OPT_p = (1 << 9), + OPT_t = (1 << 10), + OPT_v = (1 << 11), + OPT_V = (1 << 12), + OPT_U = (1 << 13), /* from here: sslsvd only */ + OPT_slash = (1 << 14), + OPT_Z = (1 << 15), + OPT_K = (1 << 16), +}; + +static void connection_status(void) +{ + /* "only 1 client max" desn't need this */ + if (cmax > 1) + bb_error_msg("status %u/%u", cnum, cmax); +} + +static void sig_child_handler(int sig UNUSED_PARAM) +{ + int wstat; + pid_t pid; + + while ((pid = wait_any_nohang(&wstat)) > 0) { + if (max_per_host) + ipsvd_perhost_remove(pid); + if (cnum) + cnum--; + if (verbose) + print_waitstat(pid, wstat); + } + if (verbose) + connection_status(); +} + +int tcpudpsvd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv) +{ + char *str_C, *str_t; + char *user; + struct hcc *hccp; + const char *instructs; + char *msg_per_host = NULL; + unsigned len_per_host = len_per_host; /* gcc */ +#ifndef SSLSVD + struct bb_uidgid_t ugid; +#endif + bool tcp; + uint16_t local_port; + char *preset_local_hostname = NULL; + char *remote_hostname = remote_hostname; /* for compiler */ + char *remote_addr = remote_addr; /* for compiler */ + len_and_sockaddr *lsa; + len_and_sockaddr local, remote; + socklen_t sa_len; + int pid; + int sock; + int conn; + unsigned backlog = 20; + + INIT_G(); + + tcp = (applet_name[0] == 't'); + + /* 3+ args, -i at most once, -p implies -h, -v is counter, -b N, -c N */ + opt_complementary = "-3:i--i:ph:vv:b+:c+"; +#ifdef SSLSVD + getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:", + &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, + &backlog, &str_t, &ssluser, &root, &cert, &key, &verbose + ); +#else + /* "+": stop on first non-option */ + getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v", + &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, + &backlog, &str_t, &verbose + ); +#endif + if (option_mask32 & OPT_C) { /* -C n[:message] */ + max_per_host = bb_strtou(str_C, &str_C, 10); + if (str_C[0]) { + if (str_C[0] != ':') + bb_show_usage(); + msg_per_host = str_C + 1; + len_per_host = strlen(msg_per_host); + } + } + if (max_per_host > cmax) + max_per_host = cmax; + if (option_mask32 & OPT_u) { + xget_uidgid(&ugid, user); + } +#ifdef SSLSVD + if (option_mask32 & OPT_U) ssluser = optarg; + if (option_mask32 & OPT_slash) root = optarg; + if (option_mask32 & OPT_Z) cert = optarg; + if (option_mask32 & OPT_K) key = optarg; +#endif + argv += optind; + if (!argv[0][0] || LONE_CHAR(argv[0], '0')) + argv[0] = (char*)"0.0.0.0"; + + /* Per-IP flood protection is not thought-out for UDP */ + if (!tcp) + max_per_host = 0; + + bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */ + +#ifdef SSLSVD + sslser = user; + client = 0; + if ((getuid() == 0) && !(option_mask32 & OPT_u)) { + xfunc_exitcode = 100; + bb_error_msg_and_die("-U ssluser must be set when running as root"); + } + if (option_mask32 & OPT_u) + if (!uidgid_get(&sslugid, ssluser, 1)) { + if (errno) { + bb_perror_msg_and_die("can't get user/group: %s", ssluser); + } + bb_error_msg_and_die("unknown user/group %s", ssluser); + } + if (!cert) cert = "./cert.pem"; + if (!key) key = cert; + if (matrixSslOpen() < 0) + fatal("cannot initialize ssl"); + if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) { + if (client) + fatal("cannot read cert, key, or ca file"); + fatal("cannot read cert or key file"); + } + if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0) + fatal("cannot create ssl session"); +#endif + + sig_block(SIGCHLD); + signal(SIGCHLD, sig_child_handler); + bb_signals(BB_FATAL_SIGS, sig_term_handler); + signal(SIGPIPE, SIG_IGN); + + if (max_per_host) + ipsvd_perhost_init(cmax); + + local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0); + lsa = xhost2sockaddr(argv[0], local_port); + argv += 2; + + sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + setsockopt_reuseaddr(sock); + sa_len = lsa->len; /* I presume sockaddr len stays the same */ + xbind(sock, &lsa->u.sa, sa_len); + if (tcp) + xlisten(sock, backlog); + else /* udp: needed for recv_from_to to work: */ + socket_want_pktinfo(sock); + /* ndelay_off(sock); - it is the default I think? */ + +#ifndef SSLSVD + if (option_mask32 & OPT_u) { + /* drop permissions */ + xsetgid(ugid.gid); + xsetuid(ugid.uid); + } +#endif + + if (verbose) { + char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa); + bb_error_msg("listening on %s, starting", addr); + free(addr); +#ifndef SSLSVD + if (option_mask32 & OPT_u) + printf(", uid %u, gid %u", + (unsigned)ugid.uid, (unsigned)ugid.gid); +#endif + } + + /* Main accept() loop */ + + again: + hccp = NULL; + + while (cnum >= cmax) + wait_for_any_sig(); /* expecting SIGCHLD */ + + /* Accept a connection to fd #0 */ + again1: + close(0); + again2: + sig_unblock(SIGCHLD); + local.len = remote.len = sa_len; + if (tcp) { + conn = accept(sock, &remote.u.sa, &remote.len); + } else { + /* In case recv_from_to won't be able to recover local addr. + * Also sets port - recv_from_to is unable to do it. */ + local = *lsa; + conn = recv_from_to(sock, NULL, 0, MSG_PEEK, + &remote.u.sa, &local.u.sa, sa_len); + } + sig_block(SIGCHLD); + if (conn < 0) { + if (errno != EINTR) + bb_perror_msg(tcp ? "accept" : "recv"); + goto again2; + } + xmove_fd(tcp ? conn : sock, 0); + + if (max_per_host) { + /* Drop connection immediately if cur_per_host > max_per_host + * (minimizing load under SYN flood) */ + remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa); + cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp); + if (cur_per_host > max_per_host) { + /* ipsvd_perhost_add detected that max is exceeded + * (and did not store ip in connection table) */ + free(remote_addr); + if (msg_per_host) { + /* don't block or test for errors */ + send(0, msg_per_host, len_per_host, MSG_DONTWAIT); + } + goto again1; + } + /* NB: remote_addr is not leaked, it is stored in conn table */ + } + + if (!tcp) { + /* Voodoo magic: making udp sockets each receive its own + * packets is not trivial, and I still not sure + * I do it 100% right. + * 1) we have to do it before fork() + * 2) order is important - is it right now? */ + + /* Open new non-connected UDP socket for further clients... */ + sock = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0); + setsockopt_reuseaddr(sock); + /* Make plain write/send work for old socket by supplying default + * destination address. This also restricts incoming packets + * to ones coming from this remote IP. */ + xconnect(0, &remote.u.sa, sa_len); + /* hole? at this point we have no wildcard udp socket... + * can this cause clients to get "port unreachable" icmp? + * Yup, time window is very small, but it exists (is it?) */ + /* ..."open new socket", continued */ + xbind(sock, &lsa->u.sa, sa_len); + socket_want_pktinfo(sock); + + /* Doesn't work: + * we cannot replace fd #0 - we will lose pending packet + * which is already buffered for us! And we cannot use fd #1 + * instead - it will "intercept" all following packets, but child + * does not expect data coming *from fd #1*! */ +#if 0 + /* Make it so that local addr is fixed to localp->u.sa + * and we don't accidentally accept packets to other local IPs. */ + /* NB: we possibly bind to the _very_ same_ address & port as the one + * already bound in parent! This seems to work in Linux. + * (otherwise we can move socket to fd #0 only if bind succeeds) */ + close(0); + set_nport(localp, htons(local_port)); + xmove_fd(xsocket(localp->u.sa.sa_family, SOCK_DGRAM, 0), 0); + setsockopt_reuseaddr(0); /* crucial */ + xbind(0, &localp->u.sa, localp->len); +#endif + } + + pid = vfork(); + if (pid == -1) { + bb_perror_msg("vfork"); + goto again; + } + + if (pid != 0) { + /* Parent */ + cnum++; + if (verbose) + connection_status(); + if (hccp) + hccp->pid = pid; + /* clean up changes done by vforked child */ + undo_xsetenv(); + goto again; + } + + /* Child: prepare env, log, and exec prog */ + + /* Closing tcp listening socket */ + if (tcp) + close(sock); + + { /* vfork alert! every xmalloc in this block should be freed! */ + char *local_hostname = local_hostname; /* for compiler */ + char *local_addr = NULL; + char *free_me0 = NULL; + char *free_me1 = NULL; + char *free_me2 = NULL; + + if (verbose || !(option_mask32 & OPT_E)) { + if (!max_per_host) /* remote_addr is not yet known */ + free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa); + if (option_mask32 & OPT_h) { + free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa); + if (!remote_hostname) { + bb_error_msg("cannot look up hostname for %s", remote_addr); + remote_hostname = remote_addr; + } + } + /* Find out local IP peer connected to. + * Errors ignored (I'm not paranoid enough to imagine kernel + * which doesn't know local IP). */ + if (tcp) + getsockname(0, &local.u.sa, &local.len); + /* else: for UDP it is done earlier by parent */ + local_addr = xmalloc_sockaddr2dotted(&local.u.sa); + if (option_mask32 & OPT_h) { + local_hostname = preset_local_hostname; + if (!local_hostname) { + free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa); + if (!local_hostname) + bb_error_msg_and_die("cannot look up hostname for %s", local_addr); + } + /* else: local_hostname is not NULL, but is NOT malloced! */ + } + } + if (verbose) { + pid = getpid(); + if (max_per_host) { + bb_error_msg("concurrency %s %u/%u", + remote_addr, + cur_per_host, max_per_host); + } + bb_error_msg((option_mask32 & OPT_h) + ? "start %u %s-%s (%s-%s)" + : "start %u %s-%s", + pid, + local_addr, remote_addr, + local_hostname, remote_hostname); + } + + if (!(option_mask32 & OPT_E)) { + /* setup ucspi env */ + const char *proto = tcp ? "TCP" : "UDP"; + + /* Extract "original" destination addr:port + * from Linux firewall. Useful when you redirect + * an outbond connection to local handler, and it needs + * to know where it originally tried to connect */ + if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) { + char *addr = xmalloc_sockaddr2dotted(&local.u.sa); + xsetenv_plain("TCPORIGDSTADDR", addr); + free(addr); + } + xsetenv_plain("PROTO", proto); + xsetenv_proto(proto, "LOCALADDR", local_addr); + xsetenv_proto(proto, "REMOTEADDR", remote_addr); + if (option_mask32 & OPT_h) { + xsetenv_proto(proto, "LOCALHOST", local_hostname); + xsetenv_proto(proto, "REMOTEHOST", remote_hostname); + } + //compat? xsetenv_proto(proto, "REMOTEINFO", ""); + /* additional */ + if (cur_per_host > 0) /* can not be true for udp */ + xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host)); + } + free(local_addr); + free(free_me0); + free(free_me1); + free(free_me2); + } + + xdup2(0, 1); + + signal(SIGPIPE, SIG_DFL); /* this one was SIG_IGNed */ + /* Non-ignored signals revert to SIG_DFL on exec anyway */ + /*signal(SIGCHLD, SIG_DFL);*/ + sig_unblock(SIGCHLD); + +#ifdef SSLSVD + strcpy(id, utoa(pid)); + ssl_io(0, argv); +#else + BB_EXECVP(argv[0], argv); +#endif + bb_perror_msg_and_die("exec '%s'", argv[0]); +} + +/* +tcpsvd [-hpEvv] [-c n] [-C n:msg] [-b n] [-u user] [-l name] + [-i dir|-x cdb] [ -t sec] host port prog + +tcpsvd creates a TCP/IP socket, binds it to the address host:port, +and listens on the socket for incoming connections. + +On each incoming connection, tcpsvd conditionally runs a program, +with standard input reading from the socket, and standard output +writing to the socket, to handle this connection. tcpsvd keeps +listening on the socket for new connections, and can handle +multiple connections simultaneously. + +tcpsvd optionally checks for special instructions depending +on the IP address or hostname of the client that initiated +the connection, see ipsvd-instruct(5). + +host + host either is a hostname, or a dotted-decimal IP address, + or 0. If host is 0, tcpsvd accepts connections to any local + IP address. + * busybox accepts IPv6 addresses and host:port pairs too + In this case second parameter is ignored +port + tcpsvd accepts connections to host:port. port may be a name + from /etc/services or a number. +prog + prog consists of one or more arguments. For each connection, + tcpsvd normally runs prog, with file descriptor 0 reading from + the network, and file descriptor 1 writing to the network. + By default it also sets up TCP-related environment variables, + see tcp-environ(5) +-i dir + read instructions for handling new connections from the instructions + directory dir. See ipsvd-instruct(5) for details. + * ignored by busyboxed version +-x cdb + read instructions for handling new connections from the constant database + cdb. The constant database normally is created from an instructions + directory by running ipsvd-cdb(8). + * ignored by busyboxed version +-t sec + timeout. This option only takes effect if the -i option is given. + While checking the instructions directory, check the time of last access + of the file that matches the clients address or hostname if any, discard + and remove the file if it wasn't accessed within the last sec seconds; + tcpsvd does not discard or remove a file if the user's write permission + is not set, for those files the timeout is disabled. Default is 0, + which means that the timeout is disabled. + * ignored by busyboxed version +-l name + local hostname. Do not look up the local hostname in DNS, but use name + as hostname. This option must be set if tcpsvd listens on port 53 + to avoid loops. +-u user[:group] + drop permissions. Switch user ID to user's UID, and group ID to user's + primary GID after creating and binding to the socket. If user is followed + by a colon and a group name, the group ID is switched to the GID of group + instead. All supplementary groups are removed. +-c n + concurrency. Handle up to n connections simultaneously. Default is 30. + If there are n connections active, tcpsvd defers acceptance of a new + connection until an active connection is closed. +-C n[:msg] + per host concurrency. Allow only up to n connections from the same IP + address simultaneously. If there are n active connections from one IP + address, new incoming connections from this IP address are closed + immediately. If n is followed by :msg, the message msg is written + to the client if possible, before closing the connection. By default + msg is empty. See ipsvd-instruct(5) for supported escape sequences in msg. + + For each accepted connection, the current per host concurrency is + available through the environment variable TCPCONCURRENCY. n and msg + can be overwritten by ipsvd(7) instructions, see ipsvd-instruct(5). + By default tcpsvd doesn't keep track of connections. +-h + Look up the client's hostname in DNS. +-p + paranoid. After looking up the client's hostname in DNS, look up the IP + addresses in DNS for that hostname, and forget about the hostname + if none of the addresses match the client's IP address. You should + set this option if you use hostname based instructions. The -p option + implies the -h option. + * ignored by busyboxed version +-b n + backlog. Allow a backlog of approximately n TCP SYNs. On some systems n + is silently limited. Default is 20. +-E + no special environment. Do not set up TCP-related environment variables. +-v + verbose. Print verbose messsages to standard output. +-vv + more verbose. Print more verbose messages to standard output. + * no difference between -v and -vv in busyboxed version +*/ diff --git a/networking/tcpudp_perhost.c b/networking/tcpudp_perhost.c new file mode 100644 index 0000000..3005f12 --- /dev/null +++ b/networking/tcpudp_perhost.c @@ -0,0 +1,65 @@ +/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org> + * which are released into public domain by the author. + * Homepage: http://smarden.sunsite.dk/ipsvd/ + * + * Copyright (C) 2007 Denys Vlasenko. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "tcpudp_perhost.h" + +static struct hcc *cc; +static unsigned cclen; + +/* to be optimized */ + +void ipsvd_perhost_init(unsigned c) +{ +// free(cc); + cc = xzalloc(c * sizeof(*cc)); + cclen = c; +} + +unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp) +{ + unsigned i; + unsigned conn = 1; + int freepos = -1; + + for (i = 0; i < cclen; ++i) { + if (!cc[i].ip) { + freepos = i; + continue; + } + if (strcmp(cc[i].ip, ip) == 0) { + conn++; + continue; + } + } + if (freepos == -1) return 0; + if (conn <= maxconn) { + cc[freepos].ip = ip; + *hccpp = &cc[freepos]; + } + return conn; +} + +void ipsvd_perhost_remove(int pid) +{ + unsigned i; + for (i = 0; i < cclen; ++i) { + if (cc[i].pid == pid) { + free(cc[i].ip); + cc[i].ip = NULL; + cc[i].pid = 0; + return; + } + } +} + +//void ipsvd_perhost_free(void) +//{ +// free(cc); +//} diff --git a/networking/tcpudp_perhost.h b/networking/tcpudp_perhost.h new file mode 100644 index 0000000..2e093c1 --- /dev/null +++ b/networking/tcpudp_perhost.h @@ -0,0 +1,37 @@ +/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org> + * which are released into public domain by the author. + * Homepage: http://smarden.sunsite.dk/ipsvd/ + * + * Copyright (C) 2007 Denys Vlasenko. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +struct hcc { + char *ip; + int pid; +}; + +void ipsvd_perhost_init(unsigned); + +/* Returns number of already opened connects to this ips, including this one. + * ip should be a malloc'ed ptr. + * If return value is <= maxconn, ip is inserted into the table + * and pointer to table entry if stored in *hccpp + * (useful for storing pid later). + * Else ip is NOT inserted (you must take care of it - free() etc) */ +unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp); + +/* Finds and frees element with pid */ +void ipsvd_perhost_remove(int pid); + +//unsigned ipsvd_perhost_setpid(int pid); +//void ipsvd_perhost_free(void); + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif diff --git a/networking/telnet.c b/networking/telnet.c new file mode 100644 index 0000000..5d7ecef --- /dev/null +++ b/networking/telnet.c @@ -0,0 +1,649 @@ +/* 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 <arpa/telnet.h> +#include <netinet/in.h> +#include "libbb.h" + +#ifdef DOTRACE +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +enum { + DATABUFSIZE = 128, + IACBUFSIZE = 128, + + 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, +}; + +typedef unsigned char byte; + +enum { netfd = 3 }; + +struct globals { + int iaclen; /* could even use byte, but it's a loss on x86 */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte do_termios; +#if ENABLE_FEATURE_TELNET_TTYPE + char *ttype; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + const char *autologin; +#endif +#if ENABLE_FEATURE_AUTOWIDTH + unsigned win_width, win_height; +#endif + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + struct termios termios_def; + struct termios termios_raw; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +void BUG_telnet_globals_too_big(void); +#define INIT_G() do { \ + if (sizeof(G) > COMMON_BUFSIZE) \ + BUG_telnet_globals_too_big(); \ + /* memset(&G, 0, sizeof G); - already is */ \ +} while (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); + +static void iacflush(void) +{ + write(netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +#define write_str(fd, str) write(fd, str, sizeof(str) - 1) + +static void doexit(int ev) NORETURN; +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape(void) +{ + char b; + + if (bb_got_signal) /* came from line mode... go raw */ + rawmode(); + + write_str(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(STDIN_FILENO, &b, 1) <= 0) + doexit(EXIT_FAILURE); + + switch (b) { + case 'l': + if (!bb_got_signal) { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (bb_got_signal) { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(EXIT_SUCCESS); + } + + write_str(1, "continuing...\r\n"); + + if (bb_got_signal) + cookmode(); + + rrturn: + bb_got_signal = 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 too. + */ + + 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(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(STDOUT_FILENO, 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); +} + +#if ENABLE_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 + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) + const 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 (*G.autologin) + putiac(*G.autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#if ENABLE_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 + +static char const escapecharis[] ALIGN1 = "\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; + } + 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(); + write_str(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; + + G.telflags ^= UF_SGA; /* toggle */ + if (G.telflags & UF_SGA) + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); +} + +#if ENABLE_FEATURE_TELNET_TTYPE +static void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + + if (G.ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); +} +#endif + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + + if (G.autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); +} +#endif + +#if ENABLE_FEATURE_AUTOWIDTH +static void to_naws(void) +{ + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); +} +#endif + +static void telopt(byte c) +{ + switch (c) { + case TELOPT_ECHO: + to_echo(); break; + case TELOPT_SGA: + to_sga(); break; +#if ENABLE_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: + to_ttype(); break; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: + to_new_environ(); break; +#endif +#if ENABLE_FEATURE_AUTOWIDTH + case TELOPT_NAWS: + to_naws(); + putiac_naws(c, G.win_width, G.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; +#if ENABLE_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE, G.ttype); +#endif +#if ENABLE_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 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); +} + +/* poll gives smaller (-70 bytes) code */ +#define USE_POLL 1 + +int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int telnet_main(int argc UNUSED_PARAM, char **argv) +{ + char *host; + int port; + int len; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + + INIT_G(); + +#if ENABLE_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &G.win_width, &G.win_height); +#endif + +#if ENABLE_FEATURE_TELNET_TTYPE + G.ttype = getenv("TERM"); +#endif + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + if (1 & getopt32(argv, "al:", &G.autologin)) + G.autologin = getenv("USER"); + argv += optind; +#else + argv++; +#endif + if (!*argv) + bb_show_usage(); + host = *argv++; + port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); + if (*argv) /* extra params?? */ + bb_show_usage(); + + xmove_fd(create_and_connect_stream_or_die(host, port), netfd); + + setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + + signal(SIGINT, record_signo); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + FD_SET(netfd, &readfds); + maxfd = 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 (bb_got_signal) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(STDIN_FILENO, &rfds)) +#endif + { + len = read(STDIN_FILENO, G.buf, DATABUFSIZE); + if (len <= 0) + doexit(EXIT_SUCCESS); + 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(netfd, &rfds)) +#endif + { + len = read(netfd, G.buf, DATABUFSIZE); + if (len <= 0) { + write_str(1, "Connection closed by foreign host\r\n"); + doexit(EXIT_FAILURE); + } + TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); + handlenetinput(len); + } + } + } /* while (1) */ +} diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 0000000..46dfb31 --- /dev/null +++ b/networking/telnetd.c @@ -0,0 +1,610 @@ +/* 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 0 + +#include "libbb.h" +#include <syslog.h> + +#if DEBUG +#define TELCMDS +#define TELOPTS +#endif +#include <arpa/telnet.h> + +/* 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;*/ +/*#define TS_BUF1 ts->buf1*/ +/*#define TS_BUF2 TS_BUF2*/ +#define TS_BUF1 ((unsigned char*)(ts + 1)) +#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE) + int rdidx1, wridx1, size1; + int rdidx2, wridx2, size2; +}; + +/* Two buffers are directly after tsession in malloced memory. + * Make whole thing fit in 4k */ +enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 }; + + +/* Globals */ +static int maxfd; +static struct tsession *sessions; +static const char *loginpath = "/bin/login"; +static const char *issuefile = "/etc/issue.net"; + + +/* + Remove all IAC's from buf1 (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 buf. + + 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. + + NB: may fail to remove iacs which wrap around buffer! + */ +static unsigned char * +remove_iacs(struct tsession *ts, int *pnum_totty) +{ + unsigned char *ptr0 = TS_BUF1 + ts->wridx1; + unsigned char *ptr = ptr0; + unsigned char *totty = ptr; + unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); + int num_totty; + + while (ptr < end) { + if (*ptr != IAC) { + char c = *ptr; + + *totty++ = c; + ptr++; + /* We map \r\n ==> \r for pragmatic reasons. + * Many client implementations send \r\n when + * the user hits the CarriageReturn key. + */ + if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0')) + ptr++; + continue; + } + + if ((ptr+1) >= end) + break; + if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */ + ptr += 2; + continue; + } + if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */ + *totty++ = ptr[1]; + ptr += 2; + continue; + } + + /* + * 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 + */ + 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; + continue; + } + /* 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; + } + + num_totty = totty - ptr0; + *pnum_totty = num_totty; + /* the difference between ptr and totty is number of iacs + we removed from the stream. Adjust buf1 accordingly. */ + if ((ptr - totty) == 0) /* 99.999% of cases */ + return ptr0; + ts->wridx1 += ptr - totty; + ts->size1 -= ptr - totty; + /* move chars meant for the terminal towards the end of the buffer */ + return memmove(ptr - num_totty, ptr0, num_totty); +} + + +static struct tsession * +make_new_session( + USE_FEATURE_TELNETD_STANDALONE(int sock) + SKIP_FEATURE_TELNETD_STANDALONE(void) +) { + const char *login_argv[2]; + struct termios termbuf; + int fd, pid; + char tty_name[GETPTY_BUFSIZE]; + 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 = xgetpty(tty_name); + if (fd > maxfd) + maxfd = fd; + ts->ptyfd = fd; + ndelay_on(fd); +#if ENABLE_FEATURE_TELNETD_STANDALONE + ts->sockfd_read = sock; + /* SO_KEEPALIVE by popular demand */ + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + ndelay_on(sock); + if (!sock) { /* We are called with fd 0 - we are in inetd mode */ + sock++; /* so use fd 1 for output */ + ndelay_on(sock); + } + ts->sockfd_write = sock; + if (sock > maxfd) + maxfd = sock; +#else + /* SO_KEEPALIVE by popular demand */ + setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + /* ts->sockfd_read = 0; - done by xzalloc */ + ts->sockfd_write = 1; + 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. */ + { + static const char iacs_to_send[] ALIGN1 = { + IAC, DO, TELOPT_ECHO, + IAC, DO, TELOPT_NAWS, + IAC, DO, TELOPT_LFLOW, + IAC, WILL, TELOPT_ECHO, + IAC, WILL, TELOPT_SGA + }; + memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send)); + ts->rdidx2 = sizeof(iacs_to_send); + ts->size2 = sizeof(iacs_to_send); + } + + fflush(NULL); /* flush all streams */ + pid = vfork(); /* NOMMU-friendly */ + if (pid < 0) { + free(ts); + close(fd); + /* sock will be closed by caller */ + bb_perror_msg("vfork"); + return NULL; + } + if (pid > 0) { + /* Parent */ + ts->shell_pid = pid; + return ts; + } + + /* Child */ + /* Careful - we are after vfork! */ + + /* make new session and process group */ + setsid(); + + /* Restore default signal handling */ + bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL); + + /* open the child's side of the tty. */ + /* NB: setsid() disconnects from any previous ctty's. Therefore + * we must open child's side of the tty AFTER setsid! */ + close(0); + xopen(tty_name, O_RDWR); /* becomes our ctty */ + xdup2(0, 1); + xdup2(0, 2); + tcsetpgrp(0, getpid()); /* switch this tty's process group to us */ + + /* 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_stdin_TCSANOW(&termbuf); + + /* Uses FILE-based I/O to stdout, but does fflush(stdout), + * so should be safe with vfork. + * I fear, though, that some users will have ridiculously big + * issue files, and they may block writing to fd 1, + * (parent is supposed to read it, but parent waits + * for vforked child to exec!) */ + print_login_issue(issuefile, tty_name); + + /* Exec shell / login / whatever */ + login_argv[0] = loginpath; + login_argv[1] = NULL; + /* exec busybox applet (if PREFER_APPLETS=y), if that fails, + * exec external program */ + BB_EXECVP(loginpath, (char **)login_argv); + /* _exit is safer with vfork, and we shouldn't send message + * to remote clients anyway */ + _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/ +} + +/* Must match getopt32 string */ +enum { + OPT_WATCHCHILD = (1 << 2), /* -K */ + OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */ + OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */ + OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */ +}; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + +static void +free_session(struct tsession *ts) +{ + struct tsession *t = sessions; + + if (option_mask32 & OPT_INETD) + exit(EXIT_SUCCESS); + + /* 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; + } + +#if 0 + /* It was said that "normal" telnetd just closes ptyfd, + * doesn't send SIGKILL. When we close ptyfd, + * kernel sends SIGHUP to processes having slave side opened. */ + kill(ts->shell_pid, SIGKILL); + wait4(ts->shell_pid, NULL, 0, NULL); +#endif + close(ts->ptyfd); + close(ts->sockfd_read); + /* We do not need to close(ts->sockfd_write), it's the same + * as sockfd_read unless we are in inetd mode. But in inetd mode + * we do not reach this */ + free(ts); + + /* Scan all sessions and find new maxfd */ + maxfd = 0; + ts = sessions; + while (ts) { + if (maxfd < ts->ptyfd) + maxfd = ts->ptyfd; + if (maxfd < ts->sockfd_read) + maxfd = ts->sockfd_read; +#if 0 + /* Again, sockfd_write == sockfd_read here */ + if (maxfd < ts->sockfd_write) + maxfd = ts->sockfd_write; +#endif + ts = ts->next; + } +} + +#else /* !FEATURE_TELNETD_STANDALONE */ + +/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */ +#define free_session(ts) return 0 + +#endif + +static void handle_sigchld(int sig UNUSED_PARAM) +{ + pid_t pid; + struct tsession *ts; + + /* Looping: more than one child may have exited */ + while (1) { + pid = wait_any_nohang(NULL); + if (pid <= 0) + break; + ts = sessions; + while (ts) { + if (ts->shell_pid == pid) { + ts->shell_pid = -1; + break; + } + ts = ts->next; + } + } +} + +int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int telnetd_main(int argc UNUSED_PARAM, char **argv) +{ + fd_set rdfdset, wrfdset; + unsigned opt; + int count; + struct tsession *ts; +#if ENABLE_FEATURE_TELNETD_STANDALONE +#define IS_INETD (opt & OPT_INETD) + int master_fd = master_fd; /* be happy, gcc */ + unsigned portnbr = 23; + char *opt_bindaddr = NULL; + char *opt_portnbr; +#else + enum { + IS_INETD = 1, + master_fd = -1, + portnbr = 23, + }; +#endif + /* Even if !STANDALONE, we accept (and ignore) -i, thus people + * don't need to guess whether it's ok to pass -i to us */ + opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"), + &issuefile, &loginpath + USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); + if (!IS_INETD /*&& !re_execed*/) { + /* inform that we start in standalone mode? + * May be useful when people forget to give -i */ + /*bb_error_msg("listening for connections");*/ + if (!(opt & OPT_FOREGROUND)) { + /* DAEMON_CHDIR_ROOT was giving inconsistent + * behavior with/without -F, -i */ + bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv); + } + } + /* Redirect log to syslog early, if needed */ + if (IS_INETD || !(opt & OPT_FOREGROUND)) { + openlog(applet_name, 0, LOG_USER); + logmode = LOGMODE_SYSLOG; + } + USE_FEATURE_TELNETD_STANDALONE( + if (opt & OPT_PORT) + portnbr = xatou16(opt_portnbr); + ); + + /* Used to check access(loginpath, X_OK) here. Pointless. + * exec will do this for us for free later. */ + +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (IS_INETD) { + sessions = make_new_session(0); + if (!sessions) /* pty opening or vfork problem, exit */ + return 1; /* make_new_session prints error message */ + } else { + master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); + xlisten(master_fd, 1); + } +#else + sessions = make_new_session(); + if (!sessions) /* pty opening or vfork problem, exit */ + return 1; /* make_new_session prints error message */ +#endif + + /* We don't want to die if just one session is broken */ + signal(SIGPIPE, SIG_IGN); + + if (opt & OPT_WATCHCHILD) + signal(SIGCHLD, handle_sigchld); + else /* prevent dead children from becoming zombies */ + signal(SIGCHLD, SIG_IGN); + +/* + 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-- +----------+ + + size1: "how many bytes are buffered for pty between rdidx1 and wridx1?" + size2: "how many bytes are buffered for socket between rdidx2 and wridx2?" + + Each session has got two buffers. Buffers are circular. If sizeN == 0, + buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases + rdidxN == wridxN. +*/ + again: + FD_ZERO(&rdfdset); + FD_ZERO(&wrfdset); + + /* Select on the master socket, all telnet sockets and their + * ptys if there is room in their session buffers. + * NB: scalability problem: we recalculate entire bitmap + * before each select. Can be a problem with 500+ connections. */ + ts = sessions; + while (ts) { + struct tsession *next = ts->next; /* in case we free ts. */ + if (ts->shell_pid == -1) { + /* Child died and we detected that */ + free_session(ts); + } else { + 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 = next; + } + if (!IS_INETD) { + FD_SET(master_fd, &rdfdset); + /* This is needed because free_session() does not + * take master_fd into account when it finds new + * maxfd among remaining fd's */ + if (master_fd > maxfd) + maxfd = master_fd; + } + + count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL); + if (count < 0) + goto again; /* EINTR or ENOMEM */ + +#if ENABLE_FEATURE_TELNETD_STANDALONE + /* First check for and accept new sessions. */ + if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { + int fd; + struct tsession *new_ts; + + fd = accept(master_fd, NULL, NULL); + if (fd < 0) + goto again; + /* Create a new session and link it into our active list */ + new_ts = make_new_session(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; + unsigned char *ptr; + /* Write to pty from buffer 1. */ + ptr = remove_iacs(ts, &num_totty); + count = safe_write(ts->ptyfd, ptr, num_totty); + if (count < 0) { + if (errno == EAGAIN) + goto skip1; + goto kill_session; + } + ts->size1 -= count; + ts->wridx1 += count; + if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ + ts->wridx1 = 0; + } + skip1: + if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) { + /* Write to socket from buffer 2. */ + count = MIN(BUFSIZE - ts->wridx2, ts->size2); + count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count); + if (count < 0) { + if (errno == EAGAIN) + goto skip2; + goto kill_session; + } + ts->size2 -= count; + ts->wridx2 += count; + if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */ + ts->wridx2 = 0; + } + skip2: + /* Should not be needed, but... remove_iacs is actually buggy + * (it cannot process iacs which wrap around buffer's end)! + * Since properly fixing it requires writing bigger code, + * we rely instead on this code making it virtually impossible + * to have wrapped iac (people don't type at 2k/second). + * It also allows for bigger reads in common case. */ + if (ts->size1 == 0) { + ts->rdidx1 = 0; + ts->wridx1 = 0; + } + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } + + if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) { + /* Read from socket to buffer 1. */ + count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); + count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count); + if (count <= 0) { + if (count < 0 && errno == EAGAIN) + goto skip3; + goto kill_session; + } + /* Ignore trailing NUL if it is there */ + if (!TS_BUF1[ts->rdidx1 + count - 1]) { + --count; + } + ts->size1 += count; + ts->rdidx1 += count; + if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */ + ts->rdidx1 = 0; + } + skip3: + if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) { + /* Read from pty to buffer 2. */ + count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); + count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count); + if (count <= 0) { + if (count < 0 && errno == EAGAIN) + goto skip4; + goto kill_session; + } + ts->size2 += count; + ts->rdidx2 += count; + if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */ + ts->rdidx2 = 0; + } + skip4: + ts = next; + continue; + kill_session: + free_session(ts); + ts = next; + } + + goto again; +} diff --git a/networking/tftp.c b/networking/tftp.c new file mode 100644 index 0000000..1f70685 --- /dev/null +++ b/networking/tftp.c @@ -0,0 +1,752 @@ +/* vi: set sw=4 ts=4: */ +/* ------------------------------------------------------------------------- + * tftp.c + * + * A simple tftp client/server 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> + * + * tftpd added by Denys Vlasenko & Vladimir Dronnikov + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ + +#include "libbb.h" + +#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT + +#define TFTP_BLKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_BLKSIZE_DEFAULT_STR "512" +#define TFTP_TIMEOUT_MS 50 +#define TFTP_MAXTIMEOUT_MS 2000 +#define TFTP_NUM_RETRIES 12 /* number of backed-off retries */ + +/* 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 + +/* error codes sent over network (we use only 0, 1, 3 and 8) */ +/* generic (error message is included in the packet) */ +#define ERR_UNSPEC 0 +#define ERR_NOFILE 1 +#define ERR_ACCESS 2 +/* disk full or allocation exceeded */ +#define ERR_WRITE 3 +#define ERR_OP 4 +#define ERR_BAD_ID 5 +#define ERR_EXIST 6 +#define ERR_BAD_USER 7 +#define ERR_BAD_OPT 8 + +/* masks coming from getopt32 */ +enum { + TFTP_OPT_GET = (1 << 0), + TFTP_OPT_PUT = (1 << 1), + /* pseudo option: if set, it's tftpd */ + TFTPD_OPT = (1 << 7) * ENABLE_TFTPD, + TFTPD_OPT_r = (1 << 8) * ENABLE_TFTPD, + TFTPD_OPT_c = (1 << 9) * ENABLE_TFTPD, + TFTPD_OPT_u = (1 << 10) * ENABLE_TFTPD, +}; + +#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT +#define USE_GETPUT(...) +#define CMD_GET(cmd) 1 +#define CMD_PUT(cmd) 0 +#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT +#define USE_GETPUT(...) +#define CMD_GET(cmd) 0 +#define CMD_PUT(cmd) 1 +#else +#define USE_GETPUT(...) __VA_ARGS__ +#define CMD_GET(cmd) ((cmd) & TFTP_OPT_GET) +#define CMD_PUT(cmd) ((cmd) & TFTP_OPT_PUT) +#endif +/* NB: in the code below + * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive + */ + + +struct globals { + /* u16 TFTP_ERROR; u16 reason; both network-endian, then error text: */ + uint8_t error_pkt[4 + 32]; + char *user_opt; + /* used in tftpd_main(), a bit big for stack: */ + char block_buf[TFTP_BLKSIZE_DEFAULT]; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define block_buf (G.block_buf ) +#define user_opt (G.user_opt ) +#define error_pkt (G.error_pkt ) +#define INIT_G() do { } while (0) + +#define error_pkt_reason (error_pkt[3]) +#define error_pkt_str (error_pkt + 4) + + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + +static int tftp_blksize_check(const char *blksize_str, int maxsize) +{ + /* Check if the blksize is valid: + * RFC2348 says between 8 and 65464, + * but our implementation makes it impossible + * to use blksizes smaller than 22 octets. */ + unsigned blksize = bb_strtou(blksize_str, NULL, 10); + if (errno + || (blksize < 24) || (blksize > maxsize) + ) { + bb_error_msg("bad blocksize '%s'", blksize_str); + return -1; + } +#if ENABLE_TFTP_DEBUG + bb_error_msg("using blksize %u", blksize); +#endif + return blksize; +} + +static char *tftp_get_option(const char *option, char *buf, int len) +{ + int opt_val = 0; + int opt_found = 0; + int k; + + /* buf points to: + * "opt_name<NUL>opt_val<NUL>opt_name2<NUL>opt_val2<NUL>..." */ + + while (len > 0) { + /* Make sure options are terminated correctly */ + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + goto nul_found; + } + } + return NULL; + nul_found: + if (opt_val == 0) { /* it's "name" part */ + 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_protocol( + len_and_sockaddr *our_lsa, + len_and_sockaddr *peer_lsa, + const char *local_file + USE_TFTP(, const char *remote_file) + USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, void *tsize)) + USE_FEATURE_TFTP_BLOCKSIZE(, int blksize)) +{ +#if !ENABLE_TFTP +#define remote_file NULL +#endif +#if !(ENABLE_FEATURE_TFTP_BLOCKSIZE && ENABLE_TFTPD) +#define tsize NULL +#endif +#if !ENABLE_FEATURE_TFTP_BLOCKSIZE + enum { blksize = TFTP_BLKSIZE_DEFAULT }; +#endif + + struct pollfd pfd[1]; +#define socket_fd (pfd[0].fd) + int len; + int send_len; + USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;) + smallint finished = 0; + uint16_t opcode; + uint16_t block_nr; + uint16_t recv_blk; + int open_mode, local_fd; + int retries, waittime_ms; + int io_bufsize = blksize + 4; + char *cp; + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ + /* We must keep the transmit and receive buffers seperate */ + /* In case we rcv a garbage pkt and we need to rexmit the last pkt */ + char *xbuf = xmalloc(io_bufsize); + char *rbuf = xmalloc(io_bufsize); + + socket_fd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0); + setsockopt_reuseaddr(socket_fd); + + block_nr = 1; + cp = xbuf + 2; + + if (!ENABLE_TFTP || our_lsa) { + /* tftpd */ + + /* Create a socket which is: + * 1. bound to IP:port peer sent 1st datagram to, + * 2. connected to peer's IP:port + * This way we will answer from the IP:port peer + * expects, will not get any other packets on + * the socket, and also plain read/write will work. */ + xbind(socket_fd, &our_lsa->u.sa, our_lsa->len); + xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len); + + /* Is there an error already? Send pkt and bail out */ + if (error_pkt_reason || error_pkt_str[0]) + goto send_err_pkt; + + if (CMD_GET(option_mask32)) { + /* it's upload - we must ACK 1st packet (with filename) + * as if it's "block 0" */ + block_nr = 0; + } + + if (user_opt) { + struct passwd *pw = getpwnam(user_opt); + if (!pw) + bb_error_msg_and_die("unknown user %s", user_opt); + change_identity(pw); /* initgroups, setgid, setuid */ + } + } + + /* Open local file (must be after changing user) */ + if (CMD_PUT(option_mask32)) { + open_mode = O_RDONLY; + } else { + open_mode = O_WRONLY | O_TRUNC | O_CREAT; +#if ENABLE_TFTPD + if ((option_mask32 & (TFTPD_OPT+TFTPD_OPT_c)) == TFTPD_OPT) { + /* tftpd without -c */ + open_mode = O_WRONLY | O_TRUNC; + } +#endif + } + if (!(option_mask32 & TFTPD_OPT)) { + local_fd = CMD_GET(option_mask32) ? STDOUT_FILENO : STDIN_FILENO; + if (NOT_LONE_DASH(local_file)) + local_fd = xopen(local_file, open_mode); + } else { + local_fd = open(local_file, open_mode); + if (local_fd < 0) { + error_pkt_reason = ERR_NOFILE; + strcpy((char*)error_pkt_str, "can't open file"); + goto send_err_pkt; + } + } + + if (!ENABLE_TFTP || our_lsa) { +/* gcc 4.3.1 would NOT optimize it out as it should! */ +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (blksize != TFTP_BLKSIZE_DEFAULT || tsize) { + /* Create and send OACK packet. */ + /* For the download case, block_nr is still 1 - + * we expect 1st ACK from peer to be for (block_nr-1), + * that is, for "block 0" which is our OACK pkt */ + opcode = TFTP_OACK; + goto add_blksize_opt; + } +#endif + } else { +/* Removing it, or using if() statement instead of #if may lead to + * "warning: null argument where non-null required": */ +#if ENABLE_TFTP + /* tftp */ + + /* We can't (and don't really need to) bind the socket: + * we don't know from which local IP datagrams will be sent, + * but kernel will pick the same IP every time (unless routing + * table is changed), thus peer will see dgrams consistently + * coming from the same IP. + * We would like to connect the socket, but since peer's + * UDP code can be less perfect than ours, _peer's_ IP:port + * in replies may differ from IP:port we used to send + * our first packet. We can connect() only when we get + * first reply. */ + + /* build opcode */ + opcode = TFTP_WRQ; + if (CMD_GET(option_mask32)) { + opcode = TFTP_RRQ; + } + /* add filename and mode */ + /* fill in packet if the filename fits into xbuf */ + len = strlen(remote_file) + 1; + if (2 + len + sizeof("octet") >= io_bufsize) { + bb_error_msg("remote filename is too long"); + goto ret; + } + strcpy(cp, remote_file); + cp += len; + /* add "mode" part of the package */ + strcpy(cp, "octet"); + cp += sizeof("octet"); + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (blksize == TFTP_BLKSIZE_DEFAULT) + goto send_pkt; + + /* Non-standard blocksize: add option to pkt */ + if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN")) { + bb_error_msg("remote filename is too long"); + goto ret; + } + want_option_ack = 1; +#endif +#endif /* ENABLE_TFTP */ + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + add_blksize_opt: +#if ENABLE_TFTPD + if (tsize) { + struct stat st; + /* add "tsize", <nul>, size, <nul> */ + strcpy(cp, "tsize"); + cp += sizeof("tsize"); + fstat(local_fd, &st); + cp += snprintf(cp, 10, "%u", (int) st.st_size) + 1; + } +#endif + if (blksize != TFTP_BLKSIZE_DEFAULT) { + /* add "blksize", <nul>, blksize, <nul> */ + strcpy(cp, "blksize"); + cp += sizeof("blksize"); + cp += snprintf(cp, 6, "%d", blksize) + 1; + } +#endif + /* First packet is built, so skip packet generation */ + goto send_pkt; + } + + /* Using mostly goto's - continue/break will be less clear + * in where we actually jump to */ + while (1) { + /* Build ACK or DATA */ + cp = xbuf + 2; + *((uint16_t*)cp) = htons(block_nr); + cp += 2; + block_nr++; + opcode = TFTP_ACK; + if (CMD_PUT(option_mask32)) { + opcode = TFTP_DATA; + len = full_read(local_fd, cp, blksize); + if (len < 0) { + goto send_read_err_pkt; + } + if (len != blksize) { + finished = 1; + } + cp += len; + } + send_pkt: + /* Send packet */ + *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */ + send_len = cp - xbuf; + /* NB: send_len value is preserved in code below + * for potential resend */ + + retries = TFTP_NUM_RETRIES; /* re-initialize */ + waittime_ms = TFTP_TIMEOUT_MS; + + send_again: +#if ENABLE_TFTP_DEBUG + fprintf(stderr, "sending %u bytes\n", send_len); + for (cp = xbuf; cp < &xbuf[send_len]; cp++) + fprintf(stderr, "%02x ", (unsigned char) *cp); + fprintf(stderr, "\n"); +#endif + xsendto(socket_fd, xbuf, send_len, &peer_lsa->u.sa, peer_lsa->len); + /* Was it final ACK? then exit */ + if (finished && (opcode == TFTP_ACK)) + goto ret; + + recv_again: + /* Receive packet */ + /*pfd[0].fd = socket_fd;*/ + pfd[0].events = POLLIN; + switch (safe_poll(pfd, 1, waittime_ms)) { + default: + /*bb_perror_msg("poll"); - done in safe_poll */ + goto ret; + case 0: + retries--; + if (retries == 0) { + bb_error_msg("timeout"); + goto ret; /* no err packet sent */ + } + + /* exponential backoff with limit */ + waittime_ms += waittime_ms/2; + if (waittime_ms > TFTP_MAXTIMEOUT_MS) { + waittime_ms = TFTP_MAXTIMEOUT_MS; + } + + goto send_again; /* resend last sent pkt */ + case 1: + if (!our_lsa) { + /* tftp (not tftpd!) receiving 1st packet */ + our_lsa = ((void*)(ptrdiff_t)-1); /* not NULL */ + len = recvfrom(socket_fd, rbuf, io_bufsize, 0, + &peer_lsa->u.sa, &peer_lsa->len); + /* Our first dgram went to port 69 + * but reply may come from different one. + * Remember and use this new port (and IP) */ + if (len >= 0) + xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len); + } else { + /* tftpd, or not the very first packet: + * socket is connect()ed, can just read from it. */ + /* Don't full_read()! + * This is not TCP, one read == one pkt! */ + len = safe_read(socket_fd, rbuf, io_bufsize); + } + if (len < 0) { + goto send_read_err_pkt; + } + if (len < 4) { /* too small? */ + goto recv_again; + } + } + + /* Process recv'ed packet */ + opcode = ntohs( ((uint16_t*)rbuf)[0] ); + recv_blk = ntohs( ((uint16_t*)rbuf)[1] ); +#if ENABLE_TFTP_DEBUG + fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk); +#endif + if (opcode == TFTP_ERROR) { + static const char errcode_str[] ALIGN1 = + "\0" + "file not found\0" + "access violation\0" + "disk full\0" + "bad operation\0" + "unknown transfer id\0" + "file already exists\0" + "no such user\0" + "bad option"; + + const char *msg = ""; + + if (len > 4 && rbuf[4] != '\0') { + msg = &rbuf[4]; + rbuf[io_bufsize - 1] = '\0'; /* paranoia */ + } else if (recv_blk <= 8) { + msg = nth_string(errcode_str, recv_blk); + } + bb_error_msg("server error: (%u) %s", recv_blk, msg); + goto ret; + } + +#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_get_option("blksize", &rbuf[2], len - 2); + if (res) { + blksize = tftp_blksize_check(res, blksize); + if (blksize < 0) { + error_pkt_reason = ERR_BAD_OPT; + goto send_err_pkt; + } + io_bufsize = blksize + 4; + /* Send ACK for OACK ("block" no: 0) */ + block_nr = 0; + continue; + } + /* rfc2347: + * "An option not acknowledged by the server + * must be ignored by the client and server + * as if it were never requested." */ + } + bb_error_msg("server only supports blocksize of 512"); + blksize = TFTP_BLKSIZE_DEFAULT; + io_bufsize = TFTP_BLKSIZE_DEFAULT + 4; + } +#endif + /* block_nr is already advanced to next block# we expect + * to get / block# we are about to send next time */ + + if (CMD_GET(option_mask32) && (opcode == TFTP_DATA)) { + if (recv_blk == block_nr) { + int sz = full_write(local_fd, &rbuf[4], len - 4); + if (sz != len - 4) { + strcpy((char*)error_pkt_str, bb_msg_write_error); + error_pkt_reason = ERR_WRITE; + goto send_err_pkt; + } + if (sz != blksize) { + finished = 1; + } + continue; /* send ACK */ + } + if (recv_blk == (block_nr - 1)) { + /* Server lost our TFTP_ACK. Resend it */ + block_nr = recv_blk; + continue; + } + } + + if (CMD_PUT(option_mask32) && (opcode == TFTP_ACK)) { + /* did peer ACK our last DATA pkt? */ + if (recv_blk == (uint16_t) (block_nr - 1)) { + if (finished) + goto ret; + continue; /* send next block */ + } + } + /* Awww... recv'd packet is not recognized! */ + goto recv_again; + /* why recv_again? - rfc1123 says: + * "The sender (i.e., the side originating the DATA packets) + * must never resend the current DATA packet on receipt + * of a duplicate ACK". + * DATA pkts are resent ONLY on timeout. + * Thus "goto send_again" will ba a bad mistake above. + * See: + * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome + */ + } /* end of "while (1)" */ + ret: + if (ENABLE_FEATURE_CLEAN_UP) { + close(local_fd); + close(socket_fd); + free(xbuf); + free(rbuf); + } + return finished == 0; /* returns 1 on failure */ + + send_read_err_pkt: + strcpy((char*)error_pkt_str, bb_msg_read_error); + send_err_pkt: + if (error_pkt_str[0]) + bb_error_msg((char*)error_pkt_str); + error_pkt[1] = TFTP_ERROR; + xsendto(socket_fd, error_pkt, 4 + 1 + strlen((char*)error_pkt_str), + &peer_lsa->u.sa, peer_lsa->len); + return EXIT_FAILURE; +#undef remote_file +#undef tsize +} + +#if ENABLE_TFTP + +int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int tftp_main(int argc UNUSED_PARAM, char **argv) +{ + len_and_sockaddr *peer_lsa; + const char *local_file = NULL; + const char *remote_file = NULL; +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + const char *blksize_str = TFTP_BLKSIZE_DEFAULT_STR; + int blksize; +#endif + int result; + int port; + USE_GETPUT(int opt;) + + INIT_G(); + + /* -p or -g is mandatory, and they are mutually exclusive */ + opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:") + USE_GETPUT("g--p:p--g:"); + + USE_GETPUT(opt =) getopt32(argv, + USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p") + "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"), + &local_file, &remote_file + USE_FEATURE_TFTP_BLOCKSIZE(, &blksize_str)); + argv += optind; + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + /* Check if the blksize is valid: + * RFC2348 says between 8 and 65464 */ + blksize = tftp_blksize_check(blksize_str, 65564); + if (blksize < 0) { + //bb_error_msg("bad block size"); + return EXIT_FAILURE; + } +#endif + + if (!local_file) + local_file = remote_file; + if (!remote_file) + remote_file = local_file; + /* Error if filename or host is not known */ + if (!remote_file || !argv[0]) + bb_show_usage(); + + port = bb_lookup_port(argv[1], "udp", 69); + peer_lsa = xhost2sockaddr(argv[0], port); + +#if ENABLE_TFTP_DEBUG + fprintf(stderr, "using server '%s', remote_file '%s', local_file '%s'\n", + xmalloc_sockaddr2dotted(&peer_lsa->u.sa), + remote_file, local_file); +#endif + + result = tftp_protocol( + NULL /*our_lsa*/, peer_lsa, + local_file, remote_file + USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, NULL /*tsize*/)) + USE_FEATURE_TFTP_BLOCKSIZE(, blksize) + ); + + if (result != EXIT_SUCCESS && NOT_LONE_DASH(local_file) && CMD_GET(opt)) { + unlink(local_file); + } + return result; +} + +#endif /* ENABLE_TFTP */ + +#if ENABLE_TFTPD + +/* TODO: libbb candidate? */ +static len_and_sockaddr *get_sock_lsa(int s) +{ + len_and_sockaddr *lsa; + socklen_t len = 0; + + if (getsockname(s, NULL, &len) != 0) + return NULL; + lsa = xzalloc(LSA_LEN_SIZE + len); + lsa->len = len; + getsockname(s, &lsa->u.sa, &lsa->len); + return lsa; +} + +int tftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int tftpd_main(int argc UNUSED_PARAM, char **argv) +{ + len_and_sockaddr *our_lsa; + len_and_sockaddr *peer_lsa; + char *local_file, *mode; + const char *error_msg; + int opt, result, opcode; + USE_FEATURE_TFTP_BLOCKSIZE(int blksize = TFTP_BLKSIZE_DEFAULT;) + USE_FEATURE_TFTP_BLOCKSIZE(char *tsize = NULL;) + + INIT_G(); + + our_lsa = get_sock_lsa(STDIN_FILENO); + if (!our_lsa) { + /* This is confusing: + *bb_error_msg_and_die("stdin is not a socket"); + * Better: */ + bb_show_usage(); + /* Help text says that tftpd must be used as inetd service, + * which is by far the most usual cause of get_sock_lsa + * failure */ + } + peer_lsa = xzalloc(LSA_LEN_SIZE + our_lsa->len); + peer_lsa->len = our_lsa->len; + + /* Shifting to not collide with TFTP_OPTs */ + opt = option_mask32 = TFTPD_OPT | (getopt32(argv, "rcu:", &user_opt) << 8); + argv += optind; + if (argv[0]) + xchdir(argv[0]); + + result = recv_from_to(STDIN_FILENO, block_buf, sizeof(block_buf), + 0 /* flags */, + &peer_lsa->u.sa, &our_lsa->u.sa, our_lsa->len); + + error_msg = "malformed packet"; + opcode = ntohs(*(uint16_t*)block_buf); + if (result < 4 || result >= sizeof(block_buf) + || block_buf[result-1] != '\0' + || (USE_FEATURE_TFTP_PUT(opcode != TFTP_RRQ) /* not download */ + USE_GETPUT(&&) + USE_FEATURE_TFTP_GET(opcode != TFTP_WRQ) /* not upload */ + ) + ) { + goto err; + } + local_file = block_buf + 2; + if (local_file[0] == '.' || strstr(local_file, "/.")) { + error_msg = "dot in file name"; + goto err; + } + mode = local_file + strlen(local_file) + 1; + if (mode >= block_buf + result || strcmp(mode, "octet") != 0) { + goto err; + } +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + { + char *res; + char *opt_str = mode + sizeof("octet"); + int opt_len = block_buf + result - opt_str; + if (opt_len > 0) { + res = tftp_get_option("blksize", opt_str, opt_len); + if (res) { + blksize = tftp_blksize_check(res, 65564); + if (blksize < 0) { + error_pkt_reason = ERR_BAD_OPT; + /* will just send error pkt */ + goto do_proto; + } + } + /* did client ask us about file size? */ + tsize = tftp_get_option("tsize", opt_str, opt_len); + } + } +#endif + + if (!ENABLE_FEATURE_TFTP_PUT || opcode == TFTP_WRQ) { + if (opt & TFTPD_OPT_r) { + /* This would mean "disk full" - not true */ + /*error_pkt_reason = ERR_WRITE;*/ + error_msg = bb_msg_write_error; + goto err; + } + USE_GETPUT(option_mask32 |= TFTP_OPT_GET;) /* will receive file's data */ + } else { + USE_GETPUT(option_mask32 |= TFTP_OPT_PUT;) /* will send file's data */ + } + + /* NB: if error_pkt_str or error_pkt_reason is set up, + * tftp_protocol() just sends one error pkt and returns */ + + do_proto: + close(STDIN_FILENO); /* close old, possibly wildcard socket */ + /* tftp_protocol() will create new one, bound to particular local IP */ + result = tftp_protocol( + our_lsa, peer_lsa, + local_file USE_TFTP(, NULL /*remote_file*/) + USE_FEATURE_TFTP_BLOCKSIZE(, tsize) + USE_FEATURE_TFTP_BLOCKSIZE(, blksize) + ); + + return result; + err: + strcpy((char*)error_pkt_str, error_msg); + goto do_proto; +} + +#endif /* ENABLE_TFTPD */ + +#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */ diff --git a/networking/traceroute.c b/networking/traceroute.c new file mode 100644 index 0000000..29cebfa --- /dev/null +++ b/networking/traceroute.c @@ -0,0 +1,1349 @@ +/* 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. + */ + +/* + * 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 <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 "libbb.h" +#include "inet_common.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; + uint32_t *addrs; +}; + +/* Data section of the probe packet */ +typedef struct outdata { + unsigned char seq; /* sequence number of this packet */ + unsigned char ttl; /* ttl packet left with */ +// UNUSED. Retaining to have the same packet size. + struct timeval tv_UNUSED PACKED; /* time packet left */ +} outdata_t; + +struct IFADDRLIST { + uint32_t addr; + char device[sizeof(struct ifreq)]; +}; + + +/* Keep in sync with getopt32 call! */ +#define OPT_DONT_FRAGMNT (1<<0) /* F */ +#define OPT_USE_ICMP (1<<1) /* I */ +#define OPT_TTL_FLAG (1<<2) /* l */ +#define OPT_ADDR_NUM (1<<3) /* n */ +#define OPT_BYPASS_ROUTE (1<<4) /* r */ +#define OPT_DEBUG (1<<5) /* d */ +#define OPT_VERBOSE (1<<6) /* v */ +#define OPT_IP_CHKSUM (1<<7) /* x */ +#define OPT_TOS (1<<8) /* t */ +#define OPT_DEVICE (1<<9) /* i */ +#define OPT_MAX_TTL (1<<10) /* m */ +#define OPT_PORT (1<<11) /* p */ +#define OPT_NPROBES (1<<12) /* q */ +#define OPT_SOURCE (1<<13) /* s */ +#define OPT_WAITTIME (1<<14) /* w */ +#define OPT_PAUSE_MS (1<<15) /* z */ +#define OPT_FIRST_TTL (1<<16) /* f */ + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +/* use icmp echo instead of udp packets */ +#define useicmp (option_mask32 & OPT_USE_ICMP) +#endif +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +#define verbose (option_mask32 & OPT_VERBOSE) +#endif +#define nflag (option_mask32 & OPT_ADDR_NUM) + + +struct globals { + struct ip *outip; /* last output (udp) packet */ + struct udphdr *outudp; /* last output (udp) packet */ + struct outdata *outdata; /* last output (udp) packet */ + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + struct icmp *outicmp; /* last output (icmp) packet */ +#endif + + int rcvsock; /* receive (icmp) socket file descriptor */ + int sndsock; /* send (udp/icmp) socket file descriptor */ + + int packlen; /* total length of packet */ + int minpacket; /* min ip packet size */ + int maxpacket; // 32 * 1024; /* max ip packet size */ + int pmtu; /* Path MTU Discovery (RFC1191) */ + + char *hostname; + + uint16_t ident; + uint16_t port; // 32768 + 666; /* start udp dest port # for probe packets */ + + int waittime; // 5; /* time to wait for response (in seconds) */ + int doipcksum; // 1; /* calculate ip checksums by default */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + int optlen; /* length of ip options */ +#else +#define optlen 0 +#endif + + struct sockaddr_storage whereto; /* Who to try to reach */ + struct sockaddr_storage wherefrom; /* Who we are */ + /* last inbound (icmp) packet */ + unsigned char packet[512]; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + /* Maximum number of gateways (include room for one noop) */ +#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(uint32_t))) + /* loose source route gateway list (including room for final destination) */ + uint32_t gwlist[NGATEWAYS + 1]; +#endif +}; + +#define G (*ptr_to_globals) +#define outip (G.outip ) +#define outudp (G.outudp ) +#define outdata (G.outdata ) +#define outicmp (G.outicmp ) +#define rcvsock (G.rcvsock ) +#define sndsock (G.sndsock ) +#define packlen (G.packlen ) +#define minpacket (G.minpacket) +#define maxpacket (G.maxpacket) +#define pmtu (G.pmtu ) +#define hostname (G.hostname ) +#define ident (G.ident ) +#define port (G.port ) +#define waittime (G.waittime ) +#define doipcksum (G.doipcksum) +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +#define optlen (G.optlen ) +#endif +#define packet (G.packet ) +#define whereto (G.whereto ) +#define wherefrom (G.wherefrom) +#define gwlist (G.gwlist ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + maxpacket = 32 * 1024; \ + port = 32768 + 666; \ + waittime = 5; \ + doipcksum = 1; \ +} while (0) + + +/* + * Return the interface list + */ +static int +ifaddrlist(struct IFADDRLIST **ipaddrp) +{ + enum { IFREQ_BUFSIZE = (32 * 1024) / sizeof(struct ifreq) }; + + 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 ifr; + /* Was on stack, but 32k is a bit too much: */ + struct ifreq *ibuf = xmalloc(IFREQ_BUFSIZE * sizeof(ibuf[0])); + struct IFADDRLIST *st_ifaddrlist; + + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + ifc.ifc_len = IFREQ_BUFSIZE * sizeof(ibuf[0]); + ifc.ifc_buf = (caddr_t)ibuf; + + if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 + || ifc.ifc_len < (int)sizeof(struct ifreq) + ) { + if (errno == EINVAL) + bb_error_msg_and_die( + "SIOCGIFCONF: ifreq struct too small (%u bytes)", + (unsigned)(IFREQ_BUFSIZE * sizeof(ibuf[0]))); + 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 + ioctl_or_perror_and_die(fd, SIOCGIFADDR, (char *)&ifr, + "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"); + + free(ibuf); + close(fd); + *ipaddrp = st_ifaddrlist; + return nipaddr; +} + + +static void +setsin(struct sockaddr_in *addr_sin, uint32_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; + uint32_t mask; + uint32_t dest, tmask; + struct IFADDRLIST *al; + char buf[256], tdevice[256], device[256]; + + f = xfopen_for_read("/proc/net/route"); + + /* 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; + i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x", + tdevice, &dest, &tmask); + if (i != 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]" + +*/ + +static int +wait_for_reply(int sock, struct sockaddr_in *fromp) +{ + struct pollfd pfd[1]; + int cc = 0; + socklen_t fromlen = sizeof(*fromp); + + pfd[0].fd = sock; + pfd[0].events = POLLIN; + if (safe_poll(pfd, 1, waittime * 1000) > 0) + cc = recvfrom(sock, packet, sizeof(packet), 0, + (struct sockaddr *)fromp, &fromlen); + return cc; +} + +/* + * Checksum routine for Internet Protocol family headers (C Version) + */ +static uint16_t +in_cksum(uint16_t *addr, int len) +{ + int nleft = len; + uint16_t *w = addr; + uint16_t 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) +{ + 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((uint16_t *)outip, sizeof(*outip) + optlen); + if (outip->ip_sum == 0) + outip->ip_sum = 0xffff; + } + + /* Payload */ + outdata->seq = seq; + outdata->ttl = ttl; +// UNUSED: was storing gettimeofday's result there, but never ever checked it + /*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((uint16_t *)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((uint16_t *)ui, packlen); + if (outudp->check == 0) + outudp->check = 0xffff; + *outip = tip; + } + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + /* XXX undocumented debugging hack */ + if (verbose > 1) { + const uint16_t *sp; + int nshorts, i; + + sp = (uint16_t *)outip; + nshorts = (unsigned)packlen / sizeof(uint16_t); + 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 = xsendto(sndsock, (char *)outip, + packlen, (struct sockaddr *)&whereto, sizeof(whereto)); + if (cc != packlen) { + bb_info_msg("wrote %s %d chars, ret=%d", hostname, packlen, cc); + } +} + +#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 + +#if !ENABLE_FEATURE_TRACEROUTE_VERBOSE +#define packet_ok(buf, cc, from, seq) \ + packet_ok(buf, cc, seq) +#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; + uint32_t *lp = (uint32_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 void +print_inetname(struct sockaddr_in *from) +{ + const char *ina; + + ina = inet_ntoa(from->sin_addr); + if (nflag) + printf(" %s", ina); + else { + char *n = NULL; + if (from->sin_addr.s_addr != INADDR_ANY) + n = xmalloc_sockaddr2host_noport((struct sockaddr*)from); + printf(" %s (%s)", (n ? n : ina), ina); + free(n); + } +} + +static 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; + + print_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; + uint32_t addr, *ap; + + hi = xzalloc(sizeof(*hi)); + addr = inet_addr(host); + if (addr != 0xffffffff) { + 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); + free(hi->addrs); + free(hi); +} + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static void +getaddr(uint32_t *ap, const char *host) +{ + struct hostinfo *hi; + + hi = gethostinfo(host); + *ap = hi->addrs[0]; + freehostinfo(hi); +} +#endif + +static void +print_delta_ms(unsigned t1p, unsigned t2p) +{ + unsigned tt = t2p - t1p; + printf(" %u.%03u ms", tt/1000, tt%1000); +} + +int traceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int traceroute_main(int argc, char **argv) +{ + int code, n; + unsigned char *outp; + uint32_t *ap; + struct sockaddr_in *from; + struct sockaddr_in *to; + struct hostinfo *hi; + int ttl, probe, i; + int seq = 0; + int tos = 0; + char *tos_str; + char *source; + unsigned op; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + int lsrr = 0; +#endif + uint16_t off = 0; + struct IFADDRLIST *al; + char *device; + int max_ttl = 30; + char *max_ttl_str; + char *port_str; + int nprobes = 3; + char *nprobes_str; + char *waittime_str; + unsigned pausemsecs = 0; + char *pausemsecs_str; + int first_ttl = 1; + char *first_ttl_str; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + llist_t *source_route_list = NULL; +#endif + + INIT_G(); + from = (struct sockaddr_in *)&wherefrom; + to = (struct sockaddr_in *)&whereto; + + //opterr = 0; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + opt_complementary = "x-x:g::"; +#else + opt_complementary = "x-x"; +#endif + + op = getopt32(argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:" +#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 + , &source_route_list +#endif + ); + + if (op & OPT_DONT_FRAGMNT) + off = IP_DF; + if (op & OPT_IP_CHKSUM) { + doipcksum = 0; + bb_error_msg("warning: ip checksums disabled"); + } + if (op & OPT_TOS) + tos = xatou_range(tos_str, 0, 255); + if (op & OPT_MAX_TTL) + max_ttl = xatou_range(max_ttl_str, 1, 255); + if (op & OPT_PORT) + port = xatou16(port_str); + if (op & OPT_NPROBES) + nprobes = xatou_range(nprobes_str, 1, INT_MAX); + if (op & OPT_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 (op & OPT_WAITTIME) + waittime = xatou_range(waittime_str, 2, 24 * 60 * 60); + if (op & OPT_PAUSE_MS) + pausemsecs = xatou_range(pausemsecs_str, 0, 60 * 60 * 1000); + if (op & OPT_FIRST_TTL) + first_ttl = xatou_range(first_ttl_str, 1, 255); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + if (source_route_list) { + while (source_route_list) { + if (lsrr >= NGATEWAYS) + bb_error_msg_and_die("no more than %d gateways", NGATEWAYS); + getaddr(gwlist + lsrr, llist_pop(&source_route_list)); + ++lsrr; + } + 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(); + } + + /* Ensure the socket fds won't be 0, 1 or 2 */ + bb_sanitize_stdio(); + + rcvsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + +#if TRACEROUTE_SO_DEBUG + if (op & OPT_DEBUG) + setsockopt(rcvsock, SOL_SOCKET, SO_DEBUG, + &const_int_1, sizeof(const_int_1)); +#endif + if (op & OPT_BYPASS_ROUTE) + setsockopt(rcvsock, SOL_SOCKET, SO_DONTROUTE, + &const_int_1, sizeof(const_int_1)); + + 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, &packlen, sizeof(packlen)) < 0) { + bb_perror_msg_and_die("SO_SNDBUF"); + } +#endif +#ifdef IP_HDRINCL + if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, &const_int_1, sizeof(const_int_1)) < 0 + && errno != ENOPROTOOPT + ) { + bb_perror_msg_and_die("IP_HDRINCL"); + } +#else +#ifdef IP_TOS + if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) { + bb_perror_msg_and_die("setsockopt tos %d", tos); + } +#endif +#endif +#if TRACEROUTE_SO_DEBUG + if (op & OPT_DEBUG) + setsockopt(sndsock, SOL_SOCKET, SO_DEBUG, + &const_int_1, sizeof(const_int_1)); +#endif + if (op & OPT_BYPASS_ROUTE) + setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE, + &const_int_1, sizeof(const_int_1)); + + /* Revert to non-privileged user after opening sockets */ + xsetgid(getgid()); + xsetuid(getuid()); + + outip = 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 = (outdata_t *)(outp + 8); /* XXX magic number */ + } else +#endif + { + outip->ip_p = IPPROTO_UDP; + outudp = (struct udphdr *)outp; + outudp->source = htons(ident); + outudp->len = htons((uint16_t)(packlen - (sizeof(*outip) + optlen))); + outdata = (outdata_t *)(outudp + 1); + } + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Look for a specific device */ + if (op & OPT_DEVICE) { + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + goto found_dev; + bb_error_msg_and_die("can't find interface %s", device); + } + found_dev: + + /* Determine our source address */ + if (!(op & OPT_SOURCE)) { + /* + * If a device was specified, use the interface address. + * Otherwise, try to determine our source address. + */ + if (op & OPT_DEVICE) + 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 (op & OPT_DEVICE) { + for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap) + if (*ap == al->addr) + goto found_dev2; + bb_error_msg_and_die("%s is not on interface %s", + source, device); + found_dev2: + 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 + + printf("traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr)); + if (op & OPT_SOURCE) + printf(" from %s", source); + printf(", %d hops max, %d byte packets\n", max_ttl, packlen); + fflush(stdout); + + for (ttl = first_ttl; ttl <= max_ttl; ++ttl) { + uint32_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; + unsigned t1; + unsigned t2; + struct ip *ip; + + if (sentfirst && pausemsecs > 0) + usleep(pausemsecs * 1000); + t1 = monotonic_us(); + send_probe(++seq, ttl); + ++sentfirst; + while ((cc = wait_for_reply(rcvsock, from)) != 0) { + t2 = monotonic_us(); + 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; + } + print_delta_ms(t1, t2); + ip = (struct ip *)packet; + if (op & OPT_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); + } + bb_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..d4b76e1 --- /dev/null +++ b/networking/udhcp/Config.in @@ -0,0 +1,122 @@ +# +# 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. + +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. + +config FEATURE_UDHCPD_WRITE_LEASES_EARLY + bool "Rewrite the lease file at every new acknowledge" + default n + depends on APP_UDHCPD + help + If selected, udhcpd will write a new file with leases every + time a new lease has been accepted, thus eliminating the need + to send SIGUSR1 for the initial writing or updating. Any timed + rewriting remains undisturbed + +config DHCPD_LEASES_FILE + string "Absolute path to lease file" + default "/var/lib/misc/udhcpd.leases" + depends on APP_UDHCPD + help + udhcpd stores addresses in a lease file. This is the absolute path + of the file. Normally it is safe to leave it untouched. + +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 + runs a script when a lease is obtained or lost. + +config FEATURE_UDHCPC_ARPING + bool "Verify that the offered address is free, using ARP ping" + default y + depends on APP_UDHCPC + help + If selected, udhcpc will send ARP probes and make sure + the offered address is really not in use by anyone. The client + will DHCPDECLINE the offer if the address is in use, + and restart the discover process. + +config FEATURE_UDHCP_PORT + bool "Enable '-P port' option for udhcpd and udhcpc" + default n + depends on APP_UDHCPD || APP_UDHCPC + help + At the cost of ~300 bytes, enables -P port option. + This feature is typically not needed. + +config 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. + +config FEATURE_UDHCP_RFC3397 + bool "Support for RFC3397 domain search (experimental)" + default n + depends on APP_UDHCPD || APP_UDHCPC + help + If selected, both client and server will support passing of domain + search lists via option 119, specified in RFC3397. + +config UDHCPC_DEFAULT_SCRIPT + string "Absolute path to config script" + default "/usr/share/udhcpc/default.script" + depends on APP_UDHCPC + help + This script is called after udhcpc receives an answer. See + examples/udhcp for a working example. Normally it is safe + to leave this untouched. + +config UDHCPC_SLACK_FOR_BUGGY_SERVERS + int "DHCP options slack buffer size" + default 80 + range 0 924 + depends on APP_UDHCPD || APP_UDHCPC + help + Some buggy DHCP servers send DHCP offer packets with option + field larger than we expect (which might also be considered a + buffer overflow attempt). These packets are normally discarded. + If circumstances beyond your control force you to support such + servers, this may help. The upper limit (924) makes dhcpc accept + even 1500 byte packets (maximum-sized ethernet packets). + + This option does not make dhcp[cd] emit non-standard + sized packets. + + Known buggy DHCP servers: + 3Com OfficeConnect Remote 812 ADSL Router: + seems to confuse maximum allowed UDP packet size with + maximum size of entire IP packet, and sends packets which are + 28 bytes too large. + Seednet (ISP) VDSL: sends packets 2 bytes too large. diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild new file mode 100644 index 0000000..e938076 --- /dev/null +++ b/networking/udhcp/Kbuild @@ -0,0 +1,25 @@ +# 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 \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPD) += common.o options.o packet.o \ + signalpipe.o socket.o + +lib-$(CONFIG_APP_UDHCPC) += dhcpc.o clientpacket.o clientsocket.o \ + script.o + +UDHCPC_NEEDS_ARPING-$(CONFIG_FEATURE_UDHCPC_ARPING) = y +lib-$(UDHCPC_NEEDS_ARPING-y) += arpping.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 +lib-$(CONFIG_FEATURE_UDHCP_RFC3397) += domain_codec.o diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c new file mode 100644 index 0000000..e0710dc --- /dev/null +++ b/networking/udhcp/arpping.c @@ -0,0 +1,116 @@ +/* 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 */ + uint8_t h_dest[6]; /* 00 destination ether addr */ + uint8_t h_source[6]; /* 06 source ether addr */ + uint16_t h_proto; /* 0c packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* 0e hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* 10 protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* 12 hardware address length (must be 6) */ + uint8_t plen; /* 13 protocol address length (must be 4) */ + uint16_t operation; /* 14 ARP opcode */ + uint8_t sHaddr[6]; /* 16 sender's hardware address */ + uint8_t sInaddr[4]; /* 1c sender's IP address */ + uint8_t tHaddr[6]; /* 20 target's hardware address */ + uint8_t tInaddr[4]; /* 26 target's IP address */ + uint8_t pad[18]; /* 2a pad for min. ethernet payload (60 bytes) */ +} PACKED; + +enum { + ARP_MSG_SIZE = 0x2a +}; + + +/* Returns 1 if no reply received */ + +int FAST_FUNC arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface) +{ + int timeout_ms; + struct pollfd pfd[1]; +#define s (pfd[0].fd) /* socket */ + int rv = 1; /* "no reply received" yet */ + struct sockaddr addr; /* for interface name */ + struct arpMsg arp; + + 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 enable bcast on raw socket"); + goto ret; + } + + /* send arp request */ + memset(&arp, 0, sizeof(arp)); + memset(arp.h_dest, 0xff, 6); /* MAC DA */ + memcpy(arp.h_source, from_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.sHaddr, from_mac, 6); /* source hardware address */ + memcpy(arp.sInaddr, &from_ip, sizeof(from_ip)); /* source IP address */ + /* tHaddr is zero-fiiled */ /* target hardware address */ + memcpy(arp.tInaddr, &test_ip, sizeof(test_ip)); /* target IP address */ + + memset(&addr, 0, sizeof(addr)); + safe_strncpy(addr.sa_data, interface, sizeof(addr.sa_data)); + if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) { + // TODO: error message? caller didn't expect us to fail, + // just returning 1 "no reply received" misleads it. + goto ret; + } + + /* wait for arp reply, and check it */ + timeout_ms = 2000; + do { + int r; + unsigned prevTime = monotonic_us(); + + pfd[0].events = POLLIN; + r = safe_poll(pfd, 1, timeout_ms); + if (r < 0) + break; + if (r) { + r = read(s, &arp, sizeof(arp)); + if (r < 0) + break; + if (r >= ARP_MSG_SIZE + && arp.operation == htons(ARPOP_REPLY) + /* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */ + /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */ + && *((uint32_t *) arp.sInaddr) == test_ip + ) { + rv = 0; + break; + } + } + timeout_ms -= ((unsigned)monotonic_us() - prevTime) / 1000; + } while (timeout_ms > 0); + + ret: + close(s); + DEBUG("%srp reply received for this address", rv ? "No a" : "A"); + return rv; +} diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c new file mode 100644 index 0000000..3f9522f --- /dev/null +++ b/networking/udhcp/clientpacket.c @@ -0,0 +1,271 @@ +/* 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 (defined(__GLIBC__) && __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 */ +uint32_t FAST_FUNC random_xid(void) +{ + static smallint initialized; + + if (!initialized) { + srand(monotonic_us()); + initialized = 1; + } + 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); + if ((type != DHCPDECLINE) && (type != DHCPRELEASE)) + 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_param_req_option(struct dhcpMessage *packet) +{ + uint8_t c; + int end = end_option(packet->options); + int i, len = 0; + + for (i = 0; (c = dhcp_options[i].code) != 0; i++) { + if (((dhcp_options[i].flags & OPTION_REQ) + && !client_config.no_default_options) + || (client_config.opt_mask[c >> 3] & (1 << (c & 7))) + ) { + packet->options[end + OPT_DATA + len] = c; + len++; + } + } + if (len) { + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + } +} + +/* RFC 2131 + * 4.4.4 Use of broadcast and unicast + * + * The DHCP client broadcasts DHCPDISCOVER, DHCPREQUEST and DHCPINFORM + * messages, unless the client knows the address of a DHCP server. + * The client unicasts DHCPRELEASE messages to the server. Because + * the client is declining the use of the IP address supplied by the server, + * the client broadcasts DHCPDECLINE messages. + * + * When the DHCP client knows the address of a DHCP server, in either + * INIT or REBOOTING state, the client may use that address + * in the DHCPDISCOVER or DHCPREQUEST rather than the IP broadcast address. + * The client may also use unicast to send DHCPINFORM messages + * to a known DHCP server. If the client receives no response to DHCP + * messages sent to the IP address of a known DHCP server, the DHCP + * client reverts to using the IP broadcast address. + */ + +static int raw_bcast_from_client_config_ifindex(struct dhcpMessage *packet) +{ + return udhcp_send_raw_packet(packet, + /*src*/ INADDR_ANY, CLIENT_PORT, + /*dst*/ INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR, + client_config.ifindex); +} + + +#if ENABLE_FEATURE_UDHCPC_ARPING +/* Broadcast a DHCP decline message */ +int FAST_FUNC send_decline(uint32_t xid, uint32_t server, uint32_t requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDECLINE); + packet.xid = xid; + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + bb_info_msg("Sending decline..."); + + return raw_bcast_from_client_config_ifindex(&packet); +} +#endif + +/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ +int FAST_FUNC send_discover(uint32_t xid, uint32_t requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDISCOVER); + packet.xid = xid; + if (requested) + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + + /* Explicitly saying that we want RFC-compliant packets helps + * some buggy DHCP servers to NOT send bigger packets */ + add_simple_option(packet.options, DHCP_MAX_SIZE, htons(576)); + + add_param_req_option(&packet); + + bb_info_msg("Sending discover..."); + return raw_bcast_from_client_config_ifindex(&packet); +} + + +/* Broadcasts a DHCP request message */ +/* RFC 2131 3.1 paragraph 3: + * "The client _broadcasts_ a DHCPREQUEST message..." + */ +int FAST_FUNC send_select(uint32_t xid, uint32_t server, uint32_t 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_param_req_option(&packet); + + addr.s_addr = requested; + bb_info_msg("Sending select for %s...", inet_ntoa(addr)); + return raw_bcast_from_client_config_ifindex(&packet); +} + + +/* Unicasts or broadcasts a DHCP renew message */ +int FAST_FUNC send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + packet.ciaddr = ciaddr; + + add_param_req_option(&packet); + bb_info_msg("Sending renew..."); + if (server) + return udhcp_send_kernel_packet(&packet, + ciaddr, CLIENT_PORT, + server, SERVER_PORT); + + return raw_bcast_from_client_config_ifindex(&packet); +} + + +/* Unicasts a DHCP release message */ +int FAST_FUNC send_release(uint32_t server, uint32_t ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPRELEASE); + packet.xid = random_xid(); + packet.ciaddr = ciaddr; + + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + bb_info_msg("Sending release..."); + return udhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); +} + + +/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */ +int FAST_FUNC udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd) +{ + int bytes; + struct udp_dhcp_packet packet; + uint16_t check; + + memset(&packet, 0, sizeof(packet)); + bytes = safe_read(fd, &packet, sizeof(packet)); + if (bytes < 0) { + DEBUG("Cannot read on raw listening socket - ignoring"); + /* NB: possible down interface, etc. Caller should pause. */ + return bytes; /* returns -1 */ + } + + if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) { + DEBUG("Packet is too short, ignoring"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + /* packet is bigger than sizeof(packet), we did partial read */ + DEBUG("Oversized packet, ignoring"); + 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(packet) - can't happen */ + || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) + ) { + DEBUG("Unrelated/bogus packet"); + return -2; + } + + /* verify 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 -2; + } + + /* verify UDP checksum. IP header has to be modified for this */ + memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); + /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ + packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ + check = packet.udp.check; + packet.udp.check = 0; + 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 (payload->cookie != htonl(DHCP_MAGIC)) { + bb_error_msg("received bogus message (bad magic), ignoring"); + return -2; + } + DEBUG("Got valid DHCP packet"); + 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..1dcc105 --- /dev/null +++ b/networking/udhcp/clientsocket.c @@ -0,0 +1,108 @@ +/* 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> +#include <asm/types.h> +#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include <netpacket/packet.h> +#include <net/ethernet.h> +#else +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#endif +#include <linux/filter.h> + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" + +int FAST_FUNC udhcp_raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + /* + * Comment: + * + * I've selected not to see LL header, so BPF doesn't see it, too. + * The filter may also pass non-IP and non-ARP packets, but we do + * a more complete check when receiving the message in userspace. + * + * and filter shamelessly stolen from: + * + * http://www.flamewarmaster.de/software/dhcpclient/ + * + * There are a few other interesting ideas on that page (look under + * "Motivation"). Use of netlink events is most interesting. Think + * of various network servers listening for events and reconfiguring. + * That would obsolete sending HUP signals and/or make use of restarts. + * + * Copyright: 2006, 2007 Stefan Rompf <sux@loplof.de>. + * License: GPL v2. + * + * TODO: make conditional? + */ +#define SERVER_AND_CLIENT_PORTS ((67 << 16) + 68) + static const struct sock_filter filter_instr[] = { + /* check for udp */ + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0), /* L5, L1, is UDP? */ + /* ugly check for arp on ethernet-like and IPv4 */ + BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), /* L1: */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4), /* L3, L4 */ + /* skip IP header */ + BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* L5: */ + /* check udp source and destination ports */ + BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1), /* L3, L4 */ + /* returns */ + BPF_STMT(BPF_RET|BPF_K, 0x0fffffff ), /* L3: pass */ + BPF_STMT(BPF_RET|BPF_K, 0), /* L4: reject */ + }; + static const struct sock_fprog filter_prog = { + .len = sizeof(filter_instr) / sizeof(filter_instr[0]), + /* casting const away: */ + .filter = (struct sock_filter *) filter_instr, + }; + + DEBUG("opening raw socket on ifindex %d", ifindex); + + fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + DEBUG("got raw socket fd %d", fd); + + if (SERVER_PORT == 67 && CLIENT_PORT == 68) { + /* Use only if standard ports are in use */ + /* Ignoring error (kernel may lack support for this) */ + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, + sizeof(filter_prog)) >= 0) + DEBUG("attached filter to raw socket fd %d", fd); + } + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + xbind(fd, (struct sockaddr *) &sock, sizeof(sock)); + DEBUG("bound to raw socket fd %d", fd); + + return fd; +} diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c new file mode 100644 index 0000000..a47bbaf --- /dev/null +++ b/networking/udhcp/common.c @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +/* common.c + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" + +const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h new file mode 100644 index 0000000..15f0d9a --- /dev/null +++ b/networking/udhcp/common.h @@ -0,0 +1,110 @@ +/* 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 "libbb.h" +#include <netinet/udp.h> +#include <netinet/ip.h> + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +#define DEFAULT_SCRIPT CONFIG_UDHCPC_DEFAULT_SCRIPT + +extern const uint8_t MAC_BCAST_ADDR[6]; /* six all-ones */ + +/*** packet.h ***/ + +#define DHCP_OPTIONS_BUFSIZE 308 + +struct dhcpMessage { + uint8_t op; /* 1 = BOOTREQUEST, 2 = BOOTREPLY */ + uint8_t htype; /* hardware address type. 1 = 10mb ethernet */ + uint8_t hlen; /* hardware address length */ + uint8_t hops; /* used by relay agents only */ + uint32_t xid; /* unique id */ + uint16_t secs; /* elapsed since client began acquisition/renewal */ + uint16_t flags; /* only one flag so far: */ +#define BROADCAST_FLAG 0x8000 /* "I need broadcast replies" */ + uint32_t ciaddr; /* client IP (if client is in BOUND, RENEW or REBINDING state) */ + uint32_t yiaddr; /* 'your' (client) IP address */ + uint32_t siaddr; /* IP address of next server to use in bootstrap, + * returned in DHCPOFFER, DHCPACK by server */ + uint32_t giaddr; /* relay agent IP address */ + uint8_t chaddr[16];/* link-layer client hardware address (MAC) */ + uint8_t sname[64]; /* server host name (ASCIZ) */ + uint8_t file[128]; /* boot file name (ASCIZ) */ + uint32_t cookie; /* fixed first four option bytes (99,130,83,99 dec) */ + uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS]; +} PACKED; + +struct udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcpMessage data; +} PACKED; + +/* Let's see whether compiler understood us right */ +struct BUG_bad_sizeof_struct_udp_dhcp_packet { + char BUG_bad_sizeof_struct_udp_dhcp_packet + [(sizeof(struct udp_dhcp_packet) != 576 + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS) ? -1 : 1]; +}; + +uint16_t udhcp_checksum(void *addr, int count) FAST_FUNC; + +void udhcp_init_header(struct dhcpMessage *packet, char type) FAST_FUNC; + +/*int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd); - in dhcpc.h */ +int udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd) FAST_FUNC; + +int udhcp_send_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, const uint8_t *dest_arp, + int ifindex) FAST_FUNC; + +int udhcp_send_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) FAST_FUNC; + + +/**/ + +void udhcp_run_script(struct dhcpMessage *packet, const char *name) FAST_FUNC; + +// Still need to clean these up... + +/* 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 + +void udhcp_sp_setup(void) FAST_FUNC; +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) FAST_FUNC; +int udhcp_sp_read(const fd_set *rfds) FAST_FUNC; +int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) FAST_FUNC; +int udhcp_raw_socket(int ifindex) FAST_FUNC; +int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) FAST_FUNC; +/* Returns 1 if no reply received */ +int arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface) FAST_FUNC; + +#if ENABLE_UDHCP_DEBUG +# define DEBUG(str, args...) bb_info_msg("### " str, ## args) +#else +# define DEBUG(str, args...) do {;} while (0) +#endif + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 0000000..2d48980 --- /dev/null +++ b/networking/udhcp/dhcpc.c @@ -0,0 +1,646 @@ +/* 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 <syslog.h> + +/* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */ +#define WANT_PIDFILE 1 +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +static int sockfd = -1; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static smallint listen_mode; + +#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 +static smallint state; + +/* struct client_config_t client_config is in bb_common_bufsiz1 */ + + +/* just a little helper */ +static void change_listen_mode(int new_mode) +{ + DEBUG("entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + if (sockfd >= 0) { + close(sockfd); + sockfd = -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_listen_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_listen_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } +} + + +/* perform a release */ +static void perform_release(uint32_t requested_ip, uint32_t server_addr) +{ + char buffer[sizeof("255.255.255.255")]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + strcpy(buffer, 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_listen_mode(LISTEN_NONE); + state = RELEASED; +} + + +#if BB_MMU +static void client_background(void) +{ + bb_daemonize(0); + logmode &= ~LOGMODE_STDIO; + /* rewrite pidfile, as our pid is different now */ + write_pidfile(client_config.pidfile); +} +#endif + + +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) MAIN_EXTERNALLY_VISIBLE; +int udhcpc_main(int argc UNUSED_PARAM, char **argv) +{ + uint8_t *temp, *message; + char *str_c, *str_V, *str_h, *str_F, *str_r; + USE_FEATURE_UDHCP_PORT(char *str_P;) + llist_t *list_O = NULL; + int tryagain_timeout = 20; + int discover_timeout = 3; + int discover_retries = 3; + uint32_t server_addr = server_addr; /* for compiler */ + uint32_t requested_ip = 0; + uint32_t xid = 0; + uint32_t lease_seconds = 0; /* can be given as 32-bit quantity */ + int packet_num; + int timeout; /* must be signed */ + unsigned already_waited_sec; + unsigned opt; + int max_fd; + int retval; + struct timeval tv; + struct dhcpMessage packet; + fd_set rfds; + +#if ENABLE_GETOPT_LONG + static const char udhcpc_longopts[] ALIGN1 = + "clientid\0" Required_argument "c" + "clientid-none\0" No_argument "C" + "vendorclass\0" Required_argument "V" + "hostname\0" Required_argument "H" + "fqdn\0" Required_argument "F" + "interface\0" Required_argument "i" + "now\0" No_argument "n" + "pidfile\0" Required_argument "p" + "quit\0" No_argument "q" + "release\0" No_argument "R" + "request\0" Required_argument "r" + "script\0" Required_argument "s" + "timeout\0" Required_argument "T" + "version\0" No_argument "v" + "retries\0" Required_argument "t" + "tryagain\0" Required_argument "A" + "syslog\0" No_argument "S" + "request-option\0" Required_argument "O" + "no-default-options\0" No_argument "o" + "foreground\0" No_argument "f" + "background\0" No_argument "b" + USE_FEATURE_UDHCPC_ARPING("arping\0" No_argument "a") + USE_FEATURE_UDHCP_PORT("client-port\0" Required_argument "P") + ; +#endif + enum { + OPT_c = 1 << 0, + OPT_C = 1 << 1, + OPT_V = 1 << 2, + OPT_H = 1 << 3, + OPT_h = 1 << 4, + OPT_F = 1 << 5, + OPT_i = 1 << 6, + OPT_n = 1 << 7, + OPT_p = 1 << 8, + OPT_q = 1 << 9, + OPT_R = 1 << 10, + OPT_r = 1 << 11, + OPT_s = 1 << 12, + OPT_T = 1 << 13, + OPT_t = 1 << 14, + OPT_v = 1 << 15, + OPT_S = 1 << 16, + OPT_A = 1 << 17, + OPT_O = 1 << 18, + OPT_o = 1 << 19, + OPT_f = 1 << 20, +/* The rest has variable bit positions, need to be clever */ + OPTBIT_f = 20, + USE_FOR_MMU( OPTBIT_b,) + USE_FEATURE_UDHCPC_ARPING(OPTBIT_a,) + USE_FEATURE_UDHCP_PORT( OPTBIT_P,) + USE_FOR_MMU( OPT_b = 1 << OPTBIT_b,) + USE_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,) + USE_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,) + }; + + /* Default options. */ + USE_FEATURE_UDHCP_PORT(SERVER_PORT = 67;) + USE_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;) + client_config.interface = "eth0"; + client_config.script = DEFAULT_SCRIPT; + + /* Parse command line */ + /* Cc: mutually exclusive; O: list; -T,-t,-A take numeric param */ + opt_complementary = "c--C:C--c:O::T+:t+:A+"; + USE_GETOPT_LONG(applet_long_options = udhcpc_longopts;) + opt = getopt32(argv, "c:CV:H:h:F:i:np:qRr:s:T:t:vSA:O:of" + USE_FOR_MMU("b") + USE_FEATURE_UDHCPC_ARPING("a") + USE_FEATURE_UDHCP_PORT("P:") + , &str_c, &str_V, &str_h, &str_h, &str_F + , &client_config.interface, &client_config.pidfile, &str_r /* i,p */ + , &client_config.script /* s */ + , &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */ + , &list_O + USE_FEATURE_UDHCP_PORT(, &str_P) + ); + if (opt & OPT_c) + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0); + if (opt & OPT_V) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); + if (opt & (OPT_h|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<3>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_r) + requested_ip = inet_addr(str_r); + if (opt & OPT_v) { + puts("version "BB_VER); + return 0; + } +#if ENABLE_FEATURE_UDHCP_PORT + if (opt & OPT_P) { + CLIENT_PORT = xatou16(str_P); + SERVER_PORT = CLIENT_PORT - 1; + } +#endif + if (opt & OPT_o) + client_config.no_default_options = 1; + while (list_O) { + char *optstr = llist_pop(&list_O); + int n = index_in_strings(dhcp_option_strings, optstr); + if (n < 0) + bb_error_msg_and_die("unknown option '%s'", optstr); + n = dhcp_options[n].code; + client_config.opt_mask[n >> 3] |= 1 << (n & 7); + } + + if (udhcp_read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp)) + return 1; +#if !BB_MMU + /* on NOMMU reexec (i.e., background) early */ + if (!(opt & OPT_f)) { + bb_daemonize_or_rexec(0 /* flags */, argv); + logmode = 0; + } +#endif + if (opt & OPT_S) { + openlog(applet_name, LOG_PID, LOG_LOCAL0); + logmode |= LOGMODE_SYSLOG; + } + + /* Make sure fd 0,1,2 are open */ + bb_sanitize_stdio(); + /* Equivalent of doing a fflush after every \n */ + setlinebuf(stdout); + + /* Create pidfile */ + write_pidfile(client_config.pidfile); + + /* Goes to stdout (unless NOMMU) and possibly syslog */ + bb_info_msg("%s (v"BB_VER") started", applet_name); + + /* if not set, and not suppressed, setup the default client ID */ + if (!client_config.clientid && !(opt & OPT_C)) { + 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_listen_mode(LISTEN_RAW); + packet_num = 0; + timeout = 0; + already_waited_sec = 0; + + /* Main event loop. select() waits on signal pipe and possibly + * on sockfd. + * "continue" statements in code below jump to the top of the loop. + */ + for (;;) { + unsigned timestamp_before_wait; + + if (listen_mode != LISTEN_NONE && sockfd < 0) { + if (listen_mode == LISTEN_KERNEL) + sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface); + else + sockfd = udhcp_raw_socket(client_config.ifindex); + } + max_fd = udhcp_sp_fd_set(&rfds, sockfd); + + tv.tv_sec = timeout - already_waited_sec; + tv.tv_usec = 0; + retval = 0; /* If we already timed out, fall through, else... */ + if (tv.tv_sec > 0) { + timestamp_before_wait = (unsigned)monotonic_sec(); + DEBUG("Waiting on select..."); + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + if (retval < 0) { + /* EINTR? A signal was caught, don't panic */ + if (errno == EINTR) + continue; + /* Else: an error occured, panic! */ + bb_perror_msg_and_die("select"); + } + } + + /* If timeout dropped to zero, time to become active: + * resend discover/renew/whatever + */ + if (retval == 0) { + /* We will restart the wait in any case */ + already_waited_sec = 0; + + switch (state) { + case INIT_SELECTING: + if (packet_num < discover_retries) { + if (packet_num == 0) + xid = random_xid(); + + send_discover(xid, requested_ip); /* broadcast */ + + timeout = discover_timeout; + packet_num++; + continue; + } + leasefail: + udhcp_run_script(NULL, "leasefail"); +#if BB_MMU /* -b is not supported on NOMMU */ + if (opt & OPT_b) { /* background if no lease */ + bb_info_msg("No lease, forking to background"); + client_background(); + /* do not background again! */ + opt = ((opt & ~OPT_b) | OPT_f); + } else +#endif + if (opt & OPT_n) { /* abort if no lease */ + bb_info_msg("No lease, failing"); + retval = 1; + goto ret; + } + /* wait before trying again */ + timeout = tryagain_timeout; + packet_num = 0; + continue; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < discover_retries) { + /* send request packet */ + if (state == RENEW_REQUESTED) /* unicast */ + send_renew(xid, server_addr, requested_ip); + else /* broadcast */ + send_select(xid, server_addr, requested_ip); + + timeout = discover_timeout; + packet_num++; + continue; + } + /* timed out, go back to init state */ + if (state == RENEW_REQUESTED) + udhcp_run_script(NULL, "deconfig"); + change_listen_mode(LISTEN_RAW); + /* "discover...select...discover..." loops + * were seen in the wild. Treat them similarly + * to "no response to discover" case */ + if (state == REQUESTING) { + state = INIT_SELECTING; + goto leasefail; + } + state = INIT_SELECTING; + timeout = 0; + packet_num = 0; + continue; + case BOUND: + /* Half of the lease passed, time to enter renewing state */ + change_listen_mode(LISTEN_KERNEL); + DEBUG("Entering renew state"); + state = RENEWING; + /* fall right through */ + case RENEWING: + if (timeout > 60) { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + timeout >>= 1; + continue; + } + /* Timed out, enter rebinding state */ + DEBUG("Entering rebinding state"); + state = REBINDING; + /* fall right through */ + case REBINDING: + /* Lease is *really* about to run out, + * try to find DHCP server using broadcast */ + if (timeout > 0) { + /* send a request packet */ + send_renew(xid, 0 /* INADDR_ANY*/, requested_ip); /* broadcast */ + timeout >>= 1; + continue; + } + /* Timed out, enter init state */ + bb_info_msg("Lease lost, entering init state"); + udhcp_run_script(NULL, "deconfig"); + change_listen_mode(LISTEN_RAW); + state = INIT_SELECTING; + /*timeout = 0; - already is */ + packet_num = 0; + continue; + /* case RELEASED: */ + } + /* yah, I know, *you* say it would never happen */ + timeout = INT_MAX; + continue; /* back to main loop */ + } + + /* select() didn't timeout, something did happen. */ + /* Is it a packet? */ + if (listen_mode != LISTEN_NONE && FD_ISSET(sockfd, &rfds)) { + int len; + /* A packet is ready, read it */ + + if (listen_mode == LISTEN_KERNEL) + len = udhcp_recv_kernel_packet(&packet, sockfd); + else + len = udhcp_recv_raw_packet(&packet, sockfd); + if (len == -1) { /* error is severe, reopen socket */ + DEBUG("error on read, %s, reopening socket", strerror(errno)); + sleep(discover_timeout); /* 3 seconds by default */ + change_listen_mode(listen_mode); /* just close and reopen */ + } + /* If this packet will turn out to be unrelated/bogus, + * we will go back and wait for next one. + * Be sure timeout is properly decreased. */ + already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait; + if (len < 0) + continue; + + if (packet.xid != xid) { + DEBUG("Ignoring xid %x (our xid is %x)", + (unsigned)packet.xid, (unsigned)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; + } + + message = get_option(&packet, DHCP_MESSAGE_TYPE); + if (message == NULL) { + bb_error_msg("cannot get message type from packet - ignoring"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + /* TODO: why we don't just fetch server's IP from IP header? */ + temp = get_option(&packet, DHCP_SERVER_ID); + if (!temp) { + bb_error_msg("no server ID in message"); + continue; + /* still selecting - this server looks bad */ + } + /* it IS unaligned sometimes, don't "optimize" */ + server_addr = get_unaligned_u32p((uint32_t*)temp); + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = 0; + packet_num = 0; + already_waited_sec = 0; + } + continue; + 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_seconds = 60 * 60; + } else { + /* it IS unaligned sometimes, don't "optimize" */ + lease_seconds = get_unaligned_u32p((uint32_t*)temp); + lease_seconds = ntohl(lease_seconds); + lease_seconds &= 0x0fffffff; /* paranoia: must not be prone to overflows */ + if (lease_seconds < 10) /* and not too small */ + lease_seconds = 10; + } +#if ENABLE_FEATURE_UDHCPC_ARPING + if (opt & OPT_a) { +/* RFC 2131 3.1 paragraph 5: + * "The client receives the DHCPACK message with configuration + * parameters. The client SHOULD perform a final check on the + * parameters (e.g., ARP for allocated network address), and notes + * the duration of the lease specified in the DHCPACK message. At this + * point, the client is configured. If the client detects that the + * address is already in use (e.g., through the use of ARP), + * the client MUST send a DHCPDECLINE message to the server and restarts + * the configuration process..." */ + if (!arpping(packet.yiaddr, + (uint32_t) 0, + client_config.arp, + client_config.interface) + ) { + bb_info_msg("offered address is in use " + "(got ARP reply), declining"); + send_decline(xid, server_addr, packet.yiaddr); + + if (state != REQUESTING) + udhcp_run_script(NULL, "deconfig"); + change_listen_mode(LISTEN_RAW); + state = INIT_SELECTING; + requested_ip = 0; + timeout = tryagain_timeout; + packet_num = 0; + already_waited_sec = 0; + continue; /* back to main loop */ + } + } +#endif + /* enter bound state */ + timeout = lease_seconds / 2; + { + struct in_addr temp_addr; + temp_addr.s_addr = packet.yiaddr; + bb_info_msg("Lease of %s obtained, lease time %u", + inet_ntoa(temp_addr), (unsigned)lease_seconds); + } + requested_ip = packet.yiaddr; + udhcp_run_script(&packet, + ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); + + state = BOUND; + change_listen_mode(LISTEN_NONE); + if (opt & OPT_q) { /* quit after lease */ + if (opt & OPT_R) /* release on quit */ + perform_release(requested_ip, server_addr); + goto ret0; + } +#if BB_MMU /* NOMMU case backgrounded earlier */ + if (!(opt & OPT_f)) { + client_background(); + /* do not background again! */ + opt = ((opt & ~OPT_b) | OPT_f); + } +#endif + already_waited_sec = 0; + continue; /* back to main loop */ + } + 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"); + change_listen_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + state = INIT_SELECTING; + requested_ip = 0; + timeout = 0; + packet_num = 0; + already_waited_sec = 0; + } + continue; + /* case BOUND, RELEASED: - ignore all packets */ + } + continue; /* back to main loop */ + } + + /* select() didn't timeout, something did happen. + * But it wasn't a packet. It's a signal pipe then. */ + { + int signo = udhcp_sp_read(&rfds); + switch (signo) { + case SIGUSR1: + perform_renew(); + /* start things over */ + packet_num = 0; + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; + break; + case SIGUSR2: + perform_release(requested_ip, server_addr); + timeout = INT_MAX; + break; + case SIGTERM: + bb_info_msg("Received SIGTERM"); + if (opt & OPT_R) /* release on quit */ + perform_release(requested_ip, server_addr); + goto ret0; + } + } + } /* for (;;) - main loop ends */ + + ret0: + retval = 0; + ret: + /*if (client_config.pidfile) - remove_pidfile has its own check */ + remove_pidfile(client_config.pidfile); + return retval; +} diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h new file mode 100644 index 0000000..a934849 --- /dev/null +++ b/networking/udhcp/dhcpc.h @@ -0,0 +1,56 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.h */ + +#ifndef _DHCPC_H +#define _DHCPC_H + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +struct client_config_t { + uint8_t arp[6]; /* Our arp address */ + /* TODO: combine flag fields into single "unsigned opt" */ + /* (can be set directly to the result of getopt32) */ + char no_default_options; /* Do not include default optins in request */ + USE_FEATURE_UDHCP_PORT(uint16_t port;) + int ifindex; /* Index number of the interface to use */ + uint8_t opt_mask[256 / 8]; /* Bitmask of options to send (-O option) */ + const char *interface; /* The name of the interface to use */ + char *pidfile; /* Optionally store the process ID */ + const 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 */ +}; + +/* server_config sits in 1st half of bb_common_bufsiz1 */ +#define client_config (*(struct client_config_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE / 2])) + +#if ENABLE_FEATURE_UDHCP_PORT +#define CLIENT_PORT (client_config.port) +#else +#define CLIENT_PORT 68 +#endif + + +/*** clientpacket.h ***/ + +uint32_t random_xid(void) FAST_FUNC; +int send_discover(uint32_t xid, uint32_t requested) FAST_FUNC; +int send_select(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC; +#if ENABLE_FEATURE_UDHCPC_ARPING +int send_decline(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC; +#endif +int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC; +int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC; +int send_release(uint32_t server, uint32_t ciaddr) FAST_FUNC; + +int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd) FAST_FUNC; + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c new file mode 100644 index 0000000..b512c45 --- /dev/null +++ b/networking/udhcp/dhcpd.c @@ -0,0 +1,272 @@ +/* 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 <syslog.h> +#include "common.h" +#include "dhcpc.h" +#include "dhcpd.h" +#include "options.h" + + +/* globals */ +struct dhcpOfferedAddr *leases; +/* struct server_config_t server_config is in bb_common_bufsiz1 */ + + +int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int udhcpd_main(int argc UNUSED_PARAM, 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 timeout_end; + unsigned num_ips; + unsigned opt; + struct option_set *option; + struct dhcpOfferedAddr *lease, static_lease; + USE_FEATURE_UDHCP_PORT(char *str_P;) + +#if ENABLE_FEATURE_UDHCP_PORT + SERVER_PORT = 67; + CLIENT_PORT = 68; +#endif + + opt = getopt32(argv, "fS" USE_FEATURE_UDHCP_PORT("P:", &str_P)); + argv += optind; + + if (!(opt & 1)) { /* no -f */ + bb_daemonize_or_rexec(0, argv); + logmode &= ~LOGMODE_STDIO; + } + + if (opt & 2) { /* -S */ + openlog(applet_name, LOG_PID, LOG_LOCAL0); + logmode |= LOGMODE_SYSLOG; + } +#if ENABLE_FEATURE_UDHCP_PORT + if (opt & 4) { /* -P */ + SERVER_PORT = xatou16(str_P); + CLIENT_PORT = SERVER_PORT + 1; + } +#endif + /* Would rather not do read_config before daemonization - + * otherwise NOMMU machines will parse config twice */ + read_config(argv[0] ? argv[0] : DHCPD_CONF_FILE); + + /* Make sure fd 0,1,2 are open */ + bb_sanitize_stdio(); + /* Equivalent of doing a fflush after every \n */ + setlinebuf(stdout); + + /* Create pidfile */ + write_pidfile(server_config.pidfile); + /* if (!..) bb_perror_msg("cannot create pidfile %s", pidfile); */ + + bb_info_msg("%s (v"BB_VER") started", applet_name); + + option = find_option(server_config.options, DHCP_LEASE_TIME); + server_config.lease = LEASE_TIME; + if (option) { + memcpy(&server_config.lease, option->data + 2, 4); + server_config.lease = ntohl(server_config.lease); + } + + /* Sanity check */ + num_ips = server_config.end_ip - server_config.start_ip + 1; + if (server_config.max_leases > num_ips) { + bb_error_msg("max_leases=%u is too big, setting to %u", + (unsigned)server_config.max_leases, num_ips); + server_config.max_leases = num_ips; + } + + leases = xzalloc(server_config.max_leases * sizeof(*leases)); + read_leases(server_config.lease_file); + + if (udhcp_read_interface(server_config.interface, &server_config.ifindex, + &server_config.server, server_config.arp)) { + retval = 1; + goto ret; + } + + /* Setup the signal pipe */ + udhcp_sp_setup(); + + timeout_end = monotonic_sec() + server_config.auto_time; + while (1) { /* loop until universe collapses */ + + if (server_socket < 0) { + server_socket = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, + server_config.interface); + } + + max_sock = udhcp_sp_fd_set(&rfds, server_socket); + if (server_config.auto_time) { + tv.tv_sec = timeout_end - monotonic_sec(); + tv.tv_usec = 0; + } + retval = 0; + if (!server_config.auto_time || tv.tv_sec > 0) { + retval = select(max_sock + 1, &rfds, NULL, NULL, + server_config.auto_time ? &tv : NULL); + } + if (retval == 0) { + write_leases(); + timeout_end = monotonic_sec() + server_config.auto_time; + continue; + } + 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 = monotonic_sec() + server_config.auto_time; + continue; + case SIGTERM: + bb_info_msg("Received a SIGTERM"); + goto ret0; + case 0: break; /* no signal */ + default: continue; /* signal or error (probably EINTR) */ + } + + bytes = udhcp_recv_kernel_packet(&packet, server_socket); /* this waits for a packet - idle */ + if (bytes < 0) { + if (bytes == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + close(server_socket); + server_socket = -1; + } + continue; + } + + state = get_option(&packet, DHCP_MESSAGE_TYPE); + if (state == 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 (send_offer(&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 + ) { + send_ACK(&packet, lease->yiaddr); + } + } else if (requested) { + /* INIT-REBOOT State */ + if (lease->yiaddr == requested_align) + send_ACK(&packet, lease->yiaddr); + else + send_NAK(&packet); + } else if (lease->yiaddr == packet.ciaddr) { + /* RENEWING or REBINDING State */ + send_ACK(&packet, lease->yiaddr); + } else { /* don't know what to do!!!! */ + send_NAK(&packet); + } + + /* what to do if we have no record of the client */ + } else if (server_id) { + /* SELECTING State */ + + } else if (requested) { + /* INIT-REBOOT State */ + lease = find_lease_by_yiaddr(requested_align); + if (lease) { + if (lease_expired(lease)) { + /* probably best if we drop this lease */ + memset(lease->chaddr, 0, 16); + /* make some contention for this address */ + } else + send_NAK(&packet); + } else { + uint32_t r = ntohl(requested_align); + if (r < server_config.start_ip + || r > server_config.end_ip + ) { + send_NAK(&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]); + } + } + ret0: + retval = 0; + ret: + /*if (server_config.pidfile) - server_config.pidfile is never NULL */ + remove_pidfile(server_config.pidfile); + return retval; +} diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h new file mode 100644 index 0000000..2d97528 --- /dev/null +++ b/networking/udhcp/dhcpd.h @@ -0,0 +1,125 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.h */ + +#ifndef _DHCPD_H +#define _DHCPD_H + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +/************************************/ +/* 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 CONFIG_DHCPD_LEASES_FILE + +/* where to find the DHCP server configuration file */ +#define DHCPD_CONF_FILE "/etc/udhcpd.conf" + +struct option_set { + uint8_t *data; + struct option_set *next; +}; + +struct static_lease { + struct static_lease *next; + uint8_t *mac; + uint32_t *ip; +}; + +struct server_config_t { + uint32_t server; /* Our IP, in network order */ +#if ENABLE_FEATURE_UDHCP_PORT + uint16_t port; +#endif + /* start,end are in host order: we need to compare start <= ip <= end */ + uint32_t start_ip; /* Start address of leases, in host order */ + uint32_t end_ip; /* End of leases, in host 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 */ + char remaining; /* should the lease file be interpreted as lease time remaining, or + * as the time the lease expires */ + uint32_t lease; /* lease time in seconds (host order) */ + uint32_t max_leases; /* maximum number of leases (including reserved address) */ + uint32_t auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + uint32_t decline_time; /* how long an address is reserved if a client returns a + * decline message */ + uint32_t conflict_time; /* how long an arp conflict offender is leased for */ + uint32_t offer_time; /* how long an offered address is reserved */ + uint32_t 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 */ +}; + +#define server_config (*(struct server_config_t*)&bb_common_bufsiz1) +/* client_config sits in 2nd half of bb_common_bufsiz1 */ + +#if ENABLE_FEATURE_UDHCP_PORT +#define SERVER_PORT (server_config.port) +#else +#define SERVER_PORT 67 +#endif + +extern struct dhcpOfferedAddr *leases; + + +/*** leases.h ***/ + +struct dhcpOfferedAddr { + uint8_t chaddr[16]; + uint32_t yiaddr; /* network order */ + uint32_t expires; /* host order */ +}; + +struct dhcpOfferedAddr *add_lease(const uint8_t *chaddr, uint32_t yiaddr, unsigned long lease) FAST_FUNC; +int lease_expired(struct dhcpOfferedAddr *lease) FAST_FUNC; +struct dhcpOfferedAddr *find_lease_by_chaddr(const uint8_t *chaddr) FAST_FUNC; +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) FAST_FUNC; +uint32_t find_address(int check_expired) FAST_FUNC; + + +/*** 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) FAST_FUNC; +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) FAST_FUNC; +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip) FAST_FUNC; +/* Print out static leases just to check what's going on (debug code) */ +void printStaticLeases(struct static_lease **lease_struct) FAST_FUNC; + + +/*** serverpacket.h ***/ + +int send_offer(struct dhcpMessage *oldpacket) FAST_FUNC; +int send_NAK(struct dhcpMessage *oldpacket) FAST_FUNC; +int send_ACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) FAST_FUNC; +int send_inform(struct dhcpMessage *oldpacket) FAST_FUNC; + + +/*** files.h ***/ + +void read_config(const char *file) FAST_FUNC; +void write_leases(void) FAST_FUNC; +void read_leases(const char *file) FAST_FUNC; +struct option_set *find_option(struct option_set *opt_list, uint8_t code) FAST_FUNC; + + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c new file mode 100644 index 0000000..f3b2855 --- /dev/null +++ b/networking/udhcp/dhcprelay.c @@ -0,0 +1,314 @@ +/* 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 "options.h" + +/* constants */ +#define SERVER_PORT 67 +#define SELECT_TIMEOUT 5 /* select timeout in sec. */ +#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ + +/* This list holds information about clients. The xid_* functions manipulate this list. */ +struct xid_item { + unsigned timestamp; + int client; + uint32_t xid; + struct sockaddr_in ip; + struct xid_item *next; +}; + +#define dhcprelay_xid_list (*(struct xid_item*)&bb_common_bufsiz1) + +static struct xid_item *xid_add(uint32_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 = monotonic_sec(); + 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; + unsigned current_time = monotonic_sec(); + + 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(uint32_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(uint32_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) +{ + uint8_t *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; +} + +/** + * 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, **client_dev; + int i, cn; + + /* copy list */ + dev_list = xstrdup(dev_list); + + /* get number of items, replace ',' with NULs */ + s = dev_list; + cn = 1; + while (*s) { + if (*s == ',') { + *s = '\0'; + cn++; + } + s++; + } + *client_number = cn; + + /* create vector of pointers */ + client_dev = xzalloc(cn * sizeof(*client_dev)); + client_dev[0] = dev_list; + i = 1; + while (i != cn) { + client_dev[i] = client_dev[i - 1] + strlen(client_dev[i - 1]) + 1; + i++; + } + return client_dev; +} + + +/* Creates listen sockets (in fds) and returns numerically max fd. */ +static int init_sockets(char **client, int num_clients, + char *server, int *fds) +{ + int i, n; + + /* talk to real server on bootps */ + fds[0] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server); + n = fds[0]; + + for (i = 1; i < num_clients; i++) { + /* listen for clients on bootps */ + fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, client[i-1]); + if (fds[i] > n) + n = fds[i]; + } + return n; +} + + +/** + * 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); + 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) NORETURN; +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; + socklen_t addr_size; + struct sockaddr_in client_addr; + struct timeval tv; + int i; + + while (1) { + 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_recv_kernel_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 (udhcp_read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL)) + 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) MAIN_EXTERNALLY_VISIBLE; +int dhcprelay_main(int argc, char **argv) +{ + int num_sockets, max_socket; + int *fds; + uint32_t gw_ip; + char **clients; + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(SERVER_PORT); + 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); + num_sockets++; /* for server socket at fds[0] */ + fds = xmalloc(num_sockets * sizeof(fds[0])); + max_socket = init_sockets(clients, num_sockets, argv[2], fds); + + if (udhcp_read_interface(argv[2], NULL, &gw_ip, NULL)) + return 1; + + /* doesn't return */ + dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip); + /* return 0; - not reached */ +} diff --git a/networking/udhcp/domain_codec.c b/networking/udhcp/domain_codec.c new file mode 100644 index 0000000..6f051c4 --- /dev/null +++ b/networking/udhcp/domain_codec.c @@ -0,0 +1,205 @@ +/* vi: set sw=4 ts=4: */ + +/* RFC1035 domain compression routines (C) 2007 Gabriel Somlo <somlo at cmu.edu> + * + * Loosely based on the isc-dhcpd implementation by dhankins@isc.org + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#if ENABLE_FEATURE_UDHCP_RFC3397 + +#include "common.h" +#include "options.h" + +#define NS_MAXDNAME 1025 /* max domain name length */ +#define NS_MAXCDNAME 255 /* max compressed domain name length */ +#define NS_MAXLABEL 63 /* max label length */ +#define NS_MAXDNSRCH 6 /* max domains in search path */ +#define NS_CMPRSFLGS 0xc0 /* name compression pointer flag */ + + +/* expand a RFC1035-compressed list of domain names "cstr", of length "clen"; + * returns a newly allocated string containing the space-separated domains, + * prefixed with the contents of string pre, or NULL if an error occurs. + */ +char* FAST_FUNC dname_dec(const uint8_t *cstr, int clen, const char *pre) +{ + const uint8_t *c; + int crtpos, retpos, depth, plen = 0, len = 0; + char *dst = NULL; + + if (!cstr) + return NULL; + + if (pre) + plen = strlen(pre); + + /* We make two passes over the cstr string. First, we compute + * how long the resulting string would be. Then we allocate a + * new buffer of the required length, and fill it in with the + * expanded content. The advantage of this approach is not + * having to deal with requiring callers to supply their own + * buffer, then having to check if it's sufficiently large, etc. + */ + + while (!dst) { + + if (len > 0) { /* second pass? allocate dst buffer and copy pre */ + dst = xmalloc(len + plen); + memcpy(dst, pre, plen); + } + + crtpos = retpos = depth = len = 0; + + while (crtpos < clen) { + c = cstr + crtpos; + + if ((*c & NS_CMPRSFLGS) != 0) { /* pointer */ + if (crtpos + 2 > clen) /* no offset to jump to? abort */ + return NULL; + if (retpos == 0) /* toplevel? save return spot */ + retpos = crtpos + 2; + depth++; + crtpos = ((*c & 0x3f) << 8) | (*(c + 1) & 0xff); /* jump */ + } else if (*c) { /* label */ + if (crtpos + *c + 1 > clen) /* label too long? abort */ + return NULL; + if (dst) + memcpy(dst + plen + len, c + 1, *c); + len += *c + 1; + crtpos += *c + 1; + if (dst) + *(dst + plen + len - 1) = '.'; + } else { /* null: end of current domain name */ + if (retpos == 0) { /* toplevel? keep going */ + crtpos++; + } else { /* return to toplevel saved spot */ + crtpos = retpos; + retpos = depth = 0; + } + if (dst) + *(dst + plen + len - 1) = ' '; + } + + if (depth > NS_MAXDNSRCH || /* too many jumps? abort, it's a loop */ + len > NS_MAXDNAME * NS_MAXDNSRCH) /* result too long? abort */ + return NULL; + } + + if (!len) /* expanded string has 0 length? abort */ + return NULL; + + if (dst) + *(dst + plen + len - 1) = '\0'; + } + + return dst; +} + +/* Convert a domain name (src) from human-readable "foo.blah.com" format into + * RFC1035 encoding "\003foo\004blah\003com\000". Return allocated string, or + * NULL if an error occurs. + */ +static uint8_t *convert_dname(const char *src) +{ + uint8_t c, *res, *lp, *rp; + int len; + + res = xmalloc(strlen(src) + 2); + rp = lp = res; + rp++; + + for (;;) { + c = (uint8_t)*src++; + if (c == '.' || c == '\0') { /* end of label */ + len = rp - lp - 1; + /* label too long, too short, or two '.'s in a row? abort */ + if (len > NS_MAXLABEL || len == 0 || (c == '.' && *src == '.')) { + free(res); + return NULL; + } + *lp = len; + lp = rp++; + if (c == '\0' || *src == '\0') /* end of dname */ + break; + } else { + if (c >= 0x41 && c <= 0x5A) /* uppercase? convert to lower */ + c += 0x20; + *rp++ = c; + } + } + + *lp = 0; + if (rp - res > NS_MAXCDNAME) { /* dname too long? abort */ + free(res); + return NULL; + } + return res; +} + +/* returns the offset within cstr at which dname can be found, or -1 + */ +static int find_offset(const uint8_t *cstr, int clen, const uint8_t *dname) +{ + const uint8_t *c, *d; + int off, inc; + + /* find all labels in cstr */ + off = 0; + while (off < clen) { + c = cstr + off; + + if ((*c & NS_CMPRSFLGS) != 0) { /* pointer, skip */ + off += 2; + } else if (*c) { /* label, try matching dname */ + inc = *c + 1; + d = dname; + while (*c == *d && memcmp(c + 1, d + 1, *c) == 0) { + if (*c == 0) /* match, return offset */ + return off; + d += *c + 1; + c += *c + 1; + if ((*c & NS_CMPRSFLGS) != 0) /* pointer, jump */ + c = cstr + (((*c & 0x3f) << 8) | (*(c + 1) & 0xff)); + } + off += inc; + } else { /* null, skip */ + off++; + } + } + + return -1; +} + +/* computes string to be appended to cstr so that src would be added to + * the compression (best case, it's a 2-byte pointer to some offset within + * cstr; worst case, it's all of src, converted to rfc3011 format). + * The computed string is returned directly; its length is returned via retlen; + * NULL and 0, respectively, are returned if an error occurs. + */ +uint8_t* FAST_FUNC dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen) +{ + uint8_t *d, *dname; + int off; + + dname = convert_dname(src); + if (dname == NULL) { + *retlen = 0; + return NULL; + } + + for (d = dname; *d != 0; d += *d + 1) { + off = find_offset(cstr, clen, d); + if (off >= 0) { /* found a match, add pointer and terminate string */ + *d++ = NS_CMPRSFLGS; + *d = off; + break; + } + } + + *retlen = d - dname + 1; + return dname; +} + +#endif /* ENABLE_FEATURE_UDHCP_RFC3397 */ diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c new file mode 100644 index 0000000..3e19390 --- /dev/null +++ b/networking/udhcp/dumpleases.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "common.h" +#include "dhcpd.h" + +int dumpleases_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int dumpleases_main(int argc UNUSED_PARAM, char **argv) +{ + int fd; + int i; + unsigned opt; + time_t expires; + const char *file = LEASES_FILE; + struct dhcpOfferedAddr lease; + struct in_addr addr; + + enum { + OPT_a = 0x1, // -a + OPT_r = 0x2, // -r + OPT_f = 0x4, // -f + }; +#if ENABLE_GETOPT_LONG + static const char dumpleases_longopts[] ALIGN1 = + "absolute\0" No_argument "a" + "remaining\0" No_argument "r" + "file\0" Required_argument "f" + ; + + applet_long_options = dumpleases_longopts; +#endif + opt_complementary = "=0:a--r:r--a"; + opt = getopt32(argv, "arf:", &file); + + fd = xopen(file, O_RDONLY); + + printf("Mac Address IP-Address Expires %s\n", (opt & OPT_a) ? "at" : "in"); + /* "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */ + while (full_read(fd, &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 (!(opt & OPT_a)) { /* no -a */ + if (!expires) + puts("expired"); + 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 /* -a */ + fputs(ctime(&expires), stdout); + } + /* close(fd); */ + + return 0; +} diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c new file mode 100644 index 0000000..0b97d76 --- /dev/null +++ b/networking/udhcp/files.c @@ -0,0 +1,413 @@ +/* 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" + + +/* on these functions, make sure your datatype matches */ +static int read_ip(const char *line, void *arg) +{ + len_and_sockaddr *lsa; + + lsa = host_and_af2sockaddr(line, 0, AF_INET); + if (!lsa) + return 0; + *(uint32_t*)arg = lsa->u.sin.sin_addr.s_addr; + free(lsa); + return 1; +} + +static int read_mac(const char *line, void *arg) +{ + struct ether_addr *temp_ether_addr; + + temp_ether_addr = ether_aton_r(line, (struct ether_addr *)arg); + if (temp_ether_addr == NULL) + return 0; + return 1; +} + + +static int read_str(const char *line, void *arg) +{ + char **dest = arg; + + free(*dest); + *dest = xstrdup(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; + + if (!strcasecmp("yes", line)) { + *dest = 1; + return 1; + } + if (!strcasecmp("no", line)) { + *dest = 0; + return 1; + } + return 0; +} + + +/* find option 'code' in opt_list */ +struct option_set* FAST_FUNC find_option(struct option_set *opt_list, uint8_t 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; + 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; + + existing = find_option(*opt_list, option->code); + if (!existing) { + DEBUG("Attaching option %02x to list", option->code); + +#if ENABLE_FEATURE_UDHCP_RFC3397 + if ((option->flags & TYPE_MASK) == OPTION_STR1035) + /* reuse buffer and length for RFC1035-formatted string */ + buffer = (char *)dname_enc(NULL, 0, buffer, &length); +#endif + + /* make a new option */ + new = xmalloc(sizeof(*new)); + 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; +#if ENABLE_FEATURE_UDHCP_RFC3397 + if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL) + free(buffer); +#endif + return; + } + + /* add it to an existing option */ + DEBUG("Attaching option %02x to existing member of list", option->code); + if (option->flags & OPTION_LIST) { +#if ENABLE_FEATURE_UDHCP_RFC3397 + if ((option->flags & TYPE_MASK) == OPTION_STR1035) + /* reuse buffer and length for RFC1035-formatted string */ + buffer = (char *)dname_enc(existing->data + 2, + existing->data[OPT_LEN], buffer, &length); +#endif + if (existing->data[OPT_LEN] + length <= 255) { + existing->data = xrealloc(existing->data, + existing->data[OPT_LEN] + length + 3); + if ((option->flags & TYPE_MASK) == OPTION_STRING) { + /* ' ' can bring us to 256 - bad */ + if (existing->data[OPT_LEN] + length >= 255) + return; + /* add space separator between STRING options in a list */ + existing->data[existing->data[OPT_LEN] + 2] = ' '; + existing->data[OPT_LEN]++; + } + 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 */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL) + free(buffer); +#endif + } /* else, ignore the new data */ +} + + +/* 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; + char *line; + const struct dhcp_option *option; + int retval, length, idx; + char buffer[8] ALIGNED(4); + 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; + + idx = index_in_strings(dhcp_option_strings, opt); /* NB: was strcasecmp! */ + if (idx < 0) + return 0; + option = &dhcp_options[idx]; + + retval = 0; + do { + val = strtok(NULL, ", \t"); + if (!val) break; + length = dhcp_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); + val = strtok(NULL, ", \t/-"); + if (!val) + retval = 0; + if (retval) + retval = read_ip(val, buffer + 4); + break; + case OPTION_STRING: +#if ENABLE_FEATURE_UDHCP_RFC3397 + case OPTION_STR1035: +#endif + 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; + /* htonX are macros in older libc's, using temp var + * in code below for safety */ + /* TODO: use bb_strtoX? */ + case OPTION_U16: { + unsigned long tmp = strtoul(val, &endptr, 0); + *result_u16 = htons(tmp); + retval = (endptr[0] == '\0' /*&& tmp < 0x10000*/); + break; + } + case OPTION_S16: { + long tmp = strtol(val, &endptr, 0); + *result_u16 = htons(tmp); + retval = (endptr[0] == '\0'); + break; + } + case OPTION_U32: { + unsigned long tmp = strtoul(val, &endptr, 0); + *result_u32 = htonl(tmp); + retval = (endptr[0] == '\0'); + break; + } + case OPTION_S32: { + long tmp = strtol(val, &endptr, 0); + *result_u32 = htonl(tmp); + 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_UDHCP_DEBUG) printStaticLeases(arg); + + return 1; +} + + +struct config_keyword { + const char *keyword; + int (*handler)(const char *line, void *var); + void *var; + const char *def; +}; + +static const struct config_keyword keywords[] = { + /* keyword handler variable address default */ + {"start", read_ip, &(server_config.start_ip), "192.168.0.20"}, + {"end", read_ip, &(server_config.end_ip), "192.168.0.254"}, + {"interface", read_str, &(server_config.interface), "eth0"}, + /* Avoid "max_leases value not sane" warning by setting default + * to default_end_ip - default_start_ip + 1: */ + {"max_leases", read_u32, &(server_config.max_leases), "235"}, + {"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"}, + {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"}, + /* keywords with no defaults must be last! */ + {"option", read_opt, &(server_config.options), ""}, + {"opt", read_opt, &(server_config.options), ""}, + {"notify_file", read_str, &(server_config.notify_file), ""}, + {"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 */ +}; +enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 }; + +void FAST_FUNC read_config(const char *file) +{ + parser_t *parser; + const struct config_keyword *k; + unsigned i; + char *token[2]; + + for (i = 0; i < KWS_WITH_DEFAULTS; i++) + keywords[i].handler(keywords[i].def, keywords[i].var); + + parser = config_open(file); + while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) { + for (k = keywords, i = 0; i < ARRAY_SIZE(keywords); k++, i++) { + if (!strcasecmp(token[0], k->keyword)) { + if (!k->handler(token[1], k->var)) { + bb_error_msg("can't parse line %u in %s", + parser->lineno, file); + /* reset back to the default value */ + k->handler(k->def, k->var); + } + break; + } + } + } + config_close(parser); + + server_config.start_ip = ntohl(server_config.start_ip); + server_config.end_ip = ntohl(server_config.end_ip); +} + + +void FAST_FUNC write_leases(void) +{ + int fp; + unsigned i; + time_t curr = time(0); + unsigned long tmp_time; + + fp = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC); + if (fp < 0) { + 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) { +// TODO: vfork-based child creation + char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file); + system(cmd); + free(cmd); + } +} + + +void FAST_FUNC read_leases(const char *file) +{ + int fp; + unsigned i; + struct dhcpOfferedAddr lease; + + fp = open_or_warn(file, O_RDONLY); + if (fp < 0) { + return; + } + + i = 0; + while (i < server_config.max_leases + && full_read(fp, &lease, sizeof(lease)) == sizeof(lease) + ) { + /* ADDME: is it a static lease */ + uint32_t y = ntohl(lease.yiaddr); + if (y >= server_config.start_ip && y <= server_config.end_ip) { + lease.expires = ntohl(lease.expires); + if (!server_config.remaining) + lease.expires -= time(NULL); + 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..ff52da9 --- /dev/null +++ b/networking/udhcp/leases.c @@ -0,0 +1,149 @@ +/* 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" + + +/* Find the oldest expired lease, NULL if there are no expired leases */ +static struct dhcpOfferedAddr *oldest_expired_lease(void) +{ + struct dhcpOfferedAddr *oldest = NULL; +// TODO: use monotonic_sec() + unsigned long oldest_lease = time(0); + unsigned 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; +} + + +/* clear every lease out that chaddr OR yiaddr matches and is nonzero */ +static void clear_lease(const uint8_t *chaddr, uint32_t yiaddr) +{ + unsigned i, j; + + for (j = 0; j < 16 && !chaddr[j]; j++) + continue; + + for (i = 0; i < server_config.max_leases; i++) + if ((j != 16 && memcmp(leases[i].chaddr, chaddr, 16) == 0) + || (yiaddr && leases[i].yiaddr == yiaddr) + ) { + memset(&(leases[i]), 0, sizeof(leases[i])); + } +} + + +/* add a lease into the table, clearing out any old ones */ +struct dhcpOfferedAddr* FAST_FUNC add_lease(const 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 FAST_FUNC lease_expired(struct dhcpOfferedAddr *lease) +{ + return (lease->expires < (unsigned long) time(0)); +} + + +/* Find the first lease that matches chaddr, NULL if no match */ +struct dhcpOfferedAddr* FAST_FUNC find_lease_by_chaddr(const uint8_t *chaddr) +{ + unsigned 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* FAST_FUNC find_lease_by_yiaddr(uint32_t yiaddr) +{ + unsigned 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 nobody_responds_to_arp(uint32_t addr) +{ + /* 16 zero bytes */ + static const uint8_t blank_chaddr[16] = { 0 }; + /* = { 0 } helps gcc to put it in rodata, not bss */ + + struct in_addr temp; + int r; + + r = arpping(addr, server_config.server, server_config.arp, server_config.interface); + if (r) + return r; + + temp.s_addr = addr; + bb_info_msg("%s belongs to someone, reserving it for %u seconds", + inet_ntoa(temp), (unsigned)server_config.conflict_time); + add_lease(blank_chaddr, addr, server_config.conflict_time); + return 0; +} + + +/* find an assignable address, if check_expired is true, we check all the expired leases as well. + * Maybe this should try expired leases by age... */ +uint32_t FAST_FUNC find_address(int check_expired) +{ + uint32_t addr, ret; + struct dhcpOfferedAddr *lease = NULL; + + addr = server_config.start_ip; /* addr is in host order here */ + for (; addr <= server_config.end_ip; 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 assigned as a static lease */ + ret = htonl(addr); + if (!reservedIp(server_config.static_leases, ret)) { + /* lease is not taken */ + 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))) + && nobody_responds_to_arp(ret) /* it isn't used on the network */ + ) { + return ret; + } + } + } + return 0; +} diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c new file mode 100644 index 0000000..6bf99e2 --- /dev/null +++ b/networking/udhcp/options.c @@ -0,0 +1,234 @@ +/* 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[] = { + /* flags code */ + { OPTION_IP | OPTION_REQ, 0x01 }, /* DHCP_SUBNET */ + { OPTION_S32 , 0x02 }, /* DHCP_TIME_OFFSET */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03 }, /* DHCP_ROUTER */ + { OPTION_IP | OPTION_LIST , 0x04 }, /* DHCP_TIME_SERVER */ + { OPTION_IP | OPTION_LIST , 0x05 }, /* DHCP_NAME_SERVER */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06 }, /* DHCP_DNS_SERVER */ + { OPTION_IP | OPTION_LIST , 0x07 }, /* DHCP_LOG_SERVER */ + { OPTION_IP | OPTION_LIST , 0x08 }, /* DHCP_COOKIE_SERVER */ + { OPTION_IP | OPTION_LIST , 0x09 }, /* DHCP_LPR_SERVER */ + { OPTION_STRING | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */ + { OPTION_U16 , 0x0d }, /* DHCP_BOOT_SIZE */ + { OPTION_STRING | OPTION_LIST | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME */ + { OPTION_IP , 0x10 }, /* DHCP_SWAP_SERVER */ + { OPTION_STRING , 0x11 }, /* DHCP_ROOT_PATH */ + { OPTION_U8 , 0x17 }, /* DHCP_IP_TTL */ + { OPTION_U16 , 0x1a }, /* DHCP_MTU */ + { OPTION_IP | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST */ + { OPTION_STRING , 0x28 }, /* nisdomain */ + { OPTION_IP | OPTION_LIST , 0x29 }, /* nissrv */ + { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER */ + { OPTION_IP | OPTION_LIST , 0x2c }, /* DHCP_WINS_SERVER */ + { OPTION_IP , 0x32 }, /* DHCP_REQUESTED_IP */ + { OPTION_U32 , 0x33 }, /* DHCP_LEASE_TIME */ + { OPTION_U8 , 0x35 }, /* dhcptype */ + { OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */ + { OPTION_STRING , 0x38 }, /* DHCP_MESSAGE */ + { OPTION_STRING , 0x3C }, /* DHCP_VENDOR */ + { OPTION_STRING , 0x3D }, /* DHCP_CLIENT_ID */ + { OPTION_STRING , 0x42 }, /* tftp */ + { OPTION_STRING , 0x43 }, /* bootfile */ + { OPTION_STRING , 0x4D }, /* userclass */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + { OPTION_STR1035 | OPTION_LIST , 0x77 }, /* search */ +#endif + /* MSIE's "Web Proxy Autodiscovery Protocol" support */ + { OPTION_STRING , 0xfc }, /* wpad */ + + /* Options below have no match in dhcp_option_strings[], + * are not passed to dhcpc scripts, and cannot be specified + * with "option XXX YYY" syntax in dhcpd config file. */ + + { OPTION_U16 , 0x39 }, /* DHCP_MAX_SIZE */ + { } /* zeroed terminating entry */ +}; + +/* Used for converting options from incoming packets to env variables + * for udhcpc stript */ +/* Must match dhcp_options[] order */ +const char dhcp_option_strings[] ALIGN1 = + "subnet" "\0" /* DHCP_SUBNET */ + "timezone" "\0" /* DHCP_TIME_OFFSET */ + "router" "\0" /* DHCP_ROUTER */ + "timesrv" "\0" /* DHCP_TIME_SERVER */ + "namesrv" "\0" /* DHCP_NAME_SERVER */ + "dns" "\0" /* DHCP_DNS_SERVER */ + "logsrv" "\0" /* DHCP_LOG_SERVER */ + "cookiesrv" "\0" /* DHCP_COOKIE_SERVER */ + "lprsrv" "\0" /* DHCP_LPR_SERVER */ + "hostname" "\0" /* DHCP_HOST_NAME */ + "bootsize" "\0" /* DHCP_BOOT_SIZE */ + "domain" "\0" /* DHCP_DOMAIN_NAME */ + "swapsrv" "\0" /* DHCP_SWAP_SERVER */ + "rootpath" "\0" /* DHCP_ROOT_PATH */ + "ipttl" "\0" /* DHCP_IP_TTL */ + "mtu" "\0" /* DHCP_MTU */ + "broadcast" "\0" /* DHCP_BROADCAST */ + "nisdomain" "\0" /* */ + "nissrv" "\0" /* */ + "ntpsrv" "\0" /* DHCP_NTP_SERVER */ + "wins" "\0" /* DHCP_WINS_SERVER */ + "requestip" "\0" /* DHCP_REQUESTED_IP */ + "lease" "\0" /* DHCP_LEASE_TIME */ + "dhcptype" "\0" /* */ + "serverid" "\0" /* DHCP_SERVER_ID */ + "message" "\0" /* DHCP_MESSAGE */ + "vendorclass" "\0" /* DHCP_VENDOR */ + "clientid" "\0" /* DHCP_CLIENT_ID */ + "tftp" "\0" + "bootfile" "\0" + "userclass" "\0" +#if ENABLE_FEATURE_UDHCP_RFC3397 + "search" "\0" +#endif + /* MSIE's "Web Proxy Autodiscovery Protocol" support */ + "wpad" "\0" + ; + + +/* Lengths of the different option types */ +const uint8_t dhcp_option_lengths[] ALIGN1 = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, + [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, +#if ENABLE_FEATURE_UDHCP_RFC3397 + [OPTION_STR1035] = 1, +#endif + [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* FAST_FUNC get_option(struct dhcpMessage *packet, int code) +{ + int i, length; + uint8_t *optionptr; + int over = 0; + int curr = OPTION_FIELD; + + optionptr = packet->options; + i = 0; + length = sizeof(packet->options); + while (1) { + 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 = sizeof(packet->file); + curr = FILE_FIELD; + } else if (curr == FILE_FIELD && (over & SNAME_FIELD)) { + optionptr = packet->sname; + i = 0; + length = sizeof(packet->sname); + curr = SNAME_FIELD; + } else + return NULL; + break; + default: + i += optionptr[OPT_LEN + i] + 2; + } + } + return NULL; +} + + +/* return the position of the 'end' option (no bounds checking) */ +int FAST_FUNC 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 FAST_FUNC 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 >= DHCP_OPTIONS_BUFSIZE) { + 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 FAST_FUNC 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 = dhcp_option_lengths[dh->flags & TYPE_MASK]; + option[OPT_LEN] = len; + if (BB_BIG_ENDIAN) + data <<= 8 * (4 - len); + /* This memcpy is for 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..d18a353 --- /dev/null +++ b/networking/udhcp/options.h @@ -0,0 +1,121 @@ +/* vi: set sw=4 ts=4: */ +/* options.h */ +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +#define TYPE_MASK 0x0F + +enum { + OPTION_IP = 1, + OPTION_IP_PAIR, + OPTION_STRING, +#if ENABLE_FEATURE_UDHCP_RFC3397 + OPTION_STR1035, /* RFC1035 compressed domain name list */ +#endif + 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 */ + +/*****************************************************************/ +/* Do not modify below here unless you know what you are doing!! */ +/*****************************************************************/ + +/* DHCP protocol -- see RFC 2131 */ +#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 /* client -> server */ +#define DHCPOFFER 2 /* client <- server */ +#define DHCPREQUEST 3 /* client -> server */ +#define DHCPDECLINE 4 /* client -> server */ +#define DHCPACK 5 /* client <- server */ +#define DHCPNAK 6 /* client <- server */ +#define DHCPRELEASE 7 /* client -> server */ +#define DHCPINFORM 8 /* client -> server */ + +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* miscellaneous defines */ +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 + +struct dhcp_option { + uint8_t flags; + uint8_t code; +}; + +extern const struct dhcp_option dhcp_options[]; +extern const char dhcp_option_strings[]; +extern const uint8_t dhcp_option_lengths[]; + +uint8_t *get_option(struct dhcpMessage *packet, int code) FAST_FUNC; +int end_option(uint8_t *optionptr) FAST_FUNC; +int add_option_string(uint8_t *optionptr, uint8_t *string) FAST_FUNC; +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) FAST_FUNC; +#if ENABLE_FEATURE_UDHCP_RFC3397 +char *dname_dec(const uint8_t *cstr, int clen, const char *pre) FAST_FUNC; +uint8_t *dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen) FAST_FUNC; +#endif + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif + +#endif diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c new file mode 100644 index 0000000..1a6f7e6 --- /dev/null +++ b/networking/udhcp/packet.c @@ -0,0 +1,238 @@ +/* vi: set sw=4 ts=4: */ + +#include <netinet/in.h> +#if (defined(__GLIBC__) && __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 FAST_FUNC udhcp_init_header(struct dhcpMessage *packet, char type) +{ + memset(packet, 0, sizeof(struct dhcpMessage)); + packet->op = BOOTREQUEST; /* if client to a server */ + switch (type) { + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; /* if server to client */ + } + 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 FAST_FUNC udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd) +{ + int bytes; + unsigned char *vendor; + + memset(packet, 0, sizeof(*packet)); + bytes = safe_read(fd, packet, sizeof(*packet)); + if (bytes < 0) { + DEBUG("cannot read on listening socket, ignoring"); + return bytes; /* returns -1 */ + } + + if (packet->cookie != htonl(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); + if (vendor) { +#if 0 + static const char broken_vendors[][8] = { + "MSFT 98", + "" + }; + int i; + 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 replies", + broken_vendors[i]); + packet->flags |= htons(BROADCAST_FLAG); + } + } +#else + if (vendor[OPT_LEN - 2] == (uint8_t)(sizeof("MSFT 98")-1) + && memcmp(vendor, "MSFT 98", sizeof("MSFT 98")-1) == 0 + ) { + DEBUG("broken client (%s), forcing broadcast replies", "MSFT 98"); + packet->flags |= htons(BROADCAST_FLAG); + } +#endif + } + } + + return bytes; +} + + +uint16_t FAST_FUNC 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, send packet */ +int FAST_FUNC udhcp_send_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, const uint8_t *dest_arp, + int ifindex) +{ + struct sockaddr_ll dest; + struct udp_dhcp_packet packet; + int fd; + int result = -1; + const char *msg; + + enum { + IP_UPD_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, + UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE - offsetof(struct udp_dhcp_packet, udp), + }; + + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + msg = "socket(%s)"; + goto ret_msg; + } + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + packet.data = *payload; /* struct copy */ + + 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(dest)) < 0) { + msg = "bind(%s)"; + goto ret_close; + } + + 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); + /* size, excluding IP header: */ + packet.udp.len = htons(UPD_DHCP_SIZE); + /* for UDP checksumming, ip.len is set to UDP packet len */ + packet.ip.tot_len = packet.udp.len; + packet.udp.check = udhcp_checksum(&packet, IP_UPD_DHCP_SIZE); + /* but for sending, it is set to IP packet len */ + packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE); + 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)); + + /* Currently we send full-sized DHCP packets (zero padded). + * If you need to change this: last byte of the packet is + * packet.data.options[end_option(packet.data.options)] + */ + result = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0, + (struct sockaddr *) &dest, sizeof(dest)); + msg = "sendto"; + ret_close: + close(fd); + if (result < 0) { + ret_msg: + bb_perror_msg(msg, "PACKET"); + } + return result; +} + + +/* Let the kernel do all the work for packet generation */ +int FAST_FUNC udhcp_send_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) +{ + struct sockaddr_in client; + int fd; + int result = -1; + const char *msg; + + enum { + DHCP_SIZE = sizeof(struct dhcpMessage) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, + }; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + msg = "socket(%s)"; + goto ret_msg; + } + setsockopt_reuseaddr(fd); + + 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(client)) == -1) { + msg = "bind(%s)"; + goto ret_close; + } + + 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(client)) == -1) { + msg = "connect"; + goto ret_close; + } + + /* Currently we send full-sized DHCP packets (see above) */ + result = safe_write(fd, payload, DHCP_SIZE); + msg = "write"; + ret_close: + close(fd); + if (result < 0) { + ret_msg: + bb_perror_msg(msg, "UDP"); + } + return result; +} diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c new file mode 100644 index 0000000..8dff9b7 --- /dev/null +++ b/networking/udhcp/script.c @@ -0,0 +1,239 @@ +/* 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 "dhcpc.h" +#include "options.h" + + +/* get a rough idea of how long an option will be (rounding up...) */ +static const uint8_t max_option_length[] = { + [OPTION_IP] = sizeof("255.255.255.255 "), + [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, + [OPTION_STRING] = 1, +#if ENABLE_FEATURE_UDHCP_RFC3397 + [OPTION_STR1035] = 1, +#endif + [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 / dhcp_option_lengths[opt_index]); +} + + +static int sprintip(char *dest, const char *pre, const 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(uint32_t mask) +{ + int i = 0; + mask = ntohl(mask); /* 111110000-like bit pattern */ + while (mask) { + i++; + mask <<= 1; + } + return i; +} + + +/* Allocate and fill with the text of option 'option'. */ +static char *alloc_fill_opts(uint8_t *option, const struct dhcp_option *type_p, const char *opt_name) +{ + int len, type, optlen; + uint16_t val_u16; + int16_t val_s16; + uint32_t val_u32; + int32_t val_s32; + char *dest, *ret; + + len = option[OPT_LEN - 2]; + type = type_p->flags & TYPE_MASK; + optlen = dhcp_option_lengths[type]; + + dest = ret = xmalloc(upper_length(len, type) + strlen(opt_name) + 2); + dest += sprintf(ret, "%s=", opt_name); + + 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 ret; /* Short circuit this case */ +#if ENABLE_FEATURE_UDHCP_RFC3397 + case OPTION_STR1035: + /* unpack option into dest; use ret for prefix (i.e., "optname=") */ + dest = dname_dec(option, len, ret); + free(ret); + return dest; +#endif + } + option += optlen; + len -= optlen; + if (len <= 0) break; + dest += sprintf(dest, " "); + } + return ret; +} + + +/* put all the parameters into an environment */ +static char **fill_envp(struct dhcpMessage *packet) +{ + int num_options = 0; + int i, j; + char **envp; + char *var; + const char *opt_name; + uint8_t *temp; + char over = 0; + + if (packet) { + 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++; + temp = get_option(packet, DHCP_OPTION_OVER); + if (temp) + 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); + var = getenv("PATH"); + if (var) + envp[j++] = xasprintf("PATH=%s", var); + var = getenv("HOME"); + if (var) + envp[j++] = xasprintf("HOME=%s", var); + + if (packet == NULL) + return envp; + + envp[j] = xmalloc(sizeof("ip=255.255.255.255")); + sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr); + + opt_name = dhcp_option_strings; + i = 0; + while (*opt_name) { + temp = get_option(packet, dhcp_options[i].code); + if (!temp) + goto next; + envp[j++] = alloc_fill_opts(temp, &dhcp_options[i], opt_name); + + /* Fill in a subnet bits option for things like /24 */ + if (dhcp_options[i].code == DHCP_SUBNET) { + uint32_t subnet; + memcpy(&subnet, temp, 4); + envp[j++] = xasprintf("mask=%d", mton(subnet)); + } + next: + opt_name += strlen(opt_name) + 1; + i++; + } + 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 FAST_FUNC 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 */ +// can we use wait4pid(spawn(...)) here? + pid = vfork(); + if (pid < 0) return; + if (pid == 0) { + /* close fd's? */ + /* exec script */ + execle(client_config.script, client_config.script, + name, NULL, envp); + bb_perror_msg_and_die("exec %s", client_config.script); + } + safe_waitpid(pid, NULL, 0); + for (curr = envp; *curr; curr++) + free(*curr); + free(envp); +} diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c new file mode 100644 index 0000000..dcc234c --- /dev/null +++ b/networking/udhcp/serverpacket.c @@ -0,0 +1,266 @@ +/* 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 "dhcpc.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_send_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) +{ + const 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 (payload->flags & htons(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_send_raw_packet(payload, + /*src*/ server_config.server, SERVER_PORT, + /*dst*/ 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) +{ + if (payload->giaddr) + return send_packet_to_relay(payload); + return send_packet_to_client(payload, force_broadcast); +} + + +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 FAST_FUNC send_offer(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) >= server_config.start_ip + && ntohl(req_align) <= server_config.end_ip + && !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 FAST_FUNC send_NAK(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + + init_packet(&packet, oldpacket, DHCPNAK); + + DEBUG("Sending NAK"); + return send_packet(&packet, 1); +} + + +int FAST_FUNC send_ACK(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; + + 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; + 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); + if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) { + /* rewrite the file with leases at every new acceptance */ + write_leases(); + } + + return 0; +} + + +int FAST_FUNC 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..a025bd8 --- /dev/null +++ b/networking/udhcp/signalpipe.c @@ -0,0 +1,82 @@ +/* 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 struct fd_pair signal_pipe; + +static void signal_handler(int sig) +{ + unsigned char ch = sig; /* use char, avoid dealing with partial writes */ + if (write(signal_pipe.wr, &ch, 1) != 1) + bb_perror_msg("cannot send signal"); +} + + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void FAST_FUNC udhcp_sp_setup(void) +{ + /* was socketpair, but it needs AF_UNIX in kernel */ + xpiped_pair(signal_pipe); + close_on_exec_on(signal_pipe.rd); + close_on_exec_on(signal_pipe.wr); + ndelay_on(signal_pipe.wr); + bb_signals(0 + + (1 << SIGUSR1) + + (1 << SIGUSR2) + + (1 << 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 FAST_FUNC udhcp_sp_fd_set(fd_set *rfds, int extra_fd) +{ + FD_ZERO(rfds); + FD_SET(signal_pipe.rd, rfds); + if (extra_fd >= 0) { + close_on_exec_on(extra_fd); + FD_SET(extra_fd, rfds); + } + return signal_pipe.rd > extra_fd ? signal_pipe.rd : 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 FAST_FUNC udhcp_sp_read(const fd_set *rfds) +{ + unsigned char sig; + + if (!FD_ISSET(signal_pipe.rd, rfds)) + return 0; + + if (safe_read(signal_pipe.rd, &sig, 1) != 1) + return -1; + + return sig; +} diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c new file mode 100644 index 0000000..385d5c3 --- /dev/null +++ b/networking/udhcp/socket.c @@ -0,0 +1,111 @@ +/* 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 (defined(__GLIBC__) && __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 FAST_FUNC udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) +{ + int fd; + struct ifreq ifr; + struct sockaddr_in *our_ip; + + memset(&ifr, 0, sizeof(ifr)); + fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); + if (addr) { + if (ioctl_or_perror(fd, SIOCGIFADDR, &ifr, + "is interface %s up and configured?", interface) + ) { + 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_or_warn(fd, SIOCGIFINDEX, &ifr) != 0) { + close(fd); + return -1; + } + DEBUG("adapter index %d", ifr.ifr_ifindex); + *ifindex = ifr.ifr_ifindex; + } + + if (arp) { + if (ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr) != 0) { + 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]); + } + + close(fd); + return 0; +} + +/* 1. None of the callers expects it to ever fail */ +/* 2. ip was always INADDR_ANY */ +int FAST_FUNC udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) +{ + int fd; + struct sockaddr_in addr; + + DEBUG("Opening listen socket on *:%d %s", port, inf); + fd = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + + setsockopt_reuseaddr(fd); + if (setsockopt_broadcast(fd) == -1) + bb_perror_msg_and_die("SO_BROADCAST"); + + /* NB: bug 1032 says this doesn't work on ethernet aliases (ethN:M) */ + if (setsockopt_bindtodevice(fd, inf)) + xfunc_die(); /* warning is already printed */ + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + /* addr.sin_addr.s_addr = ip; - all-zeros is INADDR_ANY */ + xbind(fd, (struct sockaddr *)&addr, sizeof(addr)); + + return fd; +} diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c new file mode 100644 index 0000000..43f1c98 --- /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 FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC 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_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 FAST_FUNC 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..3f12e76 --- /dev/null +++ b/networking/vconfig.c @@ -0,0 +1,161 @@ +/* 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 "libbb.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[] ALIGN1 = { + 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[] ALIGN1 = { + 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[] ALIGN1 = "/proc/net/vlan/config"; + +int vconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +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 { + strncpy(ifr.device1, argv[1], IFNAMSIZ); + 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); + ioctl_or_perror_and_die(fd, SIOCSIFVLAN, &ifr, + "ioctl error for %s", *argv); + + return 0; +} diff --git a/networking/wget.c b/networking/wget.c new file mode 100644 index 0000000..23143d5 --- /dev/null +++ b/networking/wget.c @@ -0,0 +1,836 @@ +/* vi: set sw=4 ts=4: */ +/* + * wget - retrieve a file using HTTP or FTP + * + * Chip Rosenthal Covad Communications <chip@laserlink.net> + * + */ + +#include "libbb.h" + +struct host_info { + // May be used if we ever will want to free() all xstrdup()s... + /* char *allocated; */ + const char *path; + const char *user; + char *host; + int port; + smallint is_ftp; +}; + + +/* Globals (can be accessed from signal handlers) */ +struct globals { + off_t content_len; /* Content-length of the file */ + off_t beg_range; /* Range at which continue begins */ +#if ENABLE_FEATURE_WGET_STATUSBAR + off_t lastsize; + off_t totalsize; + off_t transferred; /* Number of bytes transferred so far */ + const char *curfile; /* Name of current file being transferred */ + unsigned lastupdate_sec; + unsigned start_sec; +#endif + smallint chunked; /* chunked transfer encoding */ +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +struct BUG_G_too_big { + char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1]; +}; +#define content_len (G.content_len ) +#define beg_range (G.beg_range ) +#define lastsize (G.lastsize ) +#define totalsize (G.totalsize ) +#define transferred (G.transferred ) +#define curfile (G.curfile ) +#define lastupdate_sec (G.lastupdate_sec ) +#define start_sec (G.start_sec ) +#define chunked (G.chunked ) +#define INIT_G() do { } while (0) + + +#if ENABLE_FEATURE_WGET_STATUSBAR +enum { + STALLTIME = 5 /* Seconds when xfer considered "stalled" */ +}; + +static unsigned int getttywidth(void) +{ + unsigned width; + get_terminal_width_height(0, &width, NULL); + return width; +} + +static void progressmeter(int flag) +{ + /* We can be called from signal handler */ + int save_errno = errno; + off_t abbrevsize; + unsigned since_last_update, elapsed; + unsigned ratio; + int barlength, i; + + if (flag == -1) { /* first call to progressmeter */ + start_sec = monotonic_sec(); + lastupdate_sec = start_sec; + lastsize = 0; + totalsize = content_len + beg_range; /* as content_len changes.. */ + } + + ratio = 100; + if (totalsize != 0 && !chunked) { + /* long long helps to have it working even if !LFS */ + ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize); + if (ratio > 100) ratio = 100; + } + + fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); + + barlength = getttywidth() - 49; + if (barlength > 0) { + /* god bless gcc for variable arrays :) */ + i = barlength * ratio / 100; + { + char buf[i+1]; + memset(buf, '*', i); + buf[i] = '\0'; + fprintf(stderr, "|%s%*s|", buf, barlength - i, ""); + } + } + i = 0; + abbrevsize = transferred + beg_range; + while (abbrevsize >= 100000) { + i++; + abbrevsize >>= 10; + } + /* see http://en.wikipedia.org/wiki/Tera */ + fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]); + +// Nuts! Ain't it easier to update progress meter ONLY when we transferred++? + + elapsed = monotonic_sec(); + since_last_update = elapsed - lastupdate_sec; + if (transferred > lastsize) { + lastupdate_sec = elapsed; + lastsize = transferred; + if (since_last_update >= STALLTIME) { + /* We "cut off" these seconds from elapsed time + * by adjusting start time */ + start_sec += since_last_update; + } + since_last_update = 0; /* we are un-stalled now */ + } + elapsed -= start_sec; /* now it's "elapsed since start" */ + + if (since_last_update >= STALLTIME) { + fprintf(stderr, " - stalled -"); + } else { + off_t to_download = totalsize - beg_range; + if (transferred <= 0 || (int)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 == 0) { + /* last call to progressmeter */ + alarm(0); + transferred = 0; + fputc('\n', stderr); + } else { + if (flag == -1) { /* first call to progressmeter */ + signal_SA_RESTART_empty_mask(SIGALRM, progressmeter); + } + alarm(1); + } + + errno = save_errno; +} +/* 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. + * + */ +#else /* FEATURE_WGET_STATUSBAR */ + +static ALWAYS_INLINE void progressmeter(int flag UNUSED_PARAM) { } + +#endif + + +/* Read NMEMB bytes into PTR from STREAM. Returns the number of bytes read, + * and a short count if an eof or non-interrupt error is encountered. */ +static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream) +{ + size_t ret; + char *p = (char*)ptr; + + do { + clearerr(stream); + ret = fread(p, 1, nmemb, stream); + p += ret; + nmemb -= ret; + } while (nmemb && ferror(stream) && errno == EINTR); + + return p - (char*)ptr; +} + +/* 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. buf is assumed to be char buf[512]. */ +static char *base64enc_512(char buf[512], const char *str) +{ + unsigned len = strlen(str); + if (len > 512/4*3 - 10) /* paranoia */ + len = 512/4*3 - 10; + bb_uuencode(buf, str, len, bb_uuenc_tbl_base64); + return buf; +} +#endif + + +static FILE *open_socket(len_and_sockaddr *lsa) +{ + FILE *fp; + + /* glibc 2.4 seems to try seeking on it - ??! */ + /* hopefully it understands what ESPIPE means... */ + fp = fdopen(xconnect_stream(lsa), "r+"); + if (fp == NULL) + bb_perror_msg_and_die("fdopen"); + + return fp; +} + + +static int ftpcmd(const char *s1, const 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; +} + + +static void parse_url(char *src_url, struct host_info *h) +{ + char *url, *p, *sp; + + /* 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 + 1; + } 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; + } + + sp = strrchr(h->host, '@'); + h->user = NULL; + if (sp != NULL) { + h->user = h->host; + *sp = '\0'; + h->host = sp + 1; + } + + sp = h->host; +} + + +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) + continue; + if (*s == '\n') + return NULL; + + /* convert the header name to lower case */ + for (s = buf; isalnum(*s) || *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 */ + *s++ = '\0'; + hdrval = skip_whitespace(s); + + /* locate the end of header */ + while (*s && *s != '\r' && *s != '\n') + ++s; + + /* end of header found */ + if (*s) { + *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') + continue; + /* *istrunc = 1; */ + return hdrval; +} + + +int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int wget_main(int argc UNUSED_PARAM, char **argv) +{ + char buf[512]; + struct host_info server, target; + len_and_sockaddr *lsa; + int status; + int port; + int try = 5; + unsigned opt; + char *str; + char *proxy = 0; + char *dir_prefix = NULL; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + char *extra_headers = NULL; + llist_t *headers_llist = NULL; +#endif + FILE *sfp = NULL; /* socket to web/ftp server */ + FILE *dfp; /* socket to ftp server (data) */ + char *fname_out; /* where to direct output (-O) */ + bool got_clen = 0; /* got content-length: from server */ + int output_fd = -1; + bool 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";/* "User-Agent" header field */ + + static const char keywords[] ALIGN1 = + "content-length\0""transfer-encoding\0""chunked\0""location\0"; + enum { + KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location + }; + enum { + WGET_OPT_CONTINUE = (1 << 0), + WGET_OPT_SPIDER = (1 << 1), + WGET_OPT_QUIET = (1 << 2), + WGET_OPT_OUTNAME = (1 << 3), + WGET_OPT_PREFIX = (1 << 4), + WGET_OPT_PROXY = (1 << 5), + WGET_OPT_USER_AGENT = (1 << 6), + WGET_OPT_RETRIES = (1 << 7), + WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8), + WGET_OPT_PASSIVE = (1 << 9), + WGET_OPT_HEADER = (1 << 10), + }; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + static const char wget_longopts[] ALIGN1 = + /* name, has_arg, val */ + "continue\0" No_argument "c" + "spider\0" No_argument "s" + "quiet\0" No_argument "q" + "output-document\0" Required_argument "O" + "directory-prefix\0" Required_argument "P" + "proxy\0" Required_argument "Y" + "user-agent\0" Required_argument "U" + /* Ignored: */ + // "tries\0" Required_argument "t" + // "timeout\0" Required_argument "T" + /* Ignored (we always use PASV): */ + "passive-ftp\0" No_argument "\xff" + "header\0" Required_argument "\xfe" + ; +#endif + + INIT_G(); + +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + applet_long_options = wget_longopts; +#endif + /* server.allocated = target.allocated = NULL; */ + opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); + opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:", + &fname_out, &dir_prefix, + &proxy_flag, &user_agent, + NULL, /* -t RETRIES */ + NULL /* -T NETWORK_READ_TIMEOUT */ + 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; + while (ll) { + size += strlen(ll->data) + 2; + ll = ll->link; + } + extra_headers = cp = xmalloc(size); + while (headers_llist) { + cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist)); + } + } +#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 there was no -O FILE */ + if (!(opt & WGET_OPT_OUTNAME)) { + fname_out = bb_get_last_path_component_nostrip(target.path); + /* handle "wget http://kernel.org//" */ + if (fname_out[0] == '/' || !fname_out[0]) + fname_out = (char*)"index.html"; + /* -P DIR is considered only if there was no -O FILE */ + if (dir_prefix) + fname_out = concat_path_file(dir_prefix, fname_out); + } else { + if (LONE_DASH(fname_out)) { + /* -O - */ + output_fd = 1; + opt &= ~WGET_OPT_CONTINUE; + } + } +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = bb_get_last_path_component_nostrip(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 (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... */ + lsa = xhost2sockaddr(server.host, server.port); + if (!(opt & WGET_OPT_QUIET)) { + fprintf(stderr, "Connecting to %s (%s)\n", server.host, + xmalloc_sockaddr2dotted(&lsa->u.sa)); + /* We leak result of xmalloc_sockaddr2dotted */ + } + + if (use_proxy || !target.is_ftp) { + /* + * HTTP session + */ + do { + got_clen = 0; + chunked = 0; + + if (!--try) + bb_error_msg_and_die("too many redirections"); + + /* Open socket to http server */ + if (sfp) fclose(sfp); + sfp = open_socket(lsa); + + /* Send HTTP request. */ + if (use_proxy) { + fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n", + target.is_ftp ? "f" : "ht", target.host, + 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, "Proxy-Authorization: Basic %s\r\n"+6, + base64enc_512(buf, target.user)); + } + if (use_proxy && server.user) { + fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", + base64enc_512(buf, server.user)); + } +#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"); + + str = buf; + str = skip_non_whitespace(str); + str = skip_whitespace(str); + // FIXME: no error check + // xatou wouldn't work: "200 OK" + status = atoi(str); + switch (status) { + case 0: + case 100: + while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL) + /* eat all remaining headers */; + goto read_response; + case 200: +/* +Response 204 doesn't say "null file", it says "metadata +has changed but data didn't": + +"10.2.5 204 No Content +The server has fulfilled the request but does not need to return +an entity-body, and might want to return updated metainformation. +The response MAY include new or updated metainformation in the form +of entity-headers, which if present SHOULD be associated with +the requested variant. + +If the client is a user agent, it SHOULD NOT change its document +view from that which caused the request to be sent. This response +is primarily intended to allow input for actions to take place +without causing a change to the user agent's active document view, +although any new or updated metainformation SHOULD be applied +to the document currently in the user agent's active view. + +The 204 response MUST NOT include a message-body, and thus +is always terminated by the first empty line after the header fields." + +However, in real world it was observed that some web servers +(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero. +*/ + case 204: + break; + case 300: /* redirection */ + case 301: + case 302: + case 303: + break; + case 206: + if (beg_range) + break; + /* fall through */ + 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 ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) { + /* gethdr did already convert the "FOO:" string to lowercase */ + smalluint key = index_in_strings(keywords, *&buf) + 1; + if (key == KEY_content_length) { + content_len = BB_STRTOOFF(str, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("content-length %s is garbage", str); + } + got_clen = 1; + continue; + } + if (key == KEY_transfer_encoding) { + if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked) + bb_error_msg_and_die("transfer encoding '%s' is not supported", str); + chunked = got_clen = 1; + } + if (key == KEY_location) { + if (str[0] == '/') + /* free(target.allocated); */ + target.path = /* target.allocated = */ xstrdup(str+1); + else { + parse_url(str, &target); + if (use_proxy == 0) { + server.host = target.host; + server.port = target.port; + } + free(lsa); + lsa = xhost2sockaddr(server.host, server.port); + break; + } + } + } + } while (status >= 300); + + dfp = sfp; + + } else { + + /* + * FTP session + */ + if (!target.user) + target.user = xstrdup("anonymous:busybox@"); + + sfp = open_socket(lsa); + if (ftpcmd(NULL, NULL, sfp, buf) != 220) + bb_error_msg_and_die("%s", buf+4); + + /* + * Splitting username:password pair, + * trying to log in + */ + str = strchr(target.user, ':'); + if (str) + *(str++) = '\0'; + switch (ftpcmd("USER ", target.user, sfp, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", str, sfp, buf) == 230) + break; + /* fall through (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 + str = strrchr(buf, ')'); + if (str) str[0] = '\0'; + str = strrchr(buf, ','); + if (!str) goto pasv_error; + port = xatou_range(str+1, 0, 255); + *str = '\0'; + str = strrchr(buf, ','); + if (!str) goto pasv_error; + port += xatou_range(str+1, 0, 255) * 256; + set_nport(lsa, htons(port)); + dfp = open_socket(lsa); + + 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 %s: %s", "RETR", buf); + } + + if (opt & WGET_OPT_SPIDER) { + if (ENABLE_FEATURE_CLEAN_UP) + fclose(sfp); + return EXIT_SUCCESS; + } + + /* + * Retrieve file + */ + + /* Do it before progressmeter (want to have nice error message) */ + if (output_fd < 0) { + int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL; + /* compat with wget: -O FILE can overwrite */ + if (opt & WGET_OPT_OUTNAME) + o_flags = O_WRONLY | O_CREAT | O_TRUNC; + output_fd = xopen(fname_out, o_flags); + } + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(-1); + + if (chunked) + goto get_clen; + + /* Loops only if chunked */ + while (1) { + while (content_len > 0 || !got_clen) { + int n; + unsigned rdsz = sizeof(buf); + + if (content_len < sizeof(buf) && (chunked || got_clen)) + rdsz = (unsigned)content_len; + n = safe_fread(buf, rdsz, dfp); + if (n <= 0) { + if (ferror(dfp)) { + /* perror will not work: ferror doesn't set errno */ + bb_error_msg_and_die(bb_msg_read_error); + } + break; + } + xwrite(output_fd, buf, n); +#if ENABLE_FEATURE_WGET_STATUSBAR + transferred += n; +#endif + if (got_clen) + content_len -= n; + } + + if (!chunked) + break; + + safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ + get_clen: + safe_fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check? */ + if (content_len == 0) + break; /* all done! */ + } + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(0); + + 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); + } + + return EXIT_SUCCESS; +} diff --git a/networking/zcip.c b/networking/zcip.c new file mode 100644 index 0000000..ff9c83d --- /dev/null +++ b/networking/zcip.c @@ -0,0 +1,566 @@ +/* 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 <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> + +#include "libbb.h" +#include <syslog.h> + +/* We don't need more than 32 bits of the counter */ +#define MONOTONIC_US() ((unsigned)monotonic_us()) + +struct arp_packet { + struct ether_header eth; + struct ether_arp arp; +} 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(...) do { } while (0) + + +enum { + sock_fd = 3 +}; + +struct globals { + struct sockaddr saddr; + struct ether_addr eth_addr; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define saddr (G.saddr ) +#define eth_addr (G.eth_addr) + + +/** + * Pick a random link local IP address on 169.254/16, except that + * the first and last 256 addresses are reserved. + */ +static uint32_t pick(void) +{ + unsigned tmp; + + do { + tmp = rand() & IN_CLASSB_HOST; + } while (tmp > (IN_CLASSB_HOST - 0x0200)); + return htonl((LINKLOCAL_ADDR + 0x0100) + tmp); +} + +/** + * Broadcast an ARP packet. + */ +static void arp( + /* int op, - always ARPOP_REQUEST */ + /* const struct ether_addr *source_eth, - always ð_addr */ + struct in_addr source_ip, + const struct ether_addr *target_eth, struct in_addr target_ip) +{ + enum { op = ARPOP_REQUEST }; +#define source_eth (ð_addr) + + struct arp_packet p; + memset(&p, 0, sizeof(p)); + + // ether header + p.eth.ether_type = htons(ETHERTYPE_ARP); + memcpy(p.eth.ether_shost, source_eth, ETH_ALEN); + memset(p.eth.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_eth, ETH_ALEN); + memcpy(&p.arp.arp_spa, &source_ip, sizeof(p.arp.arp_spa)); + memcpy(&p.arp.arp_tha, target_eth, ETH_ALEN); + memcpy(&p.arp.arp_tpa, &target_ip, sizeof(p.arp.arp_tpa)); + + // send it + // Even though sock_fd is already bound to saddr, just send() + // won't work, because "socket is not connected" + // (and connect() won't fix that, "operation not supported"). + // Thus we sendto() to saddr. I wonder which sockaddr + // (from bind() or from sendto()?) kernel actually uses + // to determine iface to emit the packet from... + xsendto(sock_fd, &p, sizeof(p), &saddr, sizeof(saddr)); +#undef source_eth +} + +/** + * Run a script. + * argv[0]:intf argv[1]:script_name argv[2]:junk argv[3]:NULL + */ +static int run(char *argv[3], const char *param, struct in_addr *ip) +{ + int status; + char *addr = addr; /* for gcc */ + const char *fmt = "%s %s %s" + 3; + + argv[2] = (char*)param; + + VDBG("%s run %s %s\n", argv[0], argv[1], argv[2]); + + if (ip) { + addr = inet_ntoa(*ip); + xsetenv("ip", addr); + fmt -= 3; + } + bb_info_msg(fmt, argv[2], argv[0], addr); + + status = wait4pid(spawn(argv + 1)); + if (status < 0) { + bb_perror_msg("%s %s %s" + 3, argv[2], argv[0]); + return -errno; + } + if (status != 0) + bb_error_msg("script %s %s failed, exitcode=%d", argv[1], argv[2], status); + return status; +} + +/** + * Return milliseconds of random delay, up to "secs" seconds. + */ +static ALWAYS_INLINE unsigned random_delay_ms(unsigned secs) +{ + return rand() % (secs * 1000); +} + +/** + * main program + */ +int zcip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int zcip_main(int argc, char **argv) +{ + int state; + char *r_opt; + unsigned opts; + + // ugly trick, but I want these zeroed in one go + struct { + const struct in_addr null_ip; + const struct ether_addr null_addr; + struct in_addr ip; + struct ifreq ifr; + int timeout_ms; /* must be signed */ + unsigned conflicts; + unsigned nprobes; + unsigned nclaims; + int ready; + int verbose; + } L; +#define null_ip (L.null_ip ) +#define null_addr (L.null_addr ) +#define ip (L.ip ) +#define ifr (L.ifr ) +#define timeout_ms (L.timeout_ms) +#define conflicts (L.conflicts ) +#define nprobes (L.nprobes ) +#define nclaims (L.nclaims ) +#define ready (L.ready ) +#define verbose (L.verbose ) + + memset(&L, 0, sizeof(L)); + +#define FOREGROUND (opts & 1) +#define QUIT (opts & 2) + // parse commandline: prog [options] ifname script + // exactly 2 args; -v accumulates and implies -f + opt_complementary = "=2:vv:vf"; + opts = getopt32(argv, "fqr:v", &r_opt, &verbose); +#if !BB_MMU + // on NOMMU reexec early (or else we will rerun things twice) + if (!FOREGROUND) + bb_daemonize_or_rexec(0 /*was: DAEMON_CHDIR_ROOT*/, argv); +#endif + // open an ARP socket + // (need to do it before openlog to prevent openlog from taking + // fd 3 (sock_fd==3)) + xmove_fd(xsocket(AF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)), sock_fd); + if (!FOREGROUND) { + // do it before all bb_xx_msg calls + openlog(applet_name, 0, LOG_DAEMON); + logmode |= LOGMODE_SYSLOG; + } + 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 - 1; + + /* Now: argv[0]:junk argv[1]:intf argv[2]:script argv[3]:NULL */ + /* We need to make space for script argument: */ + argv[0] = argv[1]; + argv[1] = argv[2]; + /* Now: argv[0]:intf argv[1]:script argv[2]:junk argv[3]:NULL */ +#define argv_intf (argv[0]) + + xsetenv("interface", argv_intf); + + // initialize the interface (modprobe, ifup, etc) + if (run(argv, "init", NULL)) + return EXIT_FAILURE; + + // initialize saddr + // saddr is: { u16 sa_family; u8 sa_data[14]; } + //memset(&saddr, 0, sizeof(saddr)); + //TODO: are we leaving sa_family == 0 (AF_UNSPEC)?! + safe_strncpy(saddr.sa_data, argv_intf, sizeof(saddr.sa_data)); + + // bind to the interface's ARP socket + xbind(sock_fd, &saddr, sizeof(saddr)); + + // get the interface's ethernet address + //memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, argv_intf, sizeof(ifr.ifr_name)); + xioctl(sock_fd, SIOCGIFHWADDR, &ifr); + 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. + // we are taking low-order four bytes, as top-order ones + // aren't random enough. + // NOTE: the sequence of addresses we try changes only + // depending on when we detect conflicts. + { + uint32_t t = get_unaligned_u32p((uint32_t *) ((char *)ð_addr + 2)); + srand(t); + } + if (ip.s_addr == 0) + ip.s_addr = pick(); + + // FIXME cases to handle: + // - zcip already running! + // - link already has local address... just defend/update + + // daemonize now; don't delay system startup + if (!FOREGROUND) { +#if BB_MMU + bb_daemonize(0 /*was: DAEMON_CHDIR_ROOT*/); +#endif + bb_info_msg("start, interface %s", argv_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 uses it + // - arp announcements that we're claiming it + // - use it + // - defend it, within limits + // exit if: + // - address is successfully obtained and -q was given: + // run "<script> config", then exit with exitcode 0 + // - poll error (when does this happen?) + // - read error (when does this happen?) + // - sendto error (in arp()) (when does this happen?) + // - revents & POLLERR (link down). run "<script> deconfig" first + state = PROBE; + while (1) { + struct pollfd fds[1]; + unsigned deadline_us; + struct arp_packet p; + int source_ip_conflict; + int target_ip_conflict; + + fds[0].fd = sock_fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + // poll, being ready to adjust current timeout + if (!timeout_ms) { + timeout_ms = random_delay_ms(PROBE_WAIT); + // FIXME setsockopt(sock_fd, SO_ATTACH_FILTER, ...) to + // make the kernel filter out all packets except + // ones we'd care about. + } + // set deadline_us to the point in time when we timeout + deadline_us = MONOTONIC_US() + timeout_ms * 1000; + + VDBG("...wait %d %s nprobes=%u, nclaims=%u\n", + timeout_ms, argv_intf, nprobes, nclaims); + + switch (safe_poll(fds, 1, timeout_ms)) { + + default: + //bb_perror_msg("poll"); - done in safe_poll + return EXIT_FAILURE; + + // 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/%u %s@%s\n", + nprobes, argv_intf, inet_ntoa(ip)); + arp(/* ARPOP_REQUEST, */ + /* ð_addr, */ null_ip, + &null_addr, ip); + timeout_ms = PROBE_MIN * 1000; + timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN); + } + else { + // Switch to announce state. + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%u %s@%s\n", + nclaims, argv_intf, inet_ntoa(ip)); + arp(/* ARPOP_REQUEST, */ + /* ð_addr, */ ip, + ð_addr, ip); + timeout_ms = 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/%u %s@%s\n", + nclaims, argv_intf, inet_ntoa(ip)); + arp(/* ARPOP_REQUEST, */ + /* ð_addr, */ ip, + ð_addr, ip); + timeout_ms = 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/%u %s@%s\n", + nclaims, argv_intf, inet_ntoa(ip)); + arp(/* ARPOP_REQUEST, */ + /* ð_addr, */ ip, + ð_addr, ip); + timeout_ms = ANNOUNCE_INTERVAL * 1000; + } + else { + // Switch to monitor state. + state = MONITOR; + // link is ok to use earlier + // FIXME update filters + run(argv, "config", &ip); + ready = 1; + conflicts = 0; + timeout_ms = -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_ms = -1; + conflicts = 0; + break; + default: + // Invalid, should never happen. Restart the whole protocol. + state = PROBE; + ip.s_addr = pick(); + timeout_ms = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch (state) + break; // case 0 (timeout) + + // packets arriving, or link went down + case 1: + // We need to adjust the timeout in case we didn't receive + // a conflicting packet. + if (timeout_ms > 0) { + unsigned diff = deadline_us - MONOTONIC_US(); + if ((int)(diff) < 0) { + // Current time is greater than the expected timeout time. + // Should never happen. + VDBG("missed an expected timeout\n"); + timeout_ms = 0; + } else { + VDBG("adjusting timeout\n"); + timeout_ms = (diff / 1000) | 1; /* never 0 */ + } + } + + if ((fds[0].revents & POLLIN) == 0) { + if (fds[0].revents & POLLERR) { + // FIXME: links routinely go down; + // this shouldn't necessarily exit. + bb_error_msg("iface %s is down", argv_intf); + if (ready) { + run(argv, "deconfig", &ip); + } + return EXIT_FAILURE; + } + continue; + } + + // read ARP packet + if (safe_read(sock_fd, &p, sizeof(p)) < 0) { + bb_perror_msg_and_die(bb_msg_read_error); + } + if (p.eth.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", + argv_intf, ntohs(p.eth.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; + + source_ip_conflict = 0; + target_ip_conflict = 0; + + if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0 + && memcmp(&p.arp.arp_sha, ð_addr, ETH_ALEN) != 0 + ) { + source_ip_conflict = 1; + } + if (p.arp.arp_op == htons(ARPOP_REQUEST) + && memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0 + && memcmp(&p.arp.arp_tha, ð_addr, 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", argv_intf); + timeout_ms = RATE_LIMIT_INTERVAL * 1000; + state = RATE_LIMIT_PROBE; + } + + // restart the whole protocol + ip.s_addr = pick(); + timeout_ms = 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_ms = DEFEND_INTERVAL * 1000; + arp(/* 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(argv, "deconfig", &ip); + + // restart the whole protocol + ip.s_addr = pick(); + timeout_ms = 0; + nprobes = 0; + nclaims = 0; + } + break; + default: + // Invalid, should never happen. Restart the whole protocol. + VDBG("invalid state -- starting over\n"); + state = PROBE; + ip.s_addr = pick(); + timeout_ms = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch state + break; // case 1 (packets arriving) + } // switch poll + } // while (1) +#undef argv_intf +} |