[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit
Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/busybox/src/networking/Config.src b/ap/app/busybox/src/networking/Config.src
new file mode 100644
index 0000000..e1ae0c9
--- /dev/null
+++ b/ap/app/busybox/src/networking/Config.src
@@ -0,0 +1,997 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Networking Utilities"
+
+INSERT
+
+config FEATURE_IPV6
+ bool "Enable IPv6 support"
+ default y
+ help
+ Enable IPv6 support in busybox.
+ This adds IPv6 support in the networking applets.
+
+config FEATURE_UNIX_LOCAL
+ bool "Enable Unix domain socket support (usually not needed)"
+ default n
+ help
+ Enable Unix domain socket support in all busybox networking
+ applets. Address of the form local:/path/to/unix/socket
+ will be recognized.
+
+ This extension is almost never used in real world usage.
+ You most likely want to say N.
+
+config FEATURE_PREFER_IPV4_ADDRESS
+ bool "Prefer 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 y
+ select PLATFORM_LINUX
+ help
+ Manipulate the system ARP cache.
+
+config ARPING
+ bool "arping"
+ default y
+ select PLATFORM_LINUX
+ help
+ Ping hosts by ARP packets.
+
+config BRCTL
+ bool "brctl"
+ default y
+ select PLATFORM_LINUX
+ help
+ Manage ethernet bridges.
+ Supports addbr/delbr and addif/delif.
+
+config FEATURE_BRCTL_FANCY
+ bool "Fancy options"
+ default y
+ 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"
+ default y
+ depends on BRCTL && FEATURE_BRCTL_FANCY
+ help
+ Add support for option which prints the current config:
+ show
+
+config DNSD
+ bool "dnsd"
+ default y
+ help
+ Small and static DNS server daemon.
+
+config ETHER_WAKE
+ bool "ether-wake"
+ default y
+ select PLATFORM_LINUX
+ help
+ Send a magic packet to wake up sleeping machines.
+
+config FAKEIDENTD
+ bool "fakeidentd"
+ default y
+ select FEATURE_SYSLOG
+ help
+ fakeidentd listens on the ident port and returns a predefined
+ fake value on any query.
+
+config FTPD
+ bool "ftpd"
+ default y
+ help
+ simple FTP daemon. You have to run it via inetd.
+
+config FEATURE_FTP_WRITE
+ bool "Enable upload commands"
+ default y
+ depends on FTPD
+ help
+ Enable all kinds of FTP upload commands (-w option)
+
+config FEATURE_FTPD_ACCEPT_BROKEN_LIST
+ bool "Enable workaround for RFC-violating clients"
+ default y
+ depends on FTPD
+ help
+ Some ftp clients (among them KDE's Konqueror) issue illegal
+ "LIST -l" requests. This option works around such problems.
+ It might prevent you from listing files starting with "-" and
+ it increases the code size by ~40 bytes.
+ Most other ftp servers seem to behave similar to this.
+
+config FTPGET
+ bool "ftpget"
+ default y
+ help
+ Retrieve a remote file via FTP.
+
+config FTPPUT
+ bool "ftpput"
+ default y
+ help
+ Store a remote file via FTP.
+
+config FEATURE_FTPGETPUT_LONG_OPTIONS
+ bool "Enable long options in ftpget/ftpput"
+ default y
+ depends on LONG_OPTS && (FTPGET || FTPPUT)
+ help
+ Support long options for the ftpget/ftpput applet.
+
+config HOSTNAME
+ bool "hostname"
+ default y
+ help
+ Show or set the system's host name.
+
+config HTTPD
+ bool "httpd"
+ default y
+ help
+ Serve web pages via an HTTP server.
+
+config FEATURE_HTTPD_RANGES
+ bool "Support 'Ranges:' header"
+ default y
+ 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 y
+ depends on HTTPD
+ help
+ When enabled, httpd will use the kernel sendfile() function
+ instead of read/write loop.
+
+config FEATURE_HTTPD_SETUID
+ bool "Enable -u <user> option"
+ default y
+ 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.
+ Example for httpd.conf file:
+ /adm:toor:PaSsWd
+
+config FEATURE_HTTPD_AUTH_MD5
+ bool "Support MD5 crypted passwords for http Authentication"
+ default y
+ depends on FEATURE_HTTPD_BASIC_AUTH
+ help
+ Enables encrypted passwords, and wildcard user/passwords
+ in httpd.conf file.
+ User '*' means 'any system user name is ok',
+ password of '*' means 'use system password for this user'
+ Examples:
+ /adm:toor:$1$P/eKnWXS$aI1aPGxT.dJD5SzqAKWrF0
+ /adm:root:*
+ /wiki:*:*
+
+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 y
+ 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.conf file:
+ *.php:/path/to/your/php
+
+config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+ bool "Set REMOTE_PORT environment variable for CGI"
+ default y
+ 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 y
+ 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 y
+ 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 FEATURE_HTTPD_GZIP
+ bool "Support for GZIP content encoding"
+ default y
+ depends on HTTPD
+ help
+ Makes httpd send files using GZIP content encoding if the
+ client supports it and a pre-compressed <file>.gz exists.
+
+config IFCONFIG
+ bool "ifconfig"
+ default y
+ select PLATFORM_LINUX
+ 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 y
+ 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 y
+ 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 y
+ 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 y
+ select PLATFORM_LINUX
+ help
+ Userspace application to bind several interfaces
+ to a logical interface (use with kernel bonding driver).
+
+config IFPLUGD
+ bool "ifplugd"
+ default y
+ select PLATFORM_LINUX
+ help
+ Network interface plug detection daemon.
+
+config IFUPDOWN
+ bool "ifupdown"
+ default y
+ 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 y
+ 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 PLATFORM_LINUX
+ 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 n
+ 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 y
+ 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 y
+### 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 y
+ 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 y
+ 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 y
+ depends on INETD
+ select FEATURE_HAVE_RPC
+ help
+ Support Sun-RPC based services
+
+config IP
+ bool "ip"
+ default y
+ select PLATFORM_LINUX
+ 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 y
+ depends on IP
+ help
+ Add support for tunneling commands to "ip".
+
+config FEATURE_IP_RULE
+ bool "ip rule"
+ default y
+ depends on IP
+ help
+ Add support for rule commands to "ip".
+
+config FEATURE_IP_SHORT_FORMS
+ bool "Support short forms of ip commands"
+ default y
+ 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 y
+ 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 y
+ depends on IPCALC && LONG_OPTS
+ help
+ Support long options for the ipcalc applet.
+
+config NETSTAT
+ bool "netstat"
+ default y
+ select PLATFORM_LINUX
+ help
+ netstat prints information about the Linux networking subsystem.
+
+config FEATURE_NETSTAT_WIDE
+ bool "Enable wide netstat output"
+ default y
+ 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 y
+ 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 y
+ help
+ nslookup is a tool to query Internet name servers.
+
+config NTPD
+ bool "ntpd"
+ default y
+ select PLATFORM_LINUX
+ help
+ The NTP client/server daemon.
+
+config FEATURE_NTPD_SERVER
+ bool "Make ntpd usable as a NTP server"
+ default y
+ depends on NTPD
+ help
+ Make ntpd usable as a NTP server. If you disable this option
+ ntpd will be usable only as a NTP client.
+
+config PSCAN
+ bool "pscan"
+ default y
+ help
+ Simple network port scanner.
+
+config ROUTE
+ bool "route"
+ default y
+ select PLATFORM_LINUX
+ help
+ Route displays or manipulates the kernel's IP routing tables.
+
+config SLATTACH
+ bool "slattach"
+ default y
+ select PLATFORM_LINUX
+ help
+ slattach is a small utility to attach network interfaces to serial
+ lines.
+
+#config TC
+# bool "tc"
+# default y
+# help
+# show / manipulate traffic control settings
+#
+#config FEATURE_TC_INGRESS
+# def_bool n
+# depends on TC
+
+config TCPSVD
+ bool "tcpsvd"
+ default y
+ help
+ tcpsvd listens on a TCP port and runs a program for each new
+ connection.
+
+config TELNET
+ bool "telnet"
+ default y
+ 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 y
+ 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 y
+ depends on TELNETD
+ help
+ Selecting this will make telnetd able to run standalone.
+
+config FEATURE_TELNETD_INETD_WAIT
+ bool "Support -w SEC option (inetd wait mode)"
+ default y
+ depends on FEATURE_TELNETD_STANDALONE
+ help
+ This option allows you to run telnetd in "inet wait" mode.
+ Example inetd.conf line (note "wait", not usual "nowait"):
+
+ telnet stream tcp wait root /bin/telnetd telnetd -w10
+
+ In this example, inetd passes _listening_ socket_ as fd 0
+ to telnetd when connection appears.
+ telnetd will wait for connections until all existing
+ connections are closed, and no new connections
+ appear during 10 seconds. Then it exits, and inetd continues
+ to listen for new connections.
+
+ This option is rarely used. "tcp nowait" is much more usual
+ way of running tcp services, including telnetd.
+ You most probably want to say N here.
+
+config TFTP
+ bool "tftp"
+ default y
+ 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 y
+ 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"
+
+comment "Common options for tftp/tftpd"
+ depends on TFTP || TFTPD
+
+config FEATURE_TFTP_GET
+ bool "Enable 'tftp get' and/or tftpd upload code"
+ 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.
+
+ Note: this option does _not_ make tftpd capable of download
+ (the usual operation people need from it)!
+
+config FEATURE_TFTP_PUT
+ bool "Enable 'tftp put' and/or tftpd download code"
+ 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' and 'tsize' protocol options"
+ default y
+ depends on TFTP || TFTPD
+ help
+ Allow tftp to specify block size, and tftpd to understand
+ "blksize" and "tsize" options.
+
+config FEATURE_TFTP_PROGRESS_BAR
+ bool "Enable tftp progress meter"
+ default y
+ depends on TFTP && FEATURE_TFTP_BLOCKSIZE
+ help
+ Show progress bar.
+
+config TFTP_DEBUG
+ bool "Enable debug"
+ default n
+ depends on TFTP || TFTPD
+ help
+ Make tftp[d] print debugging messages on stderr.
+ This is useful if you are diagnosing a bug in tftp[d].
+
+config TRACEROUTE
+ bool "traceroute"
+ default y
+ select PLATFORM_LINUX
+ help
+ Utility to trace the route of IP packets.
+
+config TRACEROUTE6
+ bool "traceroute6"
+ default y
+ depends on FEATURE_IPV6 && TRACEROUTE
+ help
+ Utility to trace the route of IPv6 packets.
+
+config FEATURE_TRACEROUTE_VERBOSE
+ bool "Enable verbose output"
+ default y
+ depends on TRACEROUTE
+ help
+ Add some verbosity to traceroute. This includes among 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 option -I to use ICMP ECHO instead of UDP datagrams.
+
+config TUNCTL
+ bool "tunctl"
+ default y
+ select PLATFORM_LINUX
+ help
+ tunctl creates or deletes tun devices.
+
+config FEATURE_TUNCTL_UG
+ bool "Support owner:group assignment"
+ default y
+ depends on TUNCTL
+ help
+ Allow to specify owner and group of newly created interface.
+ 340 bytes of pure bloat. Say no here.
+
+source networking/udhcp/Config.in
+
+config IFUPDOWN_UDHCPC_CMD_OPTIONS
+ string "ifup udhcpc command line options"
+ default "-R -n"
+ depends on IFUPDOWN && UDHCPC
+ help
+ Command line options to pass to udhcpc from ifup.
+ Intended to alter options not available in /etc/network/interfaces.
+ (IE: --syslog --background etc...)
+
+config UDPSVD
+ bool "udpsvd"
+ default y
+ help
+ udpsvd listens on an UDP port and runs a program for each new
+ connection.
+
+config VCONFIG
+ bool "vconfig"
+ default y
+ select PLATFORM_LINUX
+ help
+ Creates, removes, and configures VLAN interfaces
+
+config WGET
+ bool "wget"
+ default y
+ help
+ wget is a utility for non-interactive download of files from HTTP
+ 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 y
+ depends on WGET && LONG_OPTS
+ help
+ Support long options for the wget applet.
+
+config FEATURE_WGET_TIMEOUT
+ bool "Enable read timeout option -T SEC"
+ default y
+ depends on WGET
+ help
+ Supports network read timeout for wget, so that wget will give
+ up and timeout when reading network data, through the -T command
+ line option. Currently only network data read timeout is
+ supported (i.e., timeout is not applied to the DNS nor TCP
+ connection initialization). When FEATURE_WGET_LONG_OPTIONS is
+ also enabled, the --timeout option will work in addition to -T.
+
+config ZCIP
+ bool "zcip"
+ default y
+ select PLATFORM_LINUX
+ select FEATURE_SYSLOG
+ help
+ ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927.
+ It's a daemon that allocates and defends a dynamically assigned
+ address on the 169.254/16 network, requiring no system administrator.
+
+ See http://www.zeroconf.org for further details, and "zcip.script"
+ in the busybox examples.
+
+endmenu
diff --git a/ap/app/busybox/src/networking/Kbuild.src b/ap/app/busybox/src/networking/Kbuild.src
new file mode 100644
index 0000000..944f27b
--- /dev/null
+++ b/ap/app/busybox/src/networking/Kbuild.src
@@ -0,0 +1,48 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under GPLv2, see file LICENSE in this source tree.
+
+lib-y:=
+
+INSERT
+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_FTPD) += ftpd.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_IFPLUGD) += ifplugd.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_NTPD) += ntpd.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_TUNCTL) += tunctl.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/ap/app/busybox/src/networking/arp.c b/ap/app/busybox/src/networking/arp.c
new file mode 100644
index 0000000..1c99987
--- /dev/null
+++ b/ap/app/busybox/src/networking/arp.c
@@ -0,0 +1,533 @@
+/* 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>
+ */
+
+//usage:#define arp_trivial_usage
+//usage: "\n[-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]"
+//usage: "\n[-v] [-i IF] -d HOSTNAME [pub]"
+//usage: "\n[-v] [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]"
+//usage: "\n[-v] [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub"
+//usage: "\n[-v] [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub"
+//usage:#define arp_full_usage "\n\n"
+//usage: "Manipulate ARP cache\n"
+//usage: "\n -a Display (all) hosts"
+//usage: "\n -s Set new ARP entry"
+//usage: "\n -d Delete a specified entry"
+//usage: "\n -v Verbose"
+//usage: "\n -n Don't resolve names"
+//usage: "\n -i IF Network interface"
+//usage: "\n -D Read <hwaddr> from given device"
+//usage: "\n -A,-p AF Protocol family"
+//usage: "\n -H HWTYPE Hardware address type"
+
+#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"
+
+enum {
+ ARP_OPT_A = (1 << 0),
+ ARP_OPT_p = (1 << 1),
+ ARP_OPT_H = (1 << 2),
+ ARP_OPT_t = (1 << 3),
+ ARP_OPT_i = (1 << 4),
+ ARP_OPT_a = (1 << 5),
+ ARP_OPT_d = (1 << 6),
+ ARP_OPT_n = (1 << 7), /* do not resolve addresses */
+ ARP_OPT_D = (1 << 8), /* HW-address is devicename */
+ ARP_OPT_s = (1 << 9),
+ ARP_OPT_v = (1 << 10) * DEBUG, /* debugging output flag */
+};
+
+enum {
+ sockfd = 3, /* active socket descriptor */
+};
+
+struct globals {
+ const struct aftype *ap; /* current address family */
+ const struct hwtype *hw; /* current hardware type */
+ const char *device; /* current device */
+ smallint hw_set; /* flag if hw-type was set (-H) */
+
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define ap (G.ap )
+#define hw (G.hw )
+#define device (G.device )
+#define hw_set (G.hw_set )
+#define INIT_G() do { \
+ device = ""; \
+} while (0)
+
+
+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;
+ unsigned opts;
+
+ INIT_G();
+
+ xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), sockfd);
+ ap = get_aftype(DFLT_AF);
+ if (!ap)
+ bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family");
+
+ opts = getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol,
+ &hw_type, &hw_type, &device);
+ argv += optind;
+ if (opts & (ARP_OPT_A | ARP_OPT_p)) {
+ ap = get_aftype(protocol);
+ if (ap == NULL)
+ bb_error_msg_and_die("%s: unknown %s", protocol, "address family");
+ }
+ if (opts & (ARP_OPT_A | 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 (opts & 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");
+ }
+
+ /* Now see what we have to do here... */
+ if (opts & (ARP_OPT_d | ARP_OPT_s)) {
+ if (argv[0] == NULL)
+ bb_error_msg_and_die("need host name");
+ if (opts & ARP_OPT_s)
+ return arp_set(argv);
+ return arp_del(argv);
+ }
+ //if (opts & ARP_OPT_a) - default
+ return arp_show(argv[0]);
+}
diff --git a/ap/app/busybox/src/networking/arping.c b/ap/app/busybox/src/networking/arping.c
new file mode 100644
index 0000000..a4421ed
--- /dev/null
+++ b/ap/app/busybox/src/networking/arping.c
@@ -0,0 +1,425 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * Author: Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ * Busybox port: Nick Fedchik <nick@fedchik.org.ua>
+ */
+
+//usage:#define arping_trivial_usage
+//usage: "[-fqbDUA] [-c CNT] [-w TIMEOUT] [-I IFACE] [-s SRC_IP] DST_IP"
+//usage:#define arping_full_usage "\n\n"
+//usage: "Send ARP requests/replies\n"
+//usage: "\n -f Quit on first ARP reply"
+//usage: "\n -q Quiet"
+//usage: "\n -b Keep broadcasting, don't go unicast"
+//usage: "\n -D Duplicated address detection mode"
+//usage: "\n -U Unsolicited ARP mode, update your neighbors"
+//usage: "\n -A ARP answer mode, update your neighbors"
+//usage: "\n -c N Stop after sending N ARP requests"
+//usage: "\n -w TIMEOUT Time to wait for ARP reply, seconds"
+//usage: "\n -I IFACE Interface to use (default eth0)"
+//usage: "\n -s SRC_IP Sender IP address"
+//usage: "\n DST_IP Target IP address"
+
+#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;
+} FIX_ALIASING;
+#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;
+ /* moves below assume in_addr is 4 bytes big, ensure that */
+ struct BUG_in_addr_must_be_4 {
+ char BUG_in_addr_must_be_4[
+ sizeof(struct in_addr) == 4 ? 1 : -1
+ ];
+ char BUG_s_addr_must_be_4[
+ sizeof(src_ip.s_addr) == 4 ? 1 : -1
+ ];
+ };
+
+ /* 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 recognized */
+ 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;
+
+ move_from_unaligned32(src_ip.s_addr, p + ah->ar_hln);
+ move_from_unaligned32(dst_ip.s_addr, p + ah->ar_hln + 4 + ah->ar_hln);
+
+ 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_all();
+ }
+ 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_IFNAMSIZ(ifr.ifr_name, device);
+ /* 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);
+ dst = lsa->u.sin.sin_addr;
+ 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));
+ getsockname(probe_fd, (struct sockaddr *) &saddr, &alen);
+ //never happens:
+ //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);
+ getsockname(sock_fd, (struct sockaddr *) &me, &alen);
+ //never happens:
+ //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/ap/app/busybox/src/networking/brctl.c b/ap/app/busybox/src/networking/brctl.c
new file mode 100644
index 0000000..207b069
--- /dev/null
+++ b/ap/app/busybox/src/networking/brctl.c
@@ -0,0 +1,316 @@
+/* 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 source tree.
+ */
+/* This applet currently uses only the ioctl interface and no sysfs at all.
+ * At the time of this writing this was considered a feature.
+ */
+
+//usage:#define brctl_trivial_usage
+//usage: "COMMAND [BRIDGE [INTERFACE]]"
+//usage:#define brctl_full_usage "\n\n"
+//usage: "Manage ethernet bridges\n"
+//usage: "\nCommands:"
+//usage: IF_FEATURE_BRCTL_SHOW(
+//usage: "\n show Show a list of bridges"
+//usage: )
+//usage: "\n addbr BRIDGE Create BRIDGE"
+//usage: "\n delbr BRIDGE Delete BRIDGE"
+//usage: "\n addif BRIDGE IFACE Add IFACE to BRIDGE"
+//usage: "\n delif BRIDGE IFACE Delete IFACE from BRIDGE"
+//usage: IF_FEATURE_BRCTL_FANCY(
+//usage: "\n setageing BRIDGE TIME Set ageing time"
+//usage: "\n setfd BRIDGE TIME Set bridge forward delay"
+//usage: "\n sethello BRIDGE TIME Set hello time"
+//usage: "\n setmaxage BRIDGE TIME Set max message age"
+//usage: "\n setpathcost BRIDGE COST Set path cost"
+//usage: "\n setportprio BRIDGE PRIO Set port priority"
+//usage: "\n setbridgeprio BRIDGE PRIO Set bridge priority"
+//usage: "\n stp BRIDGE [1/yes/on|0/no/off] STP on/off"
+//usage: )
+
+#include "libbb.h"
+#include <linux/sockios.h>
+#include <net/if.h>
+
+#ifndef SIOCBRADDBR
+# define SIOCBRADDBR BRCTL_ADD_BRIDGE
+#endif
+#ifndef SIOCBRDELBR
+# define SIOCBRDELBR BRCTL_DEL_BRIDGE
+#endif
+#ifndef SIOCBRADDIF
+# define SIOCBRADDIF BRCTL_ADD_IF
+#endif
+#ifndef SIOCBRDELIF
+# define SIOCBRDELIF BRCTL_DEL_IF
+#endif
+
+
+/* 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 bb_strtotimeval(struct timeval *tv,
+ const char *time_str)
+{
+ double secs;
+# if BRCTL_USE_INTERNAL
+ char *endptr;
+ secs = /*bb_*/strtod(time_str, &endptr);
+ if (endptr == time_str)
+# 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;
+ bb_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"
+ IF_FEATURE_BRCTL_FANCY(
+ "stp\0"
+ "setageing\0" "setfd\0" "sethello\0" "setmaxage\0"
+ "setpathcost\0" "setportprio\0" "setbridgeprio\0"
+ )
+ IF_FEATURE_BRCTL_SHOW("show\0");
+
+ enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif
+ IF_FEATURE_BRCTL_FANCY(,
+ ARG_stp,
+ ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage,
+ ARG_setpathcost, ARG_setportprio, ARG_setbridgeprio
+ )
+ IF_FEATURE_BRCTL_SHOW(, 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_IFNAMSIZ(ifr.ifr_name, brname);
+
+ 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 'addbr/delbr' need at least two arguments */
+ bb_show_usage();
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, br);
+ 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 */
+ static const char no_yes[] ALIGN1 =
+ "0\0" "off\0" "n\0" "no\0" /* 0 .. 3 */
+ "1\0" "on\0" "y\0" "yes\0"; /* 4 .. 7 */
+ int onoff = index_in_strings(no_yes, *argv);
+ if (onoff < 0)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ onoff = (unsigned)onoff / 4;
+ arm_ioctl(args, BRCTL_SET_BRIDGE_STP_STATE, onoff, 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_positive(*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/ap/app/busybox/src/networking/dnsd.c b/ap/app/busybox/src/networking/dnsd.c
new file mode 100644
index 0000000..fe98400
--- /dev/null
+++ b/ap/app/busybox/src/networking/dnsd.c
@@ -0,0 +1,559 @@
+/* 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 source tree.
+ *
+ * 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.
+ */
+
+//usage:#define dnsd_trivial_usage
+//usage: "[-dvs] [-c CONFFILE] [-t TTL_SEC] [-p PORT] [-i ADDR]"
+//usage:#define dnsd_full_usage "\n\n"
+//usage: "Small static DNS server daemon\n"
+//usage: "\n -c FILE Config file"
+//usage: "\n -t SEC TTL"
+//usage: "\n -p PORT Listen on PORT"
+//usage: "\n -i ADDR Listen on ADDR"
+//usage: "\n -d Daemonize"
+//usage: "\n -v Verbose"
+//usage: "\n -s Send successful replies only. Use this if you want"
+//usage: "\n to use /etc/resolv.conf with two nameserver lines:"
+//usage: "\n nameserver DNSD_SERVER"
+//usage: "\n nameserver NORMAL_DNS_SERVER"
+
+#include "libbb.h"
+#include <syslog.h>
+
+//#define DEBUG 1
+#define DEBUG 0
+
+enum {
+ /* can tweak this */
+ DEFAULT_TTL = 120,
+
+ /* cannot get bigger packets than 512 per RFC1035. */
+ MAX_PACK_LEN = 512,
+ IP_STRING_LEN = sizeof(".xxx.xxx.xxx.xxx"),
+ MAX_NAME_LEN = IP_STRING_LEN - 1 + sizeof(".in-addr.arpa"),
+ REQ_A = 1,
+ REQ_PTR = 12,
+};
+
+/* the message from client and first part of response msg */
+struct dns_head {
+ uint16_t id;
+ uint16_t flags;
+ uint16_t nquer;
+ uint16_t nansw;
+ uint16_t nauth;
+ uint16_t nadd;
+};
+/* Structure used to access type and class fields.
+ * They are totally unaligned, but gcc 4.3.4 thinks that pointer of type uint16_t*
+ * is 16-bit aligned and replaces 16-bit memcpy (in move_from_unaligned16 macro)
+ * with aligned halfword access on arm920t!
+ * Oh well. Slapping PACKED everywhere seems to help: */
+struct type_and_class {
+ uint16_t type PACKED;
+ uint16_t class PACKED;
+} PACKED;
+/* element of known name, ip address and reversed ip address */
+struct dns_entry {
+ struct dns_entry *next;
+ uint32_t ip;
+ char rip[IP_STRING_LEN]; /* length decimal reversed IP */
+ char name[1];
+};
+
+#define OPT_verbose (option_mask32 & 1)
+#define OPT_silent (option_mask32 & 2)
+
+
+/*
+ * Insert length of substrings instead of dots
+ */
+static void undot(char *rip)
+{
+ int i = 0;
+ int 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 struct dns_entry *parse_conf_file(const char *fileconf)
+{
+ char *token[2];
+ parser_t *parser;
+ struct dns_entry *m, *conf_data;
+ struct dns_entry **nextp;
+
+ conf_data = NULL;
+ nextp = &conf_data;
+
+ parser = config_open(fileconf);
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+ struct in_addr ip;
+ uint32_t v32;
+
+ if (inet_aton(token[1], &ip) == 0) {
+ bb_error_msg("error at line %u, skipping", parser->lineno);
+ continue;
+ }
+
+ if (OPT_verbose)
+ bb_error_msg("name:%s, ip:%s", token[0], token[1]);
+
+ /* sizeof(*m) includes 1 byte for m->name[0] */
+ m = xzalloc(sizeof(*m) + strlen(token[0]) + 1);
+ /*m->next = NULL;*/
+ *nextp = m;
+ nextp = &m->next;
+
+ m->name[0] = '.';
+ strcpy(m->name + 1, token[0]);
+ undot(m->name);
+ m->ip = ip.s_addr; /* in network order */
+ v32 = ntohl(m->ip);
+ /* inverted order */
+ sprintf(m->rip, ".%u.%u.%u.%u",
+ (uint8_t)(v32),
+ (uint8_t)(v32 >> 8),
+ (uint8_t)(v32 >> 16),
+ (v32 >> 24)
+ );
+ undot(m->rip);
+ }
+ config_close(parser);
+ return conf_data;
+}
+
+/*
+ * Look query up in dns records and return answer if found.
+ */
+static char *table_lookup(struct dns_entry *d,
+ uint16_t type,
+ char* query_string)
+{
+ while (d) {
+ unsigned len = d->name[0];
+ /* d->name[len] is the last (non NUL) char */
+#if DEBUG
+ char *p, *q;
+ q = query_string + 1;
+ p = d->name + 1;
+ fprintf(stderr, "%d/%d p:%s q:%s %d\n",
+ (int)strlen(p), len,
+ p, q, (int)strlen(q)
+ );
+#endif
+ if (type == htons(REQ_A)) {
+ /* search by host name */
+ if (len != 1 || d->name[1] != '*') {
+/* we are lax, hope no name component is ever >64 so that length
+ * (which will be represented as 'A','B'...) matches a lowercase letter.
+ * Actually, I think false matches are hard to construct.
+ * Example.
+ * [31] len is represented as '1', [65] as 'A', [65+32] as 'a'.
+ * [65] <65 same chars>[31]<31 same chars>NUL
+ * [65+32]<65 same chars>1 <31 same chars>NUL
+ * This example seems to be the minimal case when false match occurs.
+ */
+ if (strcasecmp(d->name, query_string) != 0)
+ goto next;
+ }
+ return (char *)&d->ip;
+#if DEBUG
+ fprintf(stderr, "Found IP:%x\n", (int)d->ip);
+#endif
+ return 0;
+ }
+ /* search by IP-address */
+ if ((len != 1 || d->name[1] != '*')
+ /* we assume (do not check) that query_string
+ * ends in ".in-addr.arpa" */
+ && strncmp(d->rip, query_string, strlen(d->rip)) == 0
+ ) {
+#if DEBUG
+ fprintf(stderr, "Found name:%s\n", d->name);
+#endif
+ return d->name;
+ }
+ next:
+ d = d->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Decode message and generate answer
+ */
+/* RFC 1035
+...
+Whenever an octet represents a numeric quantity, the left most bit
+in the diagram is the high order or most significant bit.
+That is, the bit labeled 0 is the most significant bit.
+...
+
+4.1.1. Header section format
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ID |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ |QR| OPCODE |AA|TC|RD|RA| 0 0 0| RCODE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QDCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ANCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | NSCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ARCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ID 16 bit random identifier assigned by querying peer.
+ Used to match query/response.
+QR message is a query (0), or a response (1).
+OPCODE 0 standard query (QUERY)
+ 1 inverse query (IQUERY)
+ 2 server status request (STATUS)
+AA Authoritative Answer - this bit is valid in responses.
+ Responding name server is an authority for the domain name
+ in question section. Answer section may have multiple owner names
+ because of aliases. The AA bit corresponds to the name which matches
+ the query name, or the first owner name in the answer section.
+TC TrunCation - this message was truncated.
+RD Recursion Desired - this bit may be set in a query and
+ is copied into the response. If RD is set, it directs
+ the name server to pursue the query recursively.
+ Recursive query support is optional.
+RA Recursion Available - this be is set or cleared in a
+ response, and denotes whether recursive query support is
+ available in the name server.
+RCODE Response code.
+ 0 No error condition
+ 1 Format error
+ 2 Server failure - server was unable to process the query
+ due to a problem with the name server.
+ 3 Name Error - meaningful only for responses from
+ an authoritative name server. The referenced domain name
+ does not exist.
+ 4 Not Implemented.
+ 5 Refused.
+QDCOUNT number of entries in the question section.
+ANCOUNT number of records in the answer section.
+NSCOUNT number of records in the authority records section.
+ARCOUNT number of records in the additional records section.
+
+4.1.2. Question section format
+
+The section contains QDCOUNT (usually 1) entries, each of this format:
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / QNAME /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QTYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QCLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+QNAME a domain name represented as a sequence of labels, where
+ each label consists of a length octet followed by that
+ number of octets. The domain name terminates with the
+ zero length octet for the null label of the root. Note
+ that this field may be an odd number of octets; no
+ padding is used.
+QTYPE a two octet type of the query.
+ 1 a host address [REQ_A const]
+ 2 an authoritative name server
+ 3 a mail destination (Obsolete - use MX)
+ 4 a mail forwarder (Obsolete - use MX)
+ 5 the canonical name for an alias
+ 6 marks the start of a zone of authority
+ 7 a mailbox domain name (EXPERIMENTAL)
+ 8 a mail group member (EXPERIMENTAL)
+ 9 a mail rename domain name (EXPERIMENTAL)
+ 10 a null RR (EXPERIMENTAL)
+ 11 a well known service description
+ 12 a domain name pointer [REQ_PTR const]
+ 13 host information
+ 14 mailbox or mail list information
+ 15 mail exchange
+ 16 text strings
+ 0x1c IPv6?
+ 252 a request for a transfer of an entire zone
+ 253 a request for mailbox-related records (MB, MG or MR)
+ 254 a request for mail agent RRs (Obsolete - see MX)
+ 255 a request for all records
+QCLASS a two octet code that specifies the class of the query.
+ 1 the Internet
+ (others are historic only)
+ 255 any class
+
+4.1.3. Resource Record format
+
+The answer, authority, and additional sections all share the same format:
+a variable number of resource records, where the number of records
+is specified in the corresponding count field in the header.
+Each resource record has this format:
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ / /
+ / NAME /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TYPE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | CLASS |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | TTL |
+ | |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | RDLENGTH |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+ / RDATA /
+ / /
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+NAME a domain name to which this resource record pertains.
+TYPE two octets containing one of the RR type codes. This
+ field specifies the meaning of the data in the RDATA field.
+CLASS two octets which specify the class of the data in the RDATA field.
+TTL a 32 bit unsigned integer that specifies the time interval
+ (in seconds) that the record may be cached.
+RDLENGTH a 16 bit integer, length in octets of the RDATA field.
+RDATA a variable length string of octets that describes the resource.
+ The format of this information varies according to the TYPE
+ and CLASS of the resource record.
+ If the TYPE is A and the CLASS is IN, it's a 4 octet IP address.
+
+4.1.4. Message compression
+
+In order to reduce the size of messages, domain names coan be compressed.
+An entire domain name or a list of labels at the end of a domain name
+is replaced with a pointer to a prior occurance of the same name.
+
+The pointer takes the form of a two octet sequence:
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | 1 1| OFFSET |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+The first two bits are ones. This allows a pointer to be distinguished
+from a label, since the label must begin with two zero bits because
+labels are restricted to 63 octets or less. The OFFSET field specifies
+an offset from the start of the message (i.e., the first octet
+of the ID field in the domain header).
+A zero offset specifies the first byte of the ID field, etc.
+Domain name in a message can be represented as either:
+ - a sequence of labels ending in a zero octet
+ - a pointer
+ - a sequence of labels ending with a pointer
+ */
+static int process_packet(struct dns_entry *conf_data,
+ uint32_t conf_ttl,
+ uint8_t *buf)
+{
+ struct dns_head *head;
+ struct type_and_class *unaligned_type_class;
+ const char *err_msg;
+ char *query_string;
+ char *answstr;
+ uint8_t *answb;
+ uint16_t outr_rlen;
+ uint16_t outr_flags;
+ uint16_t type;
+ uint16_t class;
+ int query_len;
+
+ head = (struct dns_head *)buf;
+ if (head->nquer == 0) {
+ bb_error_msg("packet has 0 queries, ignored");
+ return 0; /* don't reply */
+ }
+ if (head->flags & htons(0x8000)) { /* QR bit */
+ bb_error_msg("response packet, ignored");
+ return 0; /* don't reply */
+ }
+ /* QR = 1 "response", RCODE = 4 "Not Implemented" */
+ outr_flags = htons(0x8000 | 4);
+ err_msg = NULL;
+
+ /* start of query string */
+ query_string = (void *)(head + 1);
+ /* caller guarantees strlen is <= MAX_PACK_LEN */
+ query_len = strlen(query_string) + 1;
+ /* may be unaligned! */
+ unaligned_type_class = (void *)(query_string + query_len);
+ query_len += sizeof(*unaligned_type_class);
+ /* where to append answer block */
+ answb = (void *)(unaligned_type_class + 1);
+
+ /* OPCODE != 0 "standard query"? */
+ if ((head->flags & htons(0x7800)) != 0) {
+ err_msg = "opcode != 0";
+ goto empty_packet;
+ }
+ move_from_unaligned16(class, &unaligned_type_class->class);
+ if (class != htons(1)) { /* not class INET? */
+ err_msg = "class != 1";
+ goto empty_packet;
+ }
+ move_from_unaligned16(type, &unaligned_type_class->type);
+ if (type != htons(REQ_A) && type != htons(REQ_PTR)) {
+ /* we can't handle this query type */
+//TODO: happens all the time with REQ_AAAA (0x1c) requests - implement those?
+ err_msg = "type is !REQ_A and !REQ_PTR";
+ goto empty_packet;
+ }
+
+ /* look up the name */
+ answstr = table_lookup(conf_data, type, query_string);
+#if DEBUG
+ /* Shows lengths instead of dots, unusable for !DEBUG */
+ bb_error_msg("'%s'->'%s'", query_string, answstr);
+#endif
+ outr_rlen = 4;
+ if (answstr && type == htons(REQ_PTR)) {
+ /* returning a host name */
+ outr_rlen = strlen(answstr) + 1;
+ }
+ if (!answstr
+ || (unsigned)(answb - buf) + query_len + 4 + 2 + outr_rlen > MAX_PACK_LEN
+ ) {
+ /* QR = 1 "response"
+ * AA = 1 "Authoritative Answer"
+ * RCODE = 3 "Name Error" */
+ err_msg = "name is not found";
+ outr_flags = htons(0x8000 | 0x0400 | 3);
+ goto empty_packet;
+ }
+
+ /* Append answer Resource Record */
+ memcpy(answb, query_string, query_len); /* name, type, class */
+ answb += query_len;
+ move_to_unaligned32((uint32_t *)answb, htonl(conf_ttl));
+ answb += 4;
+ move_to_unaligned16((uint16_t *)answb, htons(outr_rlen));
+ answb += 2;
+ memcpy(answb, answstr, outr_rlen);
+ answb += outr_rlen;
+
+ /* QR = 1 "response",
+ * AA = 1 "Authoritative Answer",
+ * TODO: need to set RA bit 0x80? One user says nslookup complains
+ * "Got recursion not available from SERVER, trying next server"
+ * "** server can't find HOSTNAME"
+ * RCODE = 0 "success"
+ */
+ if (OPT_verbose)
+ bb_error_msg("returning positive reply");
+ outr_flags = htons(0x8000 | 0x0400 | 0);
+ /* we have one answer */
+ head->nansw = htons(1);
+
+ empty_packet:
+ if ((outr_flags & htons(0xf)) != 0) { /* not a positive response */
+ if (OPT_verbose) {
+ bb_error_msg("%s, %s",
+ err_msg,
+ OPT_silent ? "dropping query" : "sending error reply"
+ );
+ }
+ if (OPT_silent)
+ return 0;
+ }
+ head->flags |= outr_flags;
+ head->nauth = head->nadd = 0;
+ head->nquer = htons(1); // why???
+
+ return answb - buf;
+}
+
+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";
+ const char *fileconf = "/etc/dnsd.conf";
+ struct dns_entry *conf_data;
+ uint32_t conf_ttl = DEFAULT_TTL;
+ char *sttl, *sport;
+ len_and_sockaddr *lsa, *from, *to;
+ unsigned lsa_size;
+ int udps, opts;
+ uint16_t port = 53;
+ /* Ensure buf is 32bit aligned (we need 16bit, but 32bit can't hurt) */
+ uint8_t buf[MAX_PACK_LEN + 1] ALIGN4;
+
+ opts = getopt32(argv, "vsi:c:t:p:d", &listen_interface, &fileconf, &sttl, &sport);
+ //if (opts & (1 << 0)) // -v
+ //if (opts & (1 << 1)) // -s
+ //if (opts & (1 << 2)) // -i
+ //if (opts & (1 << 3)) // -c
+ if (opts & (1 << 4)) // -t
+ conf_ttl = xatou_range(sttl, 1, 0xffffffff);
+ if (opts & (1 << 5)) // -p
+ port = xatou_range(sport, 1, 0xffff);
+ if (opts & (1 << 6)) { // -d
+ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ conf_data = parse_conf_file(fileconf);
+
+ 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);
+
+ {
+ char *p = xmalloc_sockaddr2dotted(&lsa->u.sa);
+ bb_error_msg("accepting UDP packets on %s", p);
+ free(p);
+ }
+
+ 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("packet size %d, ignored", r);
+ continue;
+ }
+ if (OPT_verbose)
+ bb_error_msg("got UDP packet");
+ buf[r] = '\0'; /* paranoia */
+ r = process_packet(conf_data, conf_ttl, 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/ap/app/busybox/src/networking/ether-wake.c b/ap/app/busybox/src/networking/ether-wake.c
new file mode 100644
index 0000000..bf09cd5
--- /dev/null
+++ b/ap/app/busybox/src/networking/ether-wake.c
@@ -0,0 +1,286 @@
+/* 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 source tree.
+ *
+ * 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.
+*/
+
+//usage:#define ether_wake_trivial_usage
+//usage: "[-b] [-i iface] [-p aa:bb:cc:dd[:ee:ff]] MAC"
+//usage:#define ether_wake_full_usage "\n\n"
+//usage: "Send a magic packet to wake up sleeping machines.\n"
+//usage: "MAC must be a station address (00:11:22:33:44:55) or\n"
+//usage: "a hostname with a known 'ethers' entry.\n"
+//usage: "\n -b Send wake-up packet to the broadcast address"
+//usage: "\n -i iface Interface to use (default eth0)"
+//usage: "\n -p pass Append four or six byte password PW to the packet"
+
+#include "libbb.h"
+#include <netpacket/packet.h>
+#include <netinet/ether.h>
+#include <linux/if.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_r(hostid, eaddr);
+ if (eap) {
+ bb_debug_msg("The target station address is %s\n\n", ether_ntoa(eap));
+#if !defined(__UCLIBC_MAJOR__) \
+ || __UCLIBC_MAJOR__ > 0 \
+ || __UCLIBC_MINOR__ > 9 \
+ || (__UCLIBC_MINOR__ == 9 && __UCLIBC_SUBLEVEL__ >= 30)
+ } 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("can't 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_IFNAMSIZ(if_hwaddr.ifr_name, ifname);
+ 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_IFNAMSIZ(ifr.ifr_name, ifname);
+ 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/ap/app/busybox/src/networking/ftpd.c b/ap/app/busybox/src/networking/ftpd.c
new file mode 100644
index 0000000..33db964
--- /dev/null
+++ b/ap/app/busybox/src/networking/ftpd.c
@@ -0,0 +1,1378 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
+ *
+ * Author: Adam Tkac <vonsch@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ *
+ * Only subset of FTP protocol is implemented but vast majority of clients
+ * should not have any problem.
+ *
+ * You have to run this daemon via inetd.
+ */
+
+//usage:#define ftpd_trivial_usage
+//usage: "[-wvS] [-t N] [-T N] [DIR]"
+//usage:#define ftpd_full_usage "\n\n"
+//usage: "Anonymous FTP server\n"
+//usage: "\n"
+//usage: "ftpd should be used as an inetd service.\n"
+//usage: "ftpd's line for inetd.conf:\n"
+//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
+//usage: "It also can be ran from tcpsvd:\n"
+//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
+//usage: "\n -w Allow upload"
+//usage: "\n -v Log errors to stderr. -vv: verbose log"
+//usage: "\n -S Log errors to syslog. -SS: verbose log"
+//usage: "\n -t,-T Idle and absolute timeouts"
+//usage: "\n DIR Change root to this directory"
+
+#include "libbb.h"
+#include <syslog.h>
+#include <netinet/tcp.h>
+
+#define FTP_DATACONN 150
+#define FTP_NOOPOK 200
+#define FTP_TYPEOK 200
+#define FTP_PORTOK 200
+#define FTP_STRUOK 200
+#define FTP_MODEOK 200
+#define FTP_ALLOOK 202
+#define FTP_STATOK 211
+#define FTP_STATFILE_OK 213
+#define FTP_HELP 214
+#define FTP_SYSTOK 215
+#define FTP_GREET 220
+#define FTP_GOODBYE 221
+#define FTP_TRANSFEROK 226
+#define FTP_PASVOK 227
+/*#define FTP_EPRTOK 228*/
+#define FTP_EPSVOK 229
+#define FTP_LOGINOK 230
+#define FTP_CWDOK 250
+#define FTP_RMDIROK 250
+#define FTP_DELEOK 250
+#define FTP_RENAMEOK 250
+#define FTP_PWDOK 257
+#define FTP_MKDIROK 257
+#define FTP_GIVEPWORD 331
+#define FTP_RESTOK 350
+#define FTP_RNFROK 350
+#define FTP_TIMEOUT 421
+#define FTP_BADSENDCONN 425
+#define FTP_BADSENDNET 426
+#define FTP_BADSENDFILE 451
+#define FTP_BADCMD 500
+#define FTP_COMMANDNOTIMPL 502
+#define FTP_NEEDUSER 503
+#define FTP_NEEDRNFR 503
+#define FTP_BADSTRU 504
+#define FTP_BADMODE 504
+#define FTP_LOGINERR 530
+#define FTP_FILEFAIL 550
+#define FTP_NOPERM 550
+#define FTP_UPLOADFAIL 553
+
+#define STR1(s) #s
+#define STR(s) STR1(s)
+
+/* Convert a constant to 3-digit string, packed into uint32_t */
+enum {
+ /* Shift for Nth decimal digit */
+ SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
+ SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
+ SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
+ /* And for 4th position (space) */
+ SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
+};
+#define STRNUM32(s) (uint32_t)(0 \
+ | (('0' + ((s) / 1 % 10)) << SHIFT0) \
+ | (('0' + ((s) / 10 % 10)) << SHIFT1) \
+ | (('0' + ((s) / 100 % 10)) << SHIFT2) \
+)
+#define STRNUM32sp(s) (uint32_t)(0 \
+ | (' ' << SHIFTsp) \
+ | (('0' + ((s) / 1 % 10)) << SHIFT0) \
+ | (('0' + ((s) / 10 % 10)) << SHIFT1) \
+ | (('0' + ((s) / 100 % 10)) << SHIFT2) \
+)
+
+#define MSG_OK "Operation successful\r\n"
+#define MSG_ERR "Error\r\n"
+
+struct globals {
+ int pasv_listen_fd;
+#if !BB_MMU
+ int root_fd;
+#endif
+ int local_file_fd;
+ unsigned end_time;
+ unsigned timeout;
+ unsigned verbose;
+ off_t local_file_pos;
+ off_t restart_pos;
+ len_and_sockaddr *local_addr;
+ len_and_sockaddr *port_addr;
+ char *ftp_cmd;
+ char *ftp_arg;
+#if ENABLE_FEATURE_FTP_WRITE
+ char *rnfr_filename;
+#endif
+ /* We need these aligned to uint32_t */
+ char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
+ char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+ /* Moved to main */ \
+ /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
+ /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
+} while (0)
+
+
+static char *
+escape_text(const char *prepend, const char *str, unsigned escapee)
+{
+ unsigned retlen, remainlen, chunklen;
+ char *ret, *found;
+ char append;
+
+ append = (char)escapee;
+ escapee >>= 8;
+
+ remainlen = strlen(str);
+ retlen = strlen(prepend);
+ ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
+ strcpy(ret, prepend);
+
+ for (;;) {
+ found = strchrnul(str, escapee);
+ chunklen = found - str + 1;
+
+ /* Copy chunk up to and including escapee (or NUL) to ret */
+ memcpy(ret + retlen, str, chunklen);
+ retlen += chunklen;
+
+ if (*found == '\0') {
+ /* It wasn't escapee, it was NUL! */
+ ret[retlen - 1] = append; /* replace NUL */
+ ret[retlen] = '\0'; /* add NUL */
+ break;
+ }
+ ret[retlen++] = escapee; /* duplicate escapee */
+ str = found + 1;
+ }
+ return ret;
+}
+
+/* Returns strlen as a bonus */
+static unsigned
+replace_char(char *str, char from, char to)
+{
+ char *p = str;
+ while (*p) {
+ if (*p == from)
+ *p = to;
+ p++;
+ }
+ return p - str;
+}
+
+static void
+verbose_log(const char *str)
+{
+ bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
+}
+
+/* NB: status_str is char[4] packed into uint32_t */
+static void
+cmdio_write(uint32_t status_str, const char *str)
+{
+ char *response;
+ int len;
+
+ /* FTP uses telnet protocol for command link.
+ * In telnet, 0xff is an escape char, and needs to be escaped: */
+ response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
+
+ /* FTP sends embedded LFs as NULs */
+ len = replace_char(response, '\n', '\0');
+
+ response[len++] = '\n'; /* tack on trailing '\n' */
+ xwrite(STDOUT_FILENO, response, len);
+ if (G.verbose > 1)
+ verbose_log(response);
+ free(response);
+}
+
+static void
+cmdio_write_ok(unsigned status)
+{
+ *(uint32_t *) G.msg_ok = status;
+ xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
+ if (G.verbose > 1)
+ verbose_log(G.msg_ok);
+}
+#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
+
+/* TODO: output strerr(errno) if errno != 0? */
+static void
+cmdio_write_error(unsigned status)
+{
+ *(uint32_t *) G.msg_err = status;
+ xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
+ if (G.verbose > 0)
+ verbose_log(G.msg_err);
+}
+#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
+
+static void
+cmdio_write_raw(const char *p_text)
+{
+ xwrite_str(STDOUT_FILENO, p_text);
+ if (G.verbose > 1)
+ verbose_log(p_text);
+}
+
+static void
+timeout_handler(int sig UNUSED_PARAM)
+{
+ off_t pos;
+ int sv_errno = errno;
+
+ if ((int)(monotonic_sec() - G.end_time) >= 0)
+ goto timed_out;
+
+ if (!G.local_file_fd)
+ goto timed_out;
+
+ pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
+ if (pos == G.local_file_pos)
+ goto timed_out;
+ G.local_file_pos = pos;
+
+ alarm(G.timeout);
+ errno = sv_errno;
+ return;
+
+ timed_out:
+ cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
+/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
+ exit(1);
+}
+
+/* Simple commands */
+
+static void
+handle_pwd(void)
+{
+ char *cwd, *response;
+
+ cwd = xrealloc_getcwd_or_warn(NULL);
+ if (cwd == NULL)
+ cwd = xstrdup("");
+
+ /* We have to promote each " to "" */
+ response = escape_text(" \"", cwd, ('"' << 8) + '"');
+ free(cwd);
+ cmdio_write(STRNUM32(FTP_PWDOK), response);
+ free(response);
+}
+
+static void
+handle_cwd(void)
+{
+ if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ WRITE_OK(FTP_CWDOK);
+}
+
+static void
+handle_cdup(void)
+{
+ G.ftp_arg = (char*)"..";
+ handle_cwd();
+}
+
+static void
+handle_stat(void)
+{
+ cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
+ " TYPE: BINARY\r\n"
+ STR(FTP_STATOK)" Ok\r\n");
+}
+
+/* Examples of HELP and FEAT:
+# nc -vvv ftp.kernel.org 21
+ftp.kernel.org (130.239.17.4:21) open
+220 Welcome to ftp.kernel.org.
+FEAT
+211-Features:
+ EPRT
+ EPSV
+ MDTM
+ PASV
+ REST STREAM
+ SIZE
+ TVFS
+ UTF8
+211 End
+HELP
+214-The following commands are recognized.
+ ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
+ MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
+ RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
+ XPWD XRMD
+214 Help OK.
+*/
+static void
+handle_feat(unsigned status)
+{
+ cmdio_write(status, "-Features:");
+ cmdio_write_raw(" EPSV\r\n"
+ " PASV\r\n"
+ " REST STREAM\r\n"
+ " MDTM\r\n"
+ " SIZE\r\n");
+ cmdio_write(status, " Ok");
+}
+
+/* Download commands */
+
+static inline int
+port_active(void)
+{
+ return (G.port_addr != NULL);
+}
+
+static inline int
+pasv_active(void)
+{
+ return (G.pasv_listen_fd > STDOUT_FILENO);
+}
+
+static void
+port_pasv_cleanup(void)
+{
+ free(G.port_addr);
+ G.port_addr = NULL;
+ if (G.pasv_listen_fd > STDOUT_FILENO)
+ close(G.pasv_listen_fd);
+ G.pasv_listen_fd = -1;
+}
+
+/* On error, emits error code to the peer */
+static int
+ftpdataio_get_pasv_fd(void)
+{
+ int remote_fd;
+
+ remote_fd = accept(G.pasv_listen_fd, NULL, 0);
+
+ if (remote_fd < 0) {
+ WRITE_ERR(FTP_BADSENDCONN);
+ return remote_fd;
+ }
+
+ setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+ return remote_fd;
+}
+
+/* Clears port/pasv data.
+ * This means we dont waste resources, for example, keeping
+ * PASV listening socket open when it is no longer needed.
+ * On error, emits error code to the peer (or exits).
+ * On success, emits p_status_msg to the peer.
+ */
+static int
+get_remote_transfer_fd(const char *p_status_msg)
+{
+ int remote_fd;
+
+ if (pasv_active())
+ /* On error, emits error code to the peer */
+ remote_fd = ftpdataio_get_pasv_fd();
+ else
+ /* Exits on error */
+ remote_fd = xconnect_stream(G.port_addr);
+
+ port_pasv_cleanup();
+
+ if (remote_fd < 0)
+ return remote_fd;
+
+ cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
+ return remote_fd;
+}
+
+/* If there were neither PASV nor PORT, emits error code to the peer */
+static int
+port_or_pasv_was_seen(void)
+{
+ if (!pasv_active() && !port_active()) {
+ cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Exits on error */
+static unsigned
+bind_for_passive_mode(void)
+{
+ int fd;
+ unsigned port;
+
+ port_pasv_cleanup();
+
+ G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
+ setsockopt_reuseaddr(fd);
+
+ set_nport(&G.local_addr->u.sa, 0);
+ xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
+ xlisten(fd, 1);
+ getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
+
+ port = get_nport(&G.local_addr->u.sa);
+ port = ntohs(port);
+ return port;
+}
+
+/* Exits on error */
+static void
+handle_pasv(void)
+{
+ unsigned port;
+ char *addr, *response;
+
+ port = bind_for_passive_mode();
+
+ if (G.local_addr->u.sa.sa_family == AF_INET)
+ addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
+ else /* seen this in the wild done by other ftp servers: */
+ addr = xstrdup("0.0.0.0");
+ replace_char(addr, '.', ',');
+
+ response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
+ addr, (int)(port >> 8), (int)(port & 255));
+ free(addr);
+ cmdio_write_raw(response);
+ free(response);
+}
+
+/* Exits on error */
+static void
+handle_epsv(void)
+{
+ unsigned port;
+ char *response;
+
+ port = bind_for_passive_mode();
+ response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
+ cmdio_write_raw(response);
+ free(response);
+}
+
+static void
+handle_port(void)
+{
+ unsigned port, port_hi;
+ char *raw, *comma;
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+ socklen_t peer_ipv4_len;
+ struct sockaddr_in peer_ipv4;
+ struct in_addr port_ipv4_sin_addr;
+#endif
+
+ port_pasv_cleanup();
+
+ raw = G.ftp_arg;
+
+ /* PORT command format makes sense only over IPv4 */
+ if (!raw
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+ || G.local_addr->u.sa.sa_family != AF_INET
+#endif
+ ) {
+ bail:
+ WRITE_ERR(FTP_BADCMD);
+ return;
+ }
+
+ comma = strrchr(raw, ',');
+ if (comma == NULL)
+ goto bail;
+ *comma = '\0';
+ port = bb_strtou(&comma[1], NULL, 10);
+ if (errno || port > 0xff)
+ goto bail;
+
+ comma = strrchr(raw, ',');
+ if (comma == NULL)
+ goto bail;
+ *comma = '\0';
+ port_hi = bb_strtou(&comma[1], NULL, 10);
+ if (errno || port_hi > 0xff)
+ goto bail;
+ port |= port_hi << 8;
+
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+ replace_char(raw, ',', '.');
+
+ /* We are verifying that PORT's IP matches getpeername().
+ * Otherwise peer can make us open data connections
+ * to other hosts (security problem!)
+ * This code would be too simplistic:
+ * lsa = xdotted2sockaddr(raw, port);
+ * if (lsa == NULL) goto bail;
+ */
+ if (!inet_aton(raw, &port_ipv4_sin_addr))
+ goto bail;
+ peer_ipv4_len = sizeof(peer_ipv4);
+ if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
+ goto bail;
+ if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
+ goto bail;
+
+ G.port_addr = xdotted2sockaddr(raw, port);
+#else
+ G.port_addr = get_peer_lsa(STDIN_FILENO);
+ set_nport(&G.port_addr->u.sa, htons(port));
+#endif
+ WRITE_OK(FTP_PORTOK);
+}
+
+static void
+handle_rest(void)
+{
+ /* When ftp_arg == NULL simply restart from beginning */
+ G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
+ WRITE_OK(FTP_RESTOK);
+}
+
+static void
+handle_retr(void)
+{
+ struct stat statbuf;
+ off_t bytes_transferred;
+ int remote_fd;
+ int local_file_fd;
+ off_t offset = G.restart_pos;
+ char *response;
+
+ G.restart_pos = 0;
+
+ if (!port_or_pasv_was_seen())
+ return; /* port_or_pasv_was_seen emitted error response */
+
+ /* O_NONBLOCK is useful if file happens to be a device node */
+ local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
+ if (local_file_fd < 0) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+
+ if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
+ /* Note - pretend open failed */
+ WRITE_ERR(FTP_FILEFAIL);
+ goto file_close_out;
+ }
+ G.local_file_fd = local_file_fd;
+
+ /* Now deactive O_NONBLOCK, otherwise we have a problem
+ * on DMAPI filesystems such as XFS DMAPI.
+ */
+ ndelay_off(local_file_fd);
+
+ /* Set the download offset (from REST) if any */
+ if (offset != 0)
+ xlseek(local_file_fd, offset, SEEK_SET);
+
+ response = xasprintf(
+ " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
+ G.ftp_arg, statbuf.st_size);
+ remote_fd = get_remote_transfer_fd(response);
+ free(response);
+ if (remote_fd < 0)
+ goto file_close_out;
+
+ bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
+ close(remote_fd);
+ if (bytes_transferred < 0)
+ WRITE_ERR(FTP_BADSENDFILE);
+ else
+ WRITE_OK(FTP_TRANSFEROK);
+
+ file_close_out:
+ close(local_file_fd);
+ G.local_file_fd = 0;
+}
+
+/* List commands */
+
+static int
+popen_ls(const char *opt)
+{
+ const char *argv[5];
+ struct fd_pair outfd;
+ pid_t pid;
+
+ argv[0] = "ftpd";
+ argv[1] = opt; /* "-l" or "-1" */
+#if BB_MMU
+ argv[2] = "--";
+#else
+ /* NOMMU ftpd ls helper chdirs to argv[2],
+ * preventing peer from seeing real root. */
+ argv[2] = xrealloc_getcwd_or_warn(NULL);
+#endif
+ argv[3] = G.ftp_arg;
+ argv[4] = NULL;
+
+ /* Improve compatibility with non-RFC conforming FTP clients
+ * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
+ * See https://bugs.kde.org/show_bug.cgi?id=195578 */
+ if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
+ && G.ftp_arg && G.ftp_arg[0] == '-'
+ ) {
+ const char *tmp = strchr(G.ftp_arg, ' ');
+ if (tmp) /* skip the space */
+ tmp++;
+ argv[3] = tmp;
+ }
+
+ xpiped_pair(outfd);
+
+ /*fflush_all(); - so far we dont use stdio on output */
+ pid = BB_MMU ? xfork() : xvfork();
+ if (pid == 0) {
+ /* child */
+#if !BB_MMU
+ /* On NOMMU, we want to execute a child - copy of ourself.
+ * In chroot we usually can't do it. Thus we chdir
+ * out of the chroot back to original root,
+ * and (see later below) execute bb_busybox_exec_path
+ * relative to current directory */
+ if (fchdir(G.root_fd) != 0)
+ _exit(127);
+ /*close(G.root_fd); - close_on_exec_on() took care of this */
+#endif
+ /* NB: close _first_, then move fd! */
+ close(outfd.rd);
+ xmove_fd(outfd.wr, STDOUT_FILENO);
+ /* Opening /dev/null in chroot is hard.
+ * Just making sure STDIN_FILENO is opened
+ * to something harmless. Paranoia,
+ * ls won't read it anyway */
+ close(STDIN_FILENO);
+ dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
+#if BB_MMU
+ /* memset(&G, 0, sizeof(G)); - ls_main does it */
+ exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
+#else
+ /* + 1: we must use relative path here if in chroot.
+ * For example, execv("/proc/self/exe") will fail, since
+ * it looks for "/proc/self/exe" _relative to chroot!_ */
+ execv(bb_busybox_exec_path + 1, (char**) argv);
+ _exit(127);
+#endif
+ }
+
+ /* parent */
+ close(outfd.wr);
+#if !BB_MMU
+ free((char*)argv[2]);
+#endif
+ return outfd.rd;
+}
+
+enum {
+ USE_CTRL_CONN = 1,
+ LONG_LISTING = 2,
+};
+
+static void
+handle_dir_common(int opts)
+{
+ FILE *ls_fp;
+ char *line;
+ int ls_fd;
+
+ if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
+ return; /* port_or_pasv_was_seen emitted error response */
+
+ /* -n prevents user/groupname display,
+ * which can be problematic in chroot */
+ ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
+ ls_fp = xfdopen_for_read(ls_fd);
+
+ if (opts & USE_CTRL_CONN) {
+ /* STAT <filename> */
+ cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
+ while (1) {
+ line = xmalloc_fgetline(ls_fp);
+ if (!line)
+ break;
+ /* Hack: 0 results in no status at all */
+ /* Note: it's ok that we don't prepend space,
+ * ftp.kernel.org doesn't do that too */
+ cmdio_write(0, line);
+ free(line);
+ }
+ WRITE_OK(FTP_STATFILE_OK);
+ } else {
+ /* LIST/NLST [<filename>] */
+ int remote_fd = get_remote_transfer_fd(" Directory listing");
+ if (remote_fd >= 0) {
+ while (1) {
+ line = xmalloc_fgetline(ls_fp);
+ if (!line)
+ break;
+ /* I've seen clients complaining when they
+ * are fed with ls output with bare '\n'.
+ * Pity... that would be much simpler.
+ */
+/* TODO: need to s/LF/NUL/g here */
+ xwrite_str(remote_fd, line);
+ xwrite(remote_fd, "\r\n", 2);
+ free(line);
+ }
+ }
+ close(remote_fd);
+ WRITE_OK(FTP_TRANSFEROK);
+ }
+ fclose(ls_fp); /* closes ls_fd too */
+}
+static void
+handle_list(void)
+{
+ handle_dir_common(LONG_LISTING);
+}
+static void
+handle_nlst(void)
+{
+ /* NLST returns list of names, "\r\n" terminated without regard
+ * to the current binary flag. Names may start with "/",
+ * then they represent full names (we don't produce such names),
+ * otherwise names are relative to current directory.
+ * Embedded "\n" are replaced by NULs. This is safe since names
+ * can never contain NUL.
+ */
+ handle_dir_common(0);
+}
+static void
+handle_stat_file(void)
+{
+ handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
+}
+
+/* This can be extended to handle MLST, as all info is available
+ * in struct stat for that:
+ * MLST file_name
+ * 250-Listing file_name
+ * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
+ * 250 End
+ * Nano-doc:
+ * MLST [<file or dir name, "." assumed if not given>]
+ * Returned name should be either the same as requested, or fully qualified.
+ * If there was no parameter, return "" or (preferred) fully-qualified name.
+ * Returned "facts" (case is not important):
+ * size - size in octets
+ * modify - last modification time
+ * type - entry type (file,dir,OS.unix=block)
+ * (+ cdir and pdir types for MLSD)
+ * unique - unique id of file/directory (inode#)
+ * perm -
+ * a: can be appended to (APPE)
+ * d: can be deleted (RMD/DELE)
+ * f: can be renamed (RNFR)
+ * r: can be read (RETR)
+ * w: can be written (STOR)
+ * e: can CWD into this dir
+ * l: this dir can be listed (dir only!)
+ * c: can create files in this dir
+ * m: can create dirs in this dir (MKD)
+ * p: can delete files in this dir
+ * UNIX.mode - unix file mode
+ */
+static void
+handle_size_or_mdtm(int need_size)
+{
+ struct stat statbuf;
+ struct tm broken_out;
+ char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
+ | sizeof("NNN YYYYMMDDhhmmss\r\n")
+ ];
+
+ if (!G.ftp_arg
+ || stat(G.ftp_arg, &statbuf) != 0
+ || !S_ISREG(statbuf.st_mode)
+ ) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ if (need_size) {
+ sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
+ } else {
+ gmtime_r(&statbuf.st_mtime, &broken_out);
+ sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
+ broken_out.tm_year + 1900,
+ broken_out.tm_mon + 1,
+ broken_out.tm_mday,
+ broken_out.tm_hour,
+ broken_out.tm_min,
+ broken_out.tm_sec);
+ }
+ cmdio_write_raw(buf);
+}
+
+/* Upload commands */
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_mkd(void)
+{
+ if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ WRITE_OK(FTP_MKDIROK);
+}
+
+static void
+handle_rmd(void)
+{
+ if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ WRITE_OK(FTP_RMDIROK);
+}
+
+static void
+handle_dele(void)
+{
+ if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ WRITE_OK(FTP_DELEOK);
+}
+
+static void
+handle_rnfr(void)
+{
+ free(G.rnfr_filename);
+ G.rnfr_filename = xstrdup(G.ftp_arg);
+ WRITE_OK(FTP_RNFROK);
+}
+
+static void
+handle_rnto(void)
+{
+ int retval;
+
+ /* If we didn't get a RNFR, throw a wobbly */
+ if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
+ cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
+ return;
+ }
+
+ retval = rename(G.rnfr_filename, G.ftp_arg);
+ free(G.rnfr_filename);
+ G.rnfr_filename = NULL;
+
+ if (retval) {
+ WRITE_ERR(FTP_FILEFAIL);
+ return;
+ }
+ WRITE_OK(FTP_RENAMEOK);
+}
+
+static void
+handle_upload_common(int is_append, int is_unique)
+{
+ struct stat statbuf;
+ char *tempname;
+ off_t bytes_transferred;
+ off_t offset;
+ int local_file_fd;
+ int remote_fd;
+
+ offset = G.restart_pos;
+ G.restart_pos = 0;
+
+ if (!port_or_pasv_was_seen())
+ return; /* port_or_pasv_was_seen emitted error response */
+
+ tempname = NULL;
+ local_file_fd = -1;
+ if (is_unique) {
+ tempname = xstrdup(" FILE: uniq.XXXXXX");
+ local_file_fd = mkstemp(tempname + 7);
+ } else if (G.ftp_arg) {
+ int flags = O_WRONLY | O_CREAT | O_TRUNC;
+ if (is_append)
+ flags = O_WRONLY | O_CREAT | O_APPEND;
+ if (offset)
+ flags = O_WRONLY | O_CREAT;
+ local_file_fd = open(G.ftp_arg, flags, 0666);
+ }
+
+ if (local_file_fd < 0
+ || fstat(local_file_fd, &statbuf) != 0
+ || !S_ISREG(statbuf.st_mode)
+ ) {
+ free(tempname);
+ WRITE_ERR(FTP_UPLOADFAIL);
+ if (local_file_fd >= 0)
+ goto close_local_and_bail;
+ return;
+ }
+ G.local_file_fd = local_file_fd;
+
+ if (offset)
+ xlseek(local_file_fd, offset, SEEK_SET);
+
+ remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
+ free(tempname);
+
+ if (remote_fd < 0)
+ goto close_local_and_bail;
+
+ bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
+ close(remote_fd);
+ if (bytes_transferred < 0)
+ WRITE_ERR(FTP_BADSENDFILE);
+ else
+ WRITE_OK(FTP_TRANSFEROK);
+
+ close_local_and_bail:
+ close(local_file_fd);
+ G.local_file_fd = 0;
+}
+
+static void
+handle_stor(void)
+{
+ handle_upload_common(0, 0);
+}
+
+static void
+handle_appe(void)
+{
+ G.restart_pos = 0;
+ handle_upload_common(1, 0);
+}
+
+static void
+handle_stou(void)
+{
+ G.restart_pos = 0;
+ handle_upload_common(0, 1);
+}
+#endif /* ENABLE_FEATURE_FTP_WRITE */
+
+static uint32_t
+cmdio_get_cmd_and_arg(void)
+{
+ int len;
+ uint32_t cmdval;
+ char *cmd;
+
+ alarm(G.timeout);
+
+ free(G.ftp_cmd);
+ {
+ /* Paranoia. Peer may send 1 gigabyte long cmd... */
+ /* Using separate len_on_stk instead of len optimizes
+ * code size (allows len to be in CPU register) */
+ size_t len_on_stk = 8 * 1024;
+ G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
+ if (!cmd)
+ exit(0);
+ len = len_on_stk;
+ }
+
+ /* De-escape telnet: 0xff,0xff => 0xff */
+ /* RFC959 says that ABOR, STAT, QUIT may be sent even during
+ * data transfer, and may be preceded by telnet's "Interrupt Process"
+ * code (two-byte sequence 255,244) and then by telnet "Synch" code
+ * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
+ * and may generate SIGURG on our side. See RFC854).
+ * So far we don't support that (may install SIGURG handler if we'd want to),
+ * but we need to at least remove 255,xxx pairs. lftp sends those. */
+ /* Then de-escape FTP: NUL => '\n' */
+ /* Testing for \xff:
+ * Create file named '\xff': echo Hello >`echo -ne "\xff"`
+ * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
+ * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
+ * Testing for embedded LF:
+ * LF_HERE=`echo -ne "LF\nHERE"`
+ * echo Hello >"$LF_HERE"
+ * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
+ */
+ {
+ int dst, src;
+
+ /* Strip "\r\n" if it is there */
+ if (len != 0 && cmd[len - 1] == '\n') {
+ len--;
+ if (len != 0 && cmd[len - 1] == '\r')
+ len--;
+ cmd[len] = '\0';
+ }
+ src = strchrnul(cmd, 0xff) - cmd;
+ /* 99,99% there are neither NULs nor 255s and src == len */
+ if (src < len) {
+ dst = src;
+ do {
+ if ((unsigned char)(cmd[src]) == 255) {
+ src++;
+ /* 255,xxx - skip 255 */
+ if ((unsigned char)(cmd[src]) != 255) {
+ /* 255,!255 - skip both */
+ src++;
+ continue;
+ }
+ /* 255,255 - retain one 255 */
+ }
+ /* NUL => '\n' */
+ cmd[dst++] = cmd[src] ? cmd[src] : '\n';
+ src++;
+ } while (src < len);
+ cmd[dst] = '\0';
+ }
+ }
+
+ if (G.verbose > 1)
+ verbose_log(cmd);
+
+ G.ftp_arg = strchr(cmd, ' ');
+ if (G.ftp_arg != NULL)
+ *G.ftp_arg++ = '\0';
+
+ /* Uppercase and pack into uint32_t first word of the command */
+ cmdval = 0;
+ while (*cmd)
+ cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
+
+ return cmdval;
+}
+
+#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
+#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
+enum {
+ const_ALLO = mk_const4('A', 'L', 'L', 'O'),
+ const_APPE = mk_const4('A', 'P', 'P', 'E'),
+ const_CDUP = mk_const4('C', 'D', 'U', 'P'),
+ const_CWD = mk_const3('C', 'W', 'D'),
+ const_DELE = mk_const4('D', 'E', 'L', 'E'),
+ const_EPSV = mk_const4('E', 'P', 'S', 'V'),
+ const_FEAT = mk_const4('F', 'E', 'A', 'T'),
+ const_HELP = mk_const4('H', 'E', 'L', 'P'),
+ const_LIST = mk_const4('L', 'I', 'S', 'T'),
+ const_MDTM = mk_const4('M', 'D', 'T', 'M'),
+ const_MKD = mk_const3('M', 'K', 'D'),
+ const_MODE = mk_const4('M', 'O', 'D', 'E'),
+ const_NLST = mk_const4('N', 'L', 'S', 'T'),
+ const_NOOP = mk_const4('N', 'O', 'O', 'P'),
+ const_PASS = mk_const4('P', 'A', 'S', 'S'),
+ const_PASV = mk_const4('P', 'A', 'S', 'V'),
+ const_PORT = mk_const4('P', 'O', 'R', 'T'),
+ const_PWD = mk_const3('P', 'W', 'D'),
+ const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
+ const_REST = mk_const4('R', 'E', 'S', 'T'),
+ const_RETR = mk_const4('R', 'E', 'T', 'R'),
+ const_RMD = mk_const3('R', 'M', 'D'),
+ const_RNFR = mk_const4('R', 'N', 'F', 'R'),
+ const_RNTO = mk_const4('R', 'N', 'T', 'O'),
+ const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
+ const_STAT = mk_const4('S', 'T', 'A', 'T'),
+ const_STOR = mk_const4('S', 'T', 'O', 'R'),
+ const_STOU = mk_const4('S', 'T', 'O', 'U'),
+ const_STRU = mk_const4('S', 'T', 'R', 'U'),
+ const_SYST = mk_const4('S', 'Y', 'S', 'T'),
+ const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
+ const_USER = mk_const4('U', 'S', 'E', 'R'),
+
+#if !BB_MMU
+ OPT_l = (1 << 0),
+ OPT_1 = (1 << 1),
+#endif
+ OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
+ OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
+ OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
+};
+
+int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if !BB_MMU
+int ftpd_main(int argc, char **argv)
+#else
+int ftpd_main(int argc UNUSED_PARAM, char **argv)
+#endif
+{
+ unsigned abs_timeout;
+ unsigned verbose_S;
+ smallint opts;
+
+ INIT_G();
+
+ abs_timeout = 1 * 60 * 60;
+ verbose_S = 0;
+ G.timeout = 2 * 60;
+ opt_complementary = "t+:T+:vv:SS";
+#if BB_MMU
+ opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+#else
+ opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+ if (opts & (OPT_l|OPT_1)) {
+ /* Our secret backdoor to ls */
+/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
+/* TODO: pass -A? It shows dot files */
+/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
+ xchdir(argv[2]);
+ argv[2] = (char*)"--";
+ /* memset(&G, 0, sizeof(G)); - ls_main does it */
+ return ls_main(argc, argv);
+ }
+#endif
+ if (G.verbose < verbose_S)
+ G.verbose = verbose_S;
+ if (abs_timeout | G.timeout) {
+ if (abs_timeout == 0)
+ abs_timeout = INT_MAX;
+ G.end_time = monotonic_sec() + abs_timeout;
+ if (G.timeout > abs_timeout)
+ G.timeout = abs_timeout;
+ }
+ strcpy(G.msg_ok + 4, MSG_OK );
+ strcpy(G.msg_err + 4, MSG_ERR);
+
+ G.local_addr = get_sock_lsa(STDIN_FILENO);
+ if (!G.local_addr) {
+ /* This is confusing:
+ * bb_error_msg_and_die("stdin is not a socket");
+ * Better: */
+ bb_show_usage();
+ /* Help text says that ftpd must be used as inetd service,
+ * which is by far the most usual cause of get_sock_lsa
+ * failure */
+ }
+
+ if (!(opts & OPT_v))
+ logmode = LOGMODE_NONE;
+ if (opts & OPT_S) {
+ /* LOG_NDELAY is needed since we may chroot later */
+ openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ logmode |= LOGMODE_SYSLOG;
+ }
+ if (logmode)
+ applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
+
+#if !BB_MMU
+ G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
+ close_on_exec_on(G.root_fd);
+#endif
+
+ if (argv[optind]) {
+ xchroot(argv[optind]);
+ }
+
+ //umask(077); - admin can set umask before starting us
+
+ /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Set up options on the command socket (do we need these all? why?) */
+ setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
+ setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+ /* Telnet protocol over command link may send "urgent" data,
+ * we prefer it to be received in the "normal" data stream: */
+ setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
+
+ WRITE_OK(FTP_GREET);
+ signal(SIGALRM, timeout_handler);
+
+#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
+ {
+ smallint user_was_specified = 0;
+ while (1) {
+ uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+ if (cmdval == const_USER) {
+ if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
+ cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
+ else {
+ user_was_specified = 1;
+ cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
+ }
+ } else if (cmdval == const_PASS) {
+ if (user_was_specified)
+ break;
+ cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
+ } else if (cmdval == const_QUIT) {
+ WRITE_OK(FTP_GOODBYE);
+ return 0;
+ } else {
+ cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
+ }
+ }
+ }
+ WRITE_OK(FTP_LOGINOK);
+#endif
+
+ /* RFC-959 Section 5.1
+ * The following commands and options MUST be supported by every
+ * server-FTP and user-FTP, except in cases where the underlying
+ * file system or operating system does not allow or support
+ * a particular command.
+ * Type: ASCII Non-print, IMAGE, LOCAL 8
+ * Mode: Stream
+ * Structure: File, Record*
+ * (Record structure is REQUIRED only for hosts whose file
+ * systems support record structure).
+ * Commands:
+ * USER, PASS, ACCT, [bbox: ACCT not supported]
+ * PORT, PASV,
+ * TYPE, MODE, STRU,
+ * RETR, STOR, APPE,
+ * RNFR, RNTO, DELE,
+ * CWD, CDUP, RMD, MKD, PWD,
+ * LIST, NLST,
+ * SYST, STAT,
+ * HELP, NOOP, QUIT.
+ */
+ /* ACCOUNT (ACCT)
+ * "The argument field is a Telnet string identifying the user's account.
+ * The command is not necessarily related to the USER command, as some
+ * sites may require an account for login and others only for specific
+ * access, such as storing files. In the latter case the command may
+ * arrive at any time.
+ * There are reply codes to differentiate these cases for the automation:
+ * when account information is required for login, the response to
+ * a successful PASSword command is reply code 332. On the other hand,
+ * if account information is NOT required for login, the reply to
+ * a successful PASSword command is 230; and if the account information
+ * is needed for a command issued later in the dialogue, the server
+ * should return a 332 or 532 reply depending on whether it stores
+ * (pending receipt of the ACCounT command) or discards the command,
+ * respectively."
+ */
+
+ while (1) {
+ uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+ if (cmdval == const_QUIT) {
+ WRITE_OK(FTP_GOODBYE);
+ return 0;
+ }
+ else if (cmdval == const_USER)
+ /* This would mean "ok, now give me PASS". */
+ /*WRITE_OK(FTP_GIVEPWORD);*/
+ /* vsftpd can be configured to not require that,
+ * and this also saves one roundtrip:
+ */
+ WRITE_OK(FTP_LOGINOK);
+ else if (cmdval == const_PASS)
+ WRITE_OK(FTP_LOGINOK);
+ else if (cmdval == const_NOOP)
+ WRITE_OK(FTP_NOOPOK);
+ else if (cmdval == const_TYPE)
+ WRITE_OK(FTP_TYPEOK);
+ else if (cmdval == const_STRU)
+ WRITE_OK(FTP_STRUOK);
+ else if (cmdval == const_MODE)
+ WRITE_OK(FTP_MODEOK);
+ else if (cmdval == const_ALLO)
+ WRITE_OK(FTP_ALLOOK);
+ else if (cmdval == const_SYST)
+ cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
+ else if (cmdval == const_PWD)
+ handle_pwd();
+ else if (cmdval == const_CWD)
+ handle_cwd();
+ else if (cmdval == const_CDUP) /* cd .. */
+ handle_cdup();
+ /* HELP is nearly useless, but we can reuse FEAT for it */
+ /* lftp uses FEAT */
+ else if (cmdval == const_HELP || cmdval == const_FEAT)
+ handle_feat(cmdval == const_HELP
+ ? STRNUM32(FTP_HELP)
+ : STRNUM32(FTP_STATOK)
+ );
+ else if (cmdval == const_LIST) /* ls -l */
+ handle_list();
+ else if (cmdval == const_NLST) /* "name list", bare ls */
+ handle_nlst();
+ /* SIZE is crucial for wget's download indicator etc */
+ /* Mozilla, lftp use MDTM (presumably for caching) */
+ else if (cmdval == const_SIZE || cmdval == const_MDTM)
+ handle_size_or_mdtm(cmdval == const_SIZE);
+ else if (cmdval == const_STAT) {
+ if (G.ftp_arg == NULL)
+ handle_stat();
+ else
+ handle_stat_file();
+ }
+ else if (cmdval == const_PASV)
+ handle_pasv();
+ else if (cmdval == const_EPSV)
+ handle_epsv();
+ else if (cmdval == const_RETR)
+ handle_retr();
+ else if (cmdval == const_PORT)
+ handle_port();
+ else if (cmdval == const_REST)
+ handle_rest();
+#if ENABLE_FEATURE_FTP_WRITE
+ else if (opts & OPT_w) {
+ if (cmdval == const_STOR)
+ handle_stor();
+ else if (cmdval == const_MKD)
+ handle_mkd();
+ else if (cmdval == const_RMD)
+ handle_rmd();
+ else if (cmdval == const_DELE)
+ handle_dele();
+ else if (cmdval == const_RNFR) /* "rename from" */
+ handle_rnfr();
+ else if (cmdval == const_RNTO) /* "rename to" */
+ handle_rnto();
+ else if (cmdval == const_APPE)
+ handle_appe();
+ else if (cmdval == const_STOU) /* "store unique" */
+ handle_stou();
+ else
+ goto bad_cmd;
+ }
+#endif
+#if 0
+ else if (cmdval == const_STOR
+ || cmdval == const_MKD
+ || cmdval == const_RMD
+ || cmdval == const_DELE
+ || cmdval == const_RNFR
+ || cmdval == const_RNTO
+ || cmdval == const_APPE
+ || cmdval == const_STOU
+ ) {
+ cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
+ }
+#endif
+ else {
+ /* Which unsupported commands were seen in the wild?
+ * (doesn't necessarily mean "we must support them")
+ * foo 1.2.3: XXXX - comment
+ */
+#if ENABLE_FEATURE_FTP_WRITE
+ bad_cmd:
+#endif
+ cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
+ }
+ }
+}
diff --git a/ap/app/busybox/src/networking/ftpgetput.c b/ap/app/busybox/src/networking/ftpgetput.c
new file mode 100644
index 0000000..8283366
--- /dev/null
+++ b/ap/app/busybox/src/networking/ftpgetput.c
@@ -0,0 +1,360 @@
+/* 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 source tree.
+ */
+
+//usage:#define ftpget_trivial_usage
+//usage: "[OPTIONS] HOST [LOCAL_FILE] REMOTE_FILE"
+//usage:#define ftpget_full_usage "\n\n"
+//usage: "Download a file via FTP\n"
+//usage: IF_FEATURE_FTPGETPUT_LONG_OPTIONS(
+//usage: "\n -c,--continue Continue previous transfer"
+//usage: "\n -v,--verbose Verbose"
+//usage: "\n -u,--username USER Username"
+//usage: "\n -p,--password PASS Password"
+//usage: "\n -P,--port NUM Port"
+//usage: )
+//usage: IF_NOT_FEATURE_FTPGETPUT_LONG_OPTIONS(
+//usage: "\n -c Continue previous transfer"
+//usage: "\n -v Verbose"
+//usage: "\n -u USER Username"
+//usage: "\n -p PASS Password"
+//usage: "\n -P NUM Port"
+//usage: )
+//usage:
+//usage:#define ftpput_trivial_usage
+//usage: "[OPTIONS] HOST [REMOTE_FILE] LOCAL_FILE"
+//usage:#define ftpput_full_usage "\n\n"
+//usage: "Upload a file to a FTP server\n"
+//usage: IF_FEATURE_FTPGETPUT_LONG_OPTIONS(
+//usage: "\n -v,--verbose Verbose"
+//usage: "\n -u,--username USER Username"
+//usage: "\n -p,--password PASS Password"
+//usage: "\n -P,--port NUM Port"
+//usage: )
+//usage: IF_NOT_FEATURE_FTPGETPUT_LONG_OPTIONS(
+//usage: "\n -v Verbose"
+//usage: "\n -u USER Username"
+//usage: "\n -p PASS Password"
+//usage: "\n -P NUM Port number"
+//usage: )
+
+#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[4]; /* actually [BUFSZ] */
+} FIX_ALIASING;
+#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"); /* for ftp_die */
+ 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->u.sa, 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"u", 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)
+{
+ 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 = "-2:vv:cc"; /* must have 2 to 3 params; -v and -c count */
+ 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] ? argv[2] : argv[1]);
+}
diff --git a/ap/app/busybox/src/networking/hostname.c b/ap/app/busybox/src/networking/hostname.c
new file mode 100644
index 0000000..d2516b5
--- /dev/null
+++ b/ap/app/busybox/src/networking/hostname.c
@@ -0,0 +1,176 @@
+/* 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.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define hostname_trivial_usage
+//usage: "[OPTIONS] [HOSTNAME | -F FILE]"
+//usage:#define hostname_full_usage "\n\n"
+//usage: "Get or set hostname or DNS domain name\n"
+//usage: "\n -s Short"
+//usage: "\n -i Addresses for the hostname"
+//usage: "\n -d DNS domain name"
+//usage: "\n -f Fully qualified domain name"
+//usage: "\n -F FILE Use FILE's content as hostname"
+//usage:
+//usage:#define hostname_example_usage
+//usage: "$ hostname\n"
+//usage: "sage\n"
+//usage:
+//usage:#define dnsdomainname_trivial_usage NOUSAGE_STR
+//usage:#define dnsdomainname_full_usage ""
+
+#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))) {
+// if (errno == EPERM)
+// bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+ bb_perror_msg_and_die("sethostname");
+ }
+}
+
+/* Manpage circa 2009:
+ *
+ * hostname [-v] [-a] [--alias] [-d] [--domain] [-f] [--fqdn] [--long]
+ * [-i] [--ip-address] [-s] [--short] [-y] [--yp] [--nis]
+ *
+ * hostname [-v] [-F filename] [--file filename] / [hostname]
+ *
+ * domainname [-v] [-F filename] [--file filename] / [name]
+ * { bbox: not supported }
+ *
+ * nodename [-v] [-F filename] [--file filename] / [name]
+ * { bbox: not supported }
+ *
+ * dnsdomainname [-v]
+ * { bbox: supported: Linux kernel build needs this }
+ * nisdomainname [-v]
+ * { bbox: not supported }
+ * ypdomainname [-v]
+ * { bbox: not supported }
+ *
+ * -a, --alias
+ * Display the alias name of the host (if used).
+ * { bbox: not supported }
+ * -d, --domain
+ * Display the name of the DNS domain. Don't use the command
+ * domainname to get the DNS domain name because it will show the
+ * NIS domain name and not the DNS domain name. Use dnsdomainname
+ * instead.
+ * -f, --fqdn, --long
+ * Display the FQDN (Fully Qualified Domain Name). A FQDN consists
+ * of a short host name and the DNS domain name. Unless you are
+ * using bind or NIS for host lookups you can change the FQDN and
+ * the DNS domain name (which is part of the FQDN) in the
+ * /etc/hosts file.
+ * -i, --ip-address
+ * Display the IP address(es) of the host.
+ * -s, --short
+ * Display the short host name. This is the host name cut at the
+ * first dot.
+ * -v, --verbose
+ * Be verbose and tell what's going on.
+ * { bbox: supported but ignored }
+ * -y, --yp, --nis
+ * Display the NIS domain name. If a parameter is given (or --file
+ * name ) then root can also set a new NIS domain.
+ * { bbox: not supported }
+ * -F, --file filename
+ * Read the host name from the specified file. Comments (lines
+ * starting with a `#') are ignored.
+ */
+int hostname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hostname_main(int argc UNUSED_PARAM, char **argv)
+{
+ enum {
+ OPT_d = 0x1,
+ OPT_f = 0x2,
+ OPT_i = 0x4,
+ OPT_s = 0x8,
+ OPT_F = 0x10,
+ OPT_dfis = 0xf,
+ };
+
+ unsigned opts;
+ char *buf;
+ char *hostname_str;
+
+#if ENABLE_LONG_OPTS
+ applet_long_options =
+ "domain\0" No_argument "d"
+ "fqdn\0" No_argument "f"
+ //Enable if seen in active use in some distro:
+ // "long\0" No_argument "f"
+ // "ip-address\0" No_argument "i"
+ // "short\0" No_argument "s"
+ // "verbose\0" No_argument "v"
+ "file\0" No_argument "F"
+ ;
+
+#endif
+ /* dnsdomainname from net-tools 1.60, hostname 1.100 (2001-04-14),
+ * supports hostname's options too (not just -v as manpage says) */
+ opts = getopt32(argv, "dfisF:v", &hostname_str);
+ argv += optind;
+ buf = safe_gethostname();
+ if (applet_name[0] == 'd') /* dnsdomainname? */
+ opts = OPT_d;
+
+ if (opts & OPT_dfis) {
+ /* Cases when we need full hostname (or its part) */
+ struct hostent *hp;
+ char *p;
+
+ hp = xgethostbyname(buf);
+ p = strchrnul(hp->h_name, '.');
+ if (opts & OPT_f) {
+ puts(hp->h_name);
+ } else if (opts & OPT_s) {
+ *p = '\0';
+ puts(hp->h_name);
+ } else if (opts & OPT_d) {
+ if (*p)
+ puts(p + 1);
+ } else /*if (opts & OPT_i)*/ {
+ if (hp->h_length == sizeof(struct in_addr)) {
+ struct in_addr **h_addr_list = (struct in_addr **)hp->h_addr_list;
+ while (*h_addr_list) {
+ printf(h_addr_list[1] ? "%s " : "%s", inet_ntoa(**h_addr_list));
+ h_addr_list++;
+ }
+ bb_putchar('\n');
+ }
+ }
+ } else if (opts & OPT_F) {
+ /* Set the hostname */
+ do_sethostname(hostname_str, 1);
+ } else if (argv[0]) {
+ /* Set the hostname */
+ do_sethostname(argv[0], 0);
+ } else {
+ /* Just print the current hostname */
+ puts(buf);
+ }
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(buf);
+ return EXIT_SUCCESS;
+}
diff --git a/ap/app/busybox/src/networking/httpd.c b/ap/app/busybox/src/networking/httpd.c
new file mode 100644
index 0000000..1934bb2
--- /dev/null
+++ b/ap/app/busybox/src/networking/httpd.c
@@ -0,0 +1,2571 @@
+/* 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>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ *****************************************************************************
+ *
+ * Typical usage:
+ * For non root user:
+ * httpd -p 8080 -h $HOME/public_html
+ * For daemon start from rc script with uid=0:
+ * httpd -u www
+ * which is equivalent to (assuming user www has uid 80):
+ * httpd -p 80 -u 80 -h $PWD -c /etc/httpd.conf -r "Web Server Authentication"
+ *
+ * When an url starts with "/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.
+ *
+ * If directory URL is given, no index.html is found and CGI support is enabled,
+ * cgi-bin/index.cgi will be run. Directory to list is ../$QUERY_STRING.
+ * See httpd_indexcgi.c for an example GCI code.
+ *
+ * Doc:
+ * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+ *
+ * The applet can also be invoked as an 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 an 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/
+ * /adm:root:* # or user root, pwd from /etc/passwd on urls starting with /adm/
+ * /wiki:*:* # or any user from /etc/passwd with according pwd on urls starting with /wiki/
+ * .au:audio/basic # additional mime type for audio.au files
+ * *.php:/path/php # run xxx.php 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 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.
+ *
+ */
+ /* TODO: use TCP_CORK, parse_config() */
+
+//usage:#define httpd_trivial_usage
+//usage: "[-ifv[v]]"
+//usage: " [-c CONFFILE]"
+//usage: " [-p [IP:]PORT]"
+//usage: IF_FEATURE_HTTPD_SETUID(" [-u USER[:GRP]]")
+//usage: IF_FEATURE_HTTPD_BASIC_AUTH(" [-r REALM]")
+//usage: " [-h HOME]\n"
+//usage: "or httpd -d/-e" IF_FEATURE_HTTPD_AUTH_MD5("/-m") " STRING"
+//usage:#define httpd_full_usage "\n\n"
+//usage: "Listen for incoming HTTP requests\n"
+//usage: "\n -i Inetd mode"
+//usage: "\n -f Don't daemonize"
+//usage: "\n -v[v] Verbose"
+//usage: "\n -p [IP:]PORT Bind to IP:PORT (default *:80)"
+//usage: IF_FEATURE_HTTPD_SETUID(
+//usage: "\n -u USER[:GRP] Set uid/gid after binding to port")
+//usage: IF_FEATURE_HTTPD_BASIC_AUTH(
+//usage: "\n -r REALM Authentication Realm for Basic Authentication")
+//usage: "\n -h HOME Home directory (default .)"
+//usage: "\n -c FILE Configuration file (default {/etc,HOME}/httpd.conf)"
+//usage: IF_FEATURE_HTTPD_AUTH_MD5(
+//usage: "\n -m STRING MD5 crypt STRING")
+//usage: "\n -e STRING HTML encode STRING"
+//usage: "\n -d STRING URL decode STRING"
+
+#include "libbb.h"
+#if ENABLE_PAM
+/* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
+# undef setlocale
+/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
+ * Apparently they like to confuse people. */
+# include <security/pam_appl.h>
+# include <security/pam_misc.h>
+#endif
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+# include <sys/sendfile.h>
+#endif
+/* amount of buffering in a pipe */
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096
+#endif
+
+#define DEBUG 0
+
+#define IOBUF_SIZE 8192
+#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";
+static const char index_html[] ALIGN1 = "index.html";
+
+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 */
+#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 *opt_c_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 */
+
+ IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
+ IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
+ IF_FEATURE_HTTPD_CGI(char *referer;)
+ IF_FEATURE_HTTPD_CGI(char *user_agent;)
+ IF_FEATURE_HTTPD_CGI(char *host;)
+ IF_FEATURE_HTTPD_CGI(char *http_accept;)
+ IF_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
+ Htaccess *mime_a; /* config mime types */
+#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
+#if ENABLE_FEATURE_HTTPD_GZIP
+ /* client can handle gzip / we are going to send gzip */
+ smallint content_gzip;
+#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 opt_c_configFile (G.opt_c_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 host (G.host )
+#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 )
+#else
+enum {
+ range_start = -1,
+ range_end = MAXINT(off_t) - 1,
+ range_len = MAXINT(off_t),
+};
+#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 )
+#if ENABLE_FEATURE_HTTPD_GZIP
+# define content_gzip (G.content_gzip )
+#else
+# define content_gzip 0
+#endif
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
+ IF_FEATURE_HTTPD_RANGES(range_start = -1;) \
+ bind_addr_or_port = "80"; \
+ index_page = index_html; \
+ file_size = -1; \
+} while (0)
+
+
+#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;
+}
+
+static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
+{
+ free_llist((has_next_ptr**)pptr);
+}
+
+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.
+ *
+ * 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 param: */
+enum {
+ FIRST_PARSE = 0, /* path will be "/etc" */
+ SIGNALED_PARSE = 1, /* path will be "/etc" */
+ SUBDIR_PARSE = 2, /* path will be derived from URL */
+};
+static void parse_conf(const char *path, int flag)
+{
+ /* internally used extra flag state */
+ enum { TRY_CURDIR_PARSE = 3 };
+
+ FILE *f;
+ const char *filename;
+ char buf[160];
+
+ /* discard old rules */
+ free_Htaccess_IP_list(&ip_a_d);
+ flg_deny_all = 0;
+ /* retain previous auth and mime config only for subdir parse */
+ if (flag != SUBDIR_PARSE) {
+ free_Htaccess_list(&mime_a);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ free_Htaccess_list(&g_auth);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ free_Htaccess_list(&script_i);
+#endif
+ }
+
+ filename = opt_c_configFile;
+ 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) { /* SUBDIR or TRY_CURDIR */
+ /* config file not found, no changes to config */
+ return;
+ }
+ if (flag == FIRST_PARSE) {
+ /* -c CONFFILE given, but CONFFILE doesn't exist? */
+ if (opt_c_configFile)
+ bb_simple_perror_msg_and_die(opt_c_configFile);
+ /* else: no -c, thus we looked at /etc/httpd.conf,
+ * and it's not there. try ./httpd.conf: */
+ }
+ flag = TRY_CURDIR_PARSE;
+ filename = HTTPD_CONF;
+ }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ /* in "/file:user:pass" lines, we prepend path in subdirs */
+ if (flag != SUBDIR_PARSE)
+ path = "";
+#endif
+ /* The lines can be:
+ *
+ * I:default_index_file
+ * H:http_home
+ * [AD]:IP[/mask] # allow/deny, * for wildcard
+ * Ennn:error.html # error page for status nnn
+ * P:/url:[http://]hostname[:port]/new/path # reverse proxy
+ * .ext:mime/type # mime type
+ * *.php:/path/php # run xxx.php through an interpreter
+ * /file:user:pass # username and password
+ */
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ unsigned strlen_buf;
+ unsigned char ch;
+ char *after_colon;
+
+ { /* remove all whitespace, and # comments */
+ char *p, *p0;
+
+ p0 = buf;
+ /* skip non-whitespace beginning. Often the whole line
+ * is non-whitespace. We want this case to work fast,
+ * without needless copying, therefore we don't merge
+ * this operation into next while loop. */
+ while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
+ && ch != ' ' && ch != '\t'
+ ) {
+ p0++;
+ }
+ p = p0;
+ /* if we enter this loop, we have some whitespace.
+ * discard it */
+ while (ch != '\0' && ch != '\n' && ch != '#') {
+ if (ch != ' ' && ch != '\t') {
+ *p++ = ch;
+ }
+ ch = *++p0;
+ }
+ *p = '\0';
+ strlen_buf = p - buf;
+ if (strlen_buf == 0)
+ continue; /* empty line */
+ }
+
+ after_colon = strchr(buf, ':');
+ /* strange line? */
+ if (after_colon == NULL || *++after_colon == '\0')
+ goto config_error;
+
+ ch = (buf[0] & ~0x20); /* toupper if it's a letter */
+
+ if (ch == 'I') {
+ if (index_page != index_html)
+ free((char*)index_page);
+ index_page = xstrdup(after_colon);
+ continue;
+ }
+
+ /* do not allow jumping around using H in subdir's configs */
+ if (flag == FIRST_PARSE && ch == 'H') {
+ home_httpd = xstrdup(after_colon);
+ xchdir(home_httpd);
+ continue;
+ }
+
+ if (ch == 'A' || ch == 'D') {
+ Htaccess_IP *pip;
+
+ if (*after_colon == '*') {
+ if (ch == 'D') {
+ /* memorize "deny all" */
+ flg_deny_all = 1;
+ }
+ /* skip assumed "A:*", it is a default anyway */
+ continue;
+ }
+ /* store "allow/deny IP/mask" line */
+ pip = xzalloc(sizeof(*pip));
+ if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
+ /* IP{/mask} syntax error detected, protect all */
+ ch = 'D';
+ pip->mask = 0;
+ }
+ pip->allow_deny = ch;
+ if (ch == 'D') {
+ /* Deny:from_IP - prepend */
+ pip->next = ip_a_d;
+ ip_a_d = pip;
+ } else {
+ /* A:from_IP - append (thus all D's precedes A's) */
+ 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 && ch == 'E') {
+ unsigned i;
+ int status = atoi(buf + 1); /* error status code */
+
+ if (status < HTTP_CONTINUE) {
+ goto config_error;
+ }
+ /* 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 && ch == '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) {
+ goto config_error;
+ }
+ *host_port++ = '\0';
+ if (strncmp(host_port, "http://", 7) == 0)
+ host_port += 7;
+ if (*host_port == '\0') {
+ goto config_error;
+ }
+ url_to = strchr(host_port, '/');
+ if (url_to == NULL) {
+ goto config_error;
+ }
+ *url_to = '\0';
+ proxy_entry = xzalloc(sizeof(*proxy_entry));
+ 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
+ /* the rest of directives are non-alphabetic,
+ * must avoid using "toupper'ed" ch */
+ ch = buf[0];
+
+ if (ch == '.' /* ".ext:mime/type" */
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ || (ch == '*' && buf[1] == '.') /* "*.php:/path/php" */
+#endif
+ ) {
+ char *p;
+ Htaccess *cur;
+
+ cur = xzalloc(sizeof(*cur) /* includes space for NUL */ + strlen_buf);
+ strcpy(cur->before_colon, buf);
+ p = cur->before_colon + (after_colon - buf);
+ p[-1] = '\0';
+ cur->after_colon = p;
+ if (ch == '.') {
+ /* .mime line: prepend to mime_a list */
+ cur->next = mime_a;
+ mime_a = cur;
+ }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ else {
+ /* script interpreter line: prepend to script_i list */
+ cur->next = script_i;
+ script_i = cur;
+ }
+#endif
+ continue;
+ }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (ch == '/') { /* "/file:user:pass" */
+ char *p;
+ Htaccess *cur;
+ unsigned file_len;
+
+ /* note: path is "" unless we are in SUBDIR parse,
+ * otherwise it does NOT start with "/" */
+ cur = xzalloc(sizeof(*cur) /* includes space for NUL */
+ + 1 + strlen(path)
+ + strlen_buf
+ );
+ /* form "/path/file" */
+ sprintf(cur->before_colon, "/%s%.*s",
+ path,
+ (int) (after_colon - buf - 1), /* includes "/", but not ":" */
+ buf);
+ /* canonicalize it */
+ p = bb_simplify_abs_path_inplace(cur->before_colon);
+ file_len = p - cur->before_colon;
+ /* add "user:pass" after NUL */
+ strcpy(++p, after_colon);
+ cur->after_colon = p;
+
+ /* insert cur into g_auth */
+ /* g_auth is sorted by decreased filename length */
+ {
+ Htaccess *auth, **authp;
+
+ authp = &g_auth;
+ while ((auth = *authp) != NULL) {
+ if (file_len >= strlen(auth->before_colon)) {
+ /* insert cur before auth */
+ cur->next = auth;
+ break;
+ }
+ authp = &auth->next;
+ }
+ *authp = cur;
+ }
+ continue;
+ }
+#endif /* BASIC_AUTH */
+
+ /* the line is not recognized */
+ config_error:
+ bb_error_msg("config error '%s' in '%s'", buf, filename);
+ } /* 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++) != '\0') {
+ /* 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
+
+#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(NULL);
+ 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"u-%"OFF_FMT"u/%"OFF_FMT"u\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"u\r\n",
+ tmp_str,
+ content_gzip ? "Transfer-length:" : "Content-length:",
+ file_size
+ );
+ }
+
+ if (content_gzip)
+ len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
+
+ 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)
+{
+ IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
+ 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;
+
+ alarm(HEADER_READ_TIMEOUT);
+ 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';
+ break;
+ }
+ 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 */
+ if (toCgi_wr != fromCgi_rd)
+ close(toCgi_wr);
+ toCgi_wr = 0;
+ }
+ }
+
+ /* Now wait on the set of sockets */
+ count = safe_poll(pfd, toCgi_wr ? TO_CGI+1 : FROM_CGI+1, -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 /).
+ * const char *orig_uri The original URI before rewriting (if any)
+ * 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 *orig_uri,
+ 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 *orig_uri,
+ 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, *last_slash;
+ 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 */
+ last_slash = script = (char*)url;
+ while ((script = strchr(script + 1, '/')) != NULL) {
+ int dir;
+ *script = '\0';
+ dir = is_directory(url + 1, /*followlinks:*/ 1);
+ *script = '/';
+ if (!dir) {
+ /* not directory, found script.cgi/PATH_INFO */
+ break;
+ }
+ /* is directory, find next '/' */
+ last_slash = script;
+ }
+ setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */
+ setenv1("REQUEST_METHOD", request);
+ if (g_query) {
+ putenv(xasprintf("%s=%s?%s", "REQUEST_URI", orig_uri, g_query));
+ } else {
+ setenv1("REQUEST_URI", orig_uri);
+ }
+ 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);
+ setenv1("HTTP_HOST", host); /* set to "" if NULL */
+ /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
+ * just run "env SERVER_NAME=xyz httpd ..." instead */
+
+ xpiped_pair(fromCgi);
+ xpiped_pair(toCgi);
+
+ pid = vfork();
+ if (pid < 0) {
+ /* TODO: log perror? */
+ log_and_exit();
+ }
+
+ if (pid == 0) {
+ /* 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 = last_slash;
+ if (script != url) { /* paranoia */
+ *script = '\0';
+ if (chdir(url + 1) != 0) {
+ bb_perror_msg("can't change directory to '%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("can't execute '%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 NOINLINE void send_file_and_exit(const char *url, int what)
+{
+ char *suffix;
+ int fd;
+ ssize_t count;
+
+ if (content_gzip) {
+ /* does <url>.gz exist? Then use it instead */
+ char *gzurl = xasprintf("%s.gz", url);
+ fd = open(gzurl, O_RDONLY);
+ free(gzurl);
+ if (fd != -1) {
+ struct stat sb;
+ fstat(fd, &sb);
+ file_size = sb.st_size;
+ last_mod = sb.st_mtime;
+ } else {
+ IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
+ fd = open(url, O_RDONLY);
+ }
+ } else {
+ fd = open(url, O_RDONLY);
+ }
+ if (fd < 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 you want to know about EPIPE below
+ * (happens if you abort downloads from local httpd): */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* If not found, default is "application/octet-stream" */
+ found_mime_type = "application/octet-stream";
+ suffix = strrchr(url, '.');
+ if (suffix) {
+ static const char suffixTable[] ALIGN1 =
+ /* Shorter suffix must be first:
+ * ".html.htm" will fail for ".htm"
+ */
+ ".txt.h.c.cc.cpp\0" "text/plain\0"
+ /* .htm line must be after .h line */
+ ".htm.html\0" "text/html\0"
+ ".jpg.jpeg\0" "image/jpeg\0"
+ ".gif\0" "image/gif\0"
+ ".png\0" "image/png\0"
+ /* .css line must be after .c line */
+ ".css\0" "text/css\0"
+ ".wav\0" "audio/wav\0"
+ ".avi\0" "video/x-msvideo\0"
+ ".qt.mov\0" "video/quicktime\0"
+ ".mpe.mpeg\0" "video/mpeg\0"
+ ".mid.midi\0" "audio/midi\0"
+ ".mp3\0" "audio/mpeg\0"
+#if 0 /* unpopular */
+ ".au\0" "audio/basic\0"
+ ".pac\0" "application/x-ns-proxy-autoconfig\0"
+ ".vrml.wrl\0" "model/vrml\0"
+#endif
+ /* compiler adds another "\0" here */
+ ;
+ Htaccess *cur;
+
+ /* Examine built-in table */
+ const char *table = suffixTable;
+ const char *table_next;
+ for (; *table; table = table_next) {
+ const char *try_suffix;
+ const char *mime_type;
+ mime_type = table + strlen(table) + 1;
+ table_next = mime_type + strlen(mime_type) + 1;
+ try_suffix = strstr(table, suffix);
+ if (!try_suffix)
+ continue;
+ try_suffix += strlen(suffix);
+ if (*try_suffix == '\0' || *try_suffix == '.') {
+ found_mime_type = mime_type;
+ break;
+ }
+ /* Example: strstr(table, ".av") != NULL, but it
+ * does not match ".avi" after all and we end up here.
+ * The table is arranged so that in this case we know
+ * that it can't match anything in the following lines,
+ * and we stop the search: */
+ break;
+ }
+ /* ...then user's table */
+ for (cur = mime_a; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon, suffix) == 0) {
+ found_mime_type = cur->after_colon;
+ break;
+ }
+ }
+ }
+
+ if (DEBUG)
+ bb_error_msg("sending file '%s' content-type: %s",
+ url, found_mime_type);
+
+#if ENABLE_FEATURE_HTTPD_RANGES
+ if (what == SEND_BODY /* err pages and ranges don't mix */
+ || content_gzip /* we are sending compressed page: can't do ranges */ ///why?
+ ) {
+ range_start = -1;
+ }
+ range_len = MAXINT(off_t);
+ if (range_start >= 0) {
+ if (!range_end || range_end > file_size - 1) {
+ range_end = file_size - 1;
+ }
+ if (range_end < range_start
+ || lseek(fd, range_start, SEEK_SET) != range_start
+ ) {
+ lseek(fd, 0, SEEK_SET);
+ range_start = -1;
+ } 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
+ {
+ off_t offset = range_start;
+ while (1) {
+ /* sz is rounded down to 64k */
+ ssize_t sz = MAXINT(ssize_t) - 0xffff;
+ IF_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
+ count = sendfile(STDOUT_FILENO, fd, &offset, sz);
+ if (count < 0) {
+ if (offset == range_start)
+ break; /* fall back to read/write loop */
+ goto fin;
+ }
+ IF_FEATURE_HTTPD_RANGES(range_len -= count;)
+ if (count == 0 || range_len == 0)
+ log_and_exit();
+ }
+ }
+#endif
+ while ((count = safe_read(fd, iobuf, IOBUF_SIZE)) > 0) {
+ ssize_t n;
+ IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
+ n = full_write(STDOUT_FILENO, iobuf, count);
+ if (count != n)
+ break;
+ IF_FEATURE_HTTPD_RANGES(range_len -= count;)
+ if (range_len == 0)
+ break;
+ }
+ if (count < 0) {
+ IF_FEATURE_HTTPD_USE_SENDFILE(fin:)
+ if (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
+
+# if ENABLE_PAM
+struct pam_userinfo {
+ const char *name;
+ const char *pw;
+};
+
+static int pam_talker(int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *appdata_ptr)
+{
+ int i;
+ struct pam_userinfo *userinfo = (struct pam_userinfo *) appdata_ptr;
+ struct pam_response *response;
+
+ if (!resp || !msg || !userinfo)
+ return PAM_CONV_ERR;
+
+ /* allocate memory to store response */
+ response = xzalloc(num_msg * sizeof(*response));
+
+ /* copy values */
+ for (i = 0; i < num_msg; i++) {
+ const char *s;
+
+ switch (msg[i]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ s = userinfo->name;
+ break;
+ case PAM_PROMPT_ECHO_OFF:
+ s = userinfo->pw;
+ break;
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ s = "";
+ break;
+ default:
+ free(response);
+ return PAM_CONV_ERR;
+ }
+ response[i].resp = xstrdup(s);
+ if (PAM_SUCCESS != 0)
+ response[i].resp_retcode = PAM_SUCCESS;
+ }
+ *resp = response;
+ return PAM_SUCCESS;
+}
+# endif
+
+/*
+ * 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, 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;
+ int r;
+
+ 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 *colon_after_user;
+ const char *passwd;
+# if ENABLE_FEATURE_SHADOWPASSWDS && !ENABLE_PAM
+ char sp_buf[256];
+# endif
+
+ colon_after_user = strchr(user_and_passwd, ':');
+ if (!colon_after_user)
+ goto bad_input;
+
+ /* compare "user:" */
+ if (cur->after_colon[0] != '*'
+ && strncmp(cur->after_colon, user_and_passwd,
+ colon_after_user - user_and_passwd + 1) != 0
+ ) {
+ continue;
+ }
+ /* this cfg entry is '*' or matches username from peer */
+
+ passwd = strchr(cur->after_colon, ':');
+ if (!passwd)
+ goto bad_input;
+ passwd++;
+ if (passwd[0] == '*') {
+# if ENABLE_PAM
+ struct pam_userinfo userinfo;
+ struct pam_conv conv_info = { &pam_talker, (void *) &userinfo };
+ pam_handle_t *pamh;
+
+ *colon_after_user = '\0';
+ userinfo.name = user_and_passwd;
+ userinfo.pw = colon_after_user + 1;
+ r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS;
+ if (r == 0) {
+ r = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS
+ || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS
+ ;
+ pam_end(pamh, PAM_SUCCESS);
+ }
+ *colon_after_user = ':';
+ goto end_check_passwd;
+# else
+# if ENABLE_FEATURE_SHADOWPASSWDS
+ /* Using _r function to avoid pulling in static buffers */
+ struct spwd spw;
+# endif
+ struct passwd *pw;
+
+ *colon_after_user = '\0';
+ pw = getpwnam(user_and_passwd);
+ *colon_after_user = ':';
+ if (!pw || !pw->pw_passwd)
+ continue;
+ passwd = pw->pw_passwd;
+# if ENABLE_FEATURE_SHADOWPASSWDS
+ if ((passwd[0] == 'x' || passwd[0] == '*') && !passwd[1]) {
+ /* getspnam_r may return 0 yet set result to NULL.
+ * At least glibc 2.4 does this. Be extra paranoid here. */
+ struct spwd *result = NULL;
+ r = getspnam_r(pw->pw_name, &spw, sp_buf, sizeof(sp_buf), &result);
+ if (r == 0 && result)
+ passwd = result->sp_pwdp;
+ }
+# endif
+ /* In this case, passwd is ALWAYS encrypted:
+ * it came from /etc/passwd or /etc/shadow!
+ */
+ goto check_encrypted;
+# endif /* ENABLE_PAM */
+ }
+ /* Else: passwd is from httpd.conf, it is either plaintext or encrypted */
+
+ if (passwd[0] == '$' && isdigit(passwd[1])) {
+ char *encrypted;
+# if !ENABLE_PAM
+ check_encrypted:
+# endif
+ /* encrypt pwd from peer and check match with local one */
+ encrypted = pw_encrypt(
+ /* pwd (from peer): */ colon_after_user + 1,
+ /* salt: */ passwd,
+ /* cleanup: */ 0
+ );
+ r = strcmp(encrypted, passwd);
+ free(encrypted);
+ } else {
+ /* local passwd is from httpd.conf and it's plaintext */
+ r = strcmp(colon_after_user + 1, passwd);
+ }
+ goto end_check_passwd;
+ }
+ bad_input:
+ /* Comparing plaintext "user:pass" in one go */
+ r = strcmp(cur->after_colon, user_and_passwd);
+ end_check_passwd:
+ if (r == 0) {
+ 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 send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN;
+static void send_REQUEST_TIMEOUT_and_exit(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. get_line() needs it. */
+ signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit);
+
+ 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';
+ IF_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];
+ IF_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; - already is */
+ tptr = strchr(urlcopy, '?');
+ if (tptr) {
+ *tptr++ = '\0';
+ g_query = tptr;
+ }
+
+ /* Decode URL escape sequences */
+ tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1);
+ 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;
+ for (;;) {
+ if (*urlp == '/') {
+ /* skip duplicate (or initial) slash */
+ if (*tptr == '/') {
+ goto next_char;
+ }
+ if (*tptr == '.') {
+ if (tptr[1] == '.' && (tptr[2] == '/' || tptr[2] == '\0')) {
+ /* "..": be careful */
+ /* protect root */
+ if (urlp == urlcopy)
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ /* omit previous dir */
+ while (*--urlp != '/')
+ continue;
+ /* skip to "./" or ".<NUL>" */
+ tptr++;
+ }
+ if (tptr[1] == '/' || tptr[1] == '\0') {
+ /* skip extra "/./" */
+ goto next_char;
+ }
+ }
+ }
+ *++urlp = *tptr;
+ if (*urlp == '\0')
+ break;
+ next_char:
+ tptr++;
+ }
+
+ /* If URL is a directory, add '/' */
+ if (urlp[-1] != '/') {
+ if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) {
+ 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, /*followlinks:*/ 1)) {
+ /* 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 */
+ while (1) {
+ 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, "Host:") == 0) {
+ host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-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 = -1;
+ } else if (s[1]) {
+ range_end = BB_STRTOOFF(s+1, NULL, 10);
+ if (errno || range_end < range_start)
+ range_start = -1;
+ }
+ }
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_GZIP
+ if (STRNCASECMP(iobuf, "Accept-Encoding:") == 0) {
+ /* Note: we do not support "gzip;q=0"
+ * method of _disabling_ gzip
+ * delivery. No one uses that, though */
+ const char *s = strstr(iobuf, "gzip");
+ if (s) {
+ // want more thorough checks?
+ //if (s[-1] == ' '
+ // || s[-1] == ','
+ // || s[-1] == ':'
+ //) {
+ content_gzip = 1;
+ //}
+ }
+ }
+#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 might require passwd.
+ * Check that with dummy user:pass */
+ if (authorized < 0)
+ authorized = check_user_passwd(urlcopy, (char *) "");
+ 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(proxy_fd, 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, urlcopy, prequest, length, cookie, content_type);
+ }
+#endif
+
+ if (urlp[-1] == '/') {
+ /* When index_page string is appended to <dir>/ URL, it overwrites
+ * the query string. If we fall back to call /cgi-bin/index.cgi,
+ * query string would be lost and not available to the CGI.
+ * Work around it by making a deep copy.
+ */
+ if (ENABLE_FEATURE_HTTPD_CGI)
+ g_query = xstrdup(g_query); /* ok for NULL too */
+ strcpy(urlp, index_page);
+ }
+ if (stat(tptr, &sb) == 0) {
+#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, urlcopy, prequest, length, cookie, content_type);
+ }
+ }
+ }
+#endif
+ 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'; /* remove index_page */
+ send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length, cookie, content_type);
+ }
+ }
+ /* else fall through to send_file, it errors out if open fails: */
+
+ if (prequest != request_GET && prequest != request_HEAD) {
+ /* POST for files does not make sense */
+ send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+ }
+ send_file_and_exit(tptr,
+ (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
+ );
+#else
+ send_file_and_exit(tptr, 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 */
+ /* Do not reload config on HUP */
+ signal(SIGHUP, SIG_IGN);
+ 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 */
+ /* Do not reload config on HUP */
+ signal(SIGHUP, SIG_IGN);
+ close(server_socket);
+ xmove_fd(n, 0);
+ xdup2(0, 1);
+
+ /* Run a copy of ourself in inetd mode */
+ re_exec(argv_copy);
+ }
+ argv_copy[0][0] &= 0x7f;
+ /* 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);
+}
+
+static void sighup_handler(int sig UNUSED_PARAM)
+{
+ parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE);
+}
+
+enum {
+ c_opt_config_file = 0,
+ d_opt_decode_url,
+ h_opt_home_httpd,
+ IF_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
+ IF_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,)
+ IF_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,)
+ IF_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 = IF_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
+ OPT_REALM = IF_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0,
+ OPT_MD5 = IF_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0,
+ OPT_SETUID = IF_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;
+ IF_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
+ IF_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
+ IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
+ IF_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:"
+ IF_FEATURE_HTTPD_ENCODE_URL_STR("e:")
+ IF_FEATURE_HTTPD_BASIC_AUTH("r:")
+ IF_FEATURE_HTTPD_AUTH_MD5("m:")
+ IF_FEATURE_HTTPD_SETUID("u:")
+ "p:ifv",
+ &opt_c_configFile, &url_for_decode, &home_httpd
+ IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
+ IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
+ IF_FEATURE_HTTPD_AUTH_MD5(, &pass)
+ IF_FEATURE_HTTPD_SETUID(, &s_ugid)
+ , &bind_addr_or_port
+ , &verbose
+ );
+ if (opt & OPT_DECODE_URL) {
+ fputs(percent_decode_in_place(url_for_decode, /*strict:*/ 0), 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) {
+ char salt[sizeof("$1$XXXXXXXX")];
+ salt[0] = '$';
+ salt[1] = '1';
+ salt[2] = '$';
+ crypt_make_salt(salt + 3, 4);
+ puts(pw_encrypt(pass, salt, /*cleanup:*/ 0));
+ 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
+ /* 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
+
+ parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE);
+ if (!(opt & OPT_INETD))
+ signal(SIGHUP, sighup_handler);
+
+ 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/ap/app/busybox/src/networking/httpd_indexcgi.c b/ap/app/busybox/src/networking/httpd_indexcgi.c
new file mode 100644
index 0000000..562cd7f
--- /dev/null
+++ b/ap/app/busybox/src/networking/httpd_indexcgi.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+/*
+ * 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, static uclibc, approximate):
+ * text data bss dec hex filename
+ * 13036 44 3052 16132 3f04 index.cgi
+ * 2576 4 2048 4628 1214 index.cgi.o
+ */
+
+#define _GNU_SOURCE 1 /* for strchrnul */
+#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') > ('z' - 'a') /* 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(int argc, char *argv[])
+{
+ 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 *location;
+
+ location = getenv("REQUEST_URI");
+ if (!location)
+ return 1;
+
+ /* drop URL arguments if any */
+ strchrnul(location, '?')[0] = '\0';
+
+ if (location[0] != '/'
+ || strstr(location, "//")
+ || strstr(location, "/../")
+ || strcmp(strrchr(location, '/'), "/..") == 0
+ ) {
+ return 1;
+ }
+
+ if (chdir("..")
+ || (location[1] && chdir(location + 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(location);
+ fmt_str(
+ "</title>\n"
+ STYLE_STR
+ "</head>" "\n"
+ "<body>" "\n"
+ "<h1>Index of ");
+ fmt_html(location);
+ 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 *ptm;
+
+ 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>");
+ ptm = gmtime(&cdir->dl_mtime);
+ fmt_04u(1900 + ptm->tm_year); *dst++ = '-';
+ fmt_02u(ptm->tm_mon + 1); *dst++ = '-';
+ fmt_02u(ptm->tm_mday); *dst++ = ' ';
+ fmt_02u(ptm->tm_hour); *dst++ = ':';
+ fmt_02u(ptm->tm_min); *dst++ = ':';
+ fmt_02u(ptm->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/ap/app/busybox/src/networking/httpd_post_upload.txt b/ap/app/busybox/src/networking/httpd_post_upload.txt
new file mode 100644
index 0000000..9d504f4
--- /dev/null
+++ b/ap/app/busybox/src/networking/httpd_post_upload.txt
@@ -0,0 +1,65 @@
+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
+
+file=/tmp/$$-$RANDOM
+
+CR=`printf '\r'`
+
+# CGI output must start with at least empty line (or headers)
+printf '\r\n'
+
+IFS="$CR"
+read -r delim_line
+IFS=""
+
+while read -r line; do
+ test x"$line" = x"" && break
+ test x"$line" = x"$CR" && break
+done
+
+cat >"$file"
+
+# We need to delete the tail of "\r\ndelim_line--\r\n"
+tail_len=$((${#delim_line} + 6))
+
+# Get and check file size
+filesize=`stat -c"%s" "$file"`
+test "$filesize" -lt "$tail_len" && exit 1
+
+# Check that tail is correct
+dd if="$file" skip=$((filesize - tail_len)) bs=1 count=1000 >"$file.tail" 2>/dev/null
+printf "\r\n%s--\r\n" "$delim_line" >"$file.tail.expected"
+if ! diff -q "$file.tail" "$file.tail.expected" >/dev/null; then
+ printf "<html>\n<body>\nMalformed file upload"
+ exit 1
+fi
+rm "$file.tail"
+rm "$file.tail.expected"
+
+# Truncate the file
+dd of="$file" seek=$((filesize - tail_len)) bs=1 count=0 >/dev/null 2>/dev/null
+
+printf "<html>\n<body>\nFile upload has been accepted"
diff --git a/ap/app/busybox/src/networking/httpd_ssi.c b/ap/app/busybox/src/networking/httpd_ssi.c
new file mode 100644
index 0000000..4bd9a6d
--- /dev/null
+++ b/ap/app/busybox/src/networking/httpd_ssi.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2009 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+/*
+ * This program is a CGI application. It processes server-side includes:
+ * <!--#include file="file.html" -->
+ *
+ * Usage: put these lines in httpd.conf:
+ *
+ * *.html:/bin/httpd_ssi
+ * *.htm:/bin/httpd_ssi
+ */
+
+/* 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_ssi.c -o httpd_ssi
+*/
+
+/* Size (i386, static uclibc, approximate):
+ * text data bss dec hex filename
+ * 9487 160 68552 78199 13177 httpd_ssi
+ *
+ * Note: it wouldn't be too hard to get rid of stdio and strdup,
+ * (especially that fgets() mangles NULs...)
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <time.h>
+
+static char* skip_whitespace(char *s)
+{
+ while (*s == ' ' || *s == '\t') ++s;
+
+ return s;
+}
+
+static char line[64 * 1024];
+
+static void process_includes(const char *filename)
+{
+ int curdir_fd;
+ char *end;
+ FILE *fp = fopen(filename, "r");
+ if (!fp)
+ exit(1);
+
+ /* Ensure that nested includes are relative:
+ * if we include a/1.htm and it includes b/2.htm,
+ * we need to include a/b/2.htm, not b/2.htm
+ */
+ curdir_fd = -1;
+ end = strrchr(filename, '/');
+ if (end) {
+ curdir_fd = open(".", O_RDONLY);
+ /* *end = '\0' would mishandle "/file.htm" */
+ end[1] = '\0';
+ chdir(filename);
+ }
+
+#define INCLUDE "<!--#include"
+ while (fgets(line, sizeof(line), fp)) {
+ unsigned preceding_len;
+ char *include_directive;
+
+ include_directive = strstr(line, INCLUDE);
+ if (!include_directive) {
+ fputs(line, stdout);
+ continue;
+ }
+ preceding_len = include_directive - line;
+ if (memchr(line, '\"', preceding_len)
+ || memchr(line, '\'', preceding_len)
+ ) {
+ /* INCLUDE string may be inside "str" or 'str',
+ * ignore it */
+ fputs(line, stdout);
+ continue;
+ }
+ /* Small bug: we accept #includefile="file" too */
+ include_directive = skip_whitespace(include_directive + sizeof(INCLUDE)-1);
+ if (strncmp(include_directive, "file=\"", 6) != 0) {
+ /* "<!--#include virtual=..."? - not supported */
+ fputs(line, stdout);
+ continue;
+ }
+ include_directive += 6; /* now it points to file name */
+ end = strchr(include_directive, '\"');
+ if (!end) {
+ fputs(line, stdout);
+ continue;
+ }
+ /* We checked that this is a valid include directive */
+
+ /* Print everything before directive */
+ if (preceding_len) {
+ line[preceding_len] = '\0';
+ fputs(line, stdout);
+ }
+ /* Save everything after directive */
+ *end++ = '\0';
+ end = strchr(end, '>');
+ if (end)
+ end = strdup(end + 1);
+
+ /* FIXME:
+ * (1) are relative paths with /../ etc ok?
+ * (2) what to do with absolute paths?
+ * are they relative to doc root or to real root?
+ */
+ process_includes(include_directive);
+
+ /* Print everything after directive */
+ if (end) {
+ fputs(end, stdout);
+ free(end);
+ }
+ }
+ if (curdir_fd >= 0)
+ fchdir(curdir_fd);
+ fclose(fp);
+}
+
+int main(int argc, char *argv[])
+{
+ if (!argv[1])
+ return 1;
+
+ /* Seen from busybox.net's Apache:
+ * HTTP/1.1 200 OK
+ * Date: Thu, 10 Sep 2009 18:23:28 GMT
+ * Server: Apache
+ * Accept-Ranges: bytes
+ * Connection: close
+ * Content-Type: text/html
+ */
+ fputs(
+ /* "Date: Thu, 10 Sep 2009 18:23:28 GMT\r\n" */
+ /* "Server: Apache\r\n" */
+ /* "Accept-Ranges: bytes\r\n" - do we really accept bytes?! */
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n",
+ stdout
+ );
+ process_includes(argv[1]);
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/ifconfig.c b/ap/app/busybox/src/networking/ifconfig.c
new file mode 100644
index 0000000..782374b
--- /dev/null
+++ b/ap/app/busybox/src/networking/ifconfig.c
@@ -0,0 +1,547 @@
+/* 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 source tree.
+ */
+
+/*
+ * 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>
+ */
+
+//usage:#define ifconfig_trivial_usage
+//usage: IF_FEATURE_IFCONFIG_STATUS("[-a]") " interface [address]"
+//usage:#define ifconfig_full_usage "\n\n"
+//usage: "Configure a network interface\n"
+//usage: "\n"
+//usage: IF_FEATURE_IPV6(
+//usage: " [add ADDRESS[/PREFIXLEN]]\n")
+//usage: IF_FEATURE_IPV6(
+//usage: " [del ADDRESS[/PREFIXLEN]]\n")
+//usage: " [[-]broadcast [ADDRESS]] [[-]pointopoint [ADDRESS]]\n"
+//usage: " [netmask ADDRESS] [dstaddr ADDRESS]\n"
+//usage: IF_FEATURE_IFCONFIG_SLIP(
+//usage: " [outfill NN] [keepalive NN]\n")
+//usage: " " IF_FEATURE_IFCONFIG_HW("[hw ether" IF_FEATURE_HWIB("|infiniband")" ADDRESS] ") "[metric NN] [mtu NN]\n"
+//usage: " [[-]trailers] [[-]arp] [[-]allmulti]\n"
+//usage: " [multicast] [[-]promisc] [txqueuelen NN] [[-]dynamic]\n"
+//usage: IF_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ(
+//usage: " [mem_start NN] [io_addr NN] [irq NN]\n")
+//usage: " [up|down] ..."
+
+#include "libbb.h"
+#include "inet_common.h"
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#ifdef HAVE_NET_ETHERNET_H
+# include <net/ethernet.h>
+#endif
+
+#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)
+
+
+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)
+
+/*
+ * Set up the tables. Warning! They must have corresponding order!
+ */
+
+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
+#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
+ /* Last entry is for unmatched (assumed to be hostname/address) arg. */
+ { "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) }
+};
+
+#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 = (unsigned char)((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
+
+int ifconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifconfig_main(int argc UNUSED_PARAM, 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;
+
+#if ENABLE_FEATURE_IFCONFIG_STATUS
+ if (argv[0] && (argv[0][0] == '-' && argv[0][1] == 'a' && !argv[0][2])) {
+ interface_opt_a = 1;
+ ++argv;
+ }
+#endif
+
+ if (!argv[0] || !argv[1]) { /* one or no args */
+#if ENABLE_FEATURE_IFCONFIG_STATUS
+ return display_interfaces(argv[0] /* can be 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_IFNAMSIZ(ifr.ifr_name, *argv);
+
+ /* Process the remaining arguments. */
+ while (*++argv != 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;
+ if (mask & A_NETMASK & did_flags)
+ bb_show_usage();
+ a1op = Arg1Opt + (op - OptArray);
+ 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
+ host = *argv;
+ if (strcmp(host, "inet") == 0)
+ continue; /* compat stuff */
+ sai.sin_family = AF_INET;
+ sai.sin_port = 0;
+ if (strcmp(host, "default") == 0) {
+ /* 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 ENABLE_FEATURE_IPV6
+ char *prefix;
+ int prefix_len = 0;
+ prefix = strchr(host, '/');
+ if (prefix) {
+ prefix_len = xatou_range(prefix + 1, 0, 128);
+ *prefix = '\0';
+ }
+ resolve:
+#endif
+ lsa = xhost2sockaddr(host, 0);
+#if ENABLE_FEATURE_IPV6
+ if (lsa->u.sa.sa_family != AF_INET6 && prefix) {
+/* TODO: we do not support "ifconfig eth0 up 1.2.3.4/17".
+ * For now, just make it fail instead of silently ignoring "/17" part:
+ */
+ *prefix = '/';
+ goto resolve;
+ }
+ if (lsa->u.sa.sa_family == AF_INET6) {
+ int sockfd6;
+ struct in6_ifreq ifr6;
+
+ sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0);
+ xioctl(sockfd6, SIOCGIFINDEX, &ifr);
+ ifr6.ifr6_ifindex = ifr.ifr_ifindex;
+ ifr6.ifr6_prefixlen = prefix_len;
+ memcpy(&ifr6.ifr6_addr,
+ &lsa->u.sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ 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"
+ IF_FEATURE_HWIB("infiniband\0"), *argv) + 1;
+ if (!hw_class || !*++argv)
+ bb_show_usage();
+ 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;
+ } /* if (mask & ARG_MASK) */
+
+ 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;
+}
diff --git a/ap/app/busybox/src/networking/ifenslave.c b/ap/app/busybox/src/networking/ifenslave.c
new file mode 100644
index 0000000..c3be818
--- /dev/null
+++ b/ap/app/busybox/src/networking/ifenslave.c
@@ -0,0 +1,617 @@
+/* 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
+ */
+
+//usage:#define ifenslave_trivial_usage
+//usage: "[-cdf] MASTER_IFACE SLAVE_IFACE..."
+//usage:#define ifenslave_full_usage "\n\n"
+//usage: "Configure network interfaces for parallel routing\n"
+//usage: "\n -c,--change-active Change active slave"
+//usage: "\n -d,--detach Remove slave interface from bonding device"
+//usage: "\n -f,--force Force, even if interface is not Ethernet"
+/* //usage: "\n -r,--receive-slave Create a receive-only slave" */
+//usage:
+//usage:#define ifenslave_example_usage
+//usage: "To create a bond device, simply follow these three steps:\n"
+//usage: "- ensure that the required drivers are properly loaded:\n"
+//usage: " # modprobe bonding ; modprobe <3c59x|eepro100|pcnet32|tulip|...>\n"
+//usage: "- assign an IP address to the bond device:\n"
+//usage: " # ifconfig bond0 <addr> netmask <mask> broadcast <bcast>\n"
+//usage: "- attach all the interfaces you need to the bond device:\n"
+//usage: " # ifenslave bond0 eth0 eth1 eth2\n"
+//usage: " If bond0 didn't have a MAC address, it will take eth0's. Then, all\n"
+//usage: " interfaces attached AFTER this assignment will get the same MAC addr.\n\n"
+//usage: " To detach a dead interface without setting the bond device down:\n"
+//usage: " # ifenslave -d bond0 eth1\n\n"
+//usage: " To set the bond device down and automatically release all the slaves:\n"
+//usage: " # ifconfig bond0 down\n\n"
+//usage: " To change active slave:\n"
+//usage: " # ifenslave -c bond0 eth0\n"
+
+#include "libbb.h"
+
+/* #include <net/if.h> - no. linux/if_bonding.h pulls in linux/if.h */
+#include <linux/if.h>
+//#include <net/if_arp.h> - not needed?
+#include <linux/if_bonding.h>
+#include <linux/sockios.h>
+#include "fix_u32.h" /* hack, so we may include kernel's ethtool.h */
+#include <linux/ethtool.h>
+#ifndef BOND_ABI_VERSION
+# define BOND_ABI_VERSION 2
+#endif
+#ifndef IFNAMSIZ
+# define IFNAMSIZ 16
+#endif
+
+
+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 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_LONG_OPTS
+ 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/ap/app/busybox/src/networking/ifplugd.c b/ap/app/busybox/src/networking/ifplugd.c
new file mode 100644
index 0000000..86586f0
--- /dev/null
+++ b/ap/app/busybox/src/networking/ifplugd.c
@@ -0,0 +1,739 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ifplugd for busybox, based on ifplugd 0.28 (written by Lennart Poettering).
+ *
+ * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define ifplugd_trivial_usage
+//usage: "[OPTIONS]"
+//usage:#define ifplugd_full_usage "\n\n"
+//usage: "Network interface plug detection daemon\n"
+//usage: "\n -n Don't daemonize"
+//usage: "\n -s Don't log to syslog"
+//usage: "\n -i IFACE Interface"
+//usage: "\n -f/-F Treat link detection error as link down/link up"
+//usage: "\n (otherwise exit on error)"
+//usage: "\n -a Don't up interface at each link probe"
+//usage: "\n -M Monitor creation/destruction of interface"
+//usage: "\n (otherwise it must exist)"
+//usage: "\n -r PROG Script to run"
+//usage: "\n -x ARG Extra argument for script"
+//usage: "\n -I Don't exit on nonzero exit code from script"
+//usage: "\n -p Don't run \"up\" script on startup"
+//usage: "\n -q Don't run \"down\" script on exit"
+//usage: "\n -l Always run script on startup"
+//usage: "\n -t SECS Poll time in seconds"
+//usage: "\n -u SECS Delay before running script after link up"
+//usage: "\n -d SECS Delay after link down"
+//usage: "\n -m MODE API mode (mii, priv, ethtool, wlan, iff, auto)"
+//usage: "\n -k Kill running daemon"
+
+#include "libbb.h"
+
+#include "fix_u32.h"
+#include <linux/if.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#ifdef HAVE_NET_ETHERNET_H
+# include <net/ethernet.h>
+#endif
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <syslog.h>
+
+#define __user
+#include <linux/wireless.h>
+
+/*
+From initial port to busybox, removed most of the redundancy by
+converting implementation of a polymorphic interface to the strict
+functional style. The main role is run a script when link state
+changed, other activities like audio signal or detailed reports
+are on the script itself.
+
+One questionable point of the design is netlink usage:
+
+We have 1 second timeout by default to poll the link status,
+it is short enough so that there are no real benefits in
+using netlink to get "instantaneous" interface creation/deletion
+notifications. We can check for interface existence by just
+doing some fast ioctl using its name.
+
+Netlink code then can be just dropped (1k or more?)
+*/
+
+
+#define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
+#define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
+
+enum {
+ FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
+ FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
+ FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
+ FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
+ FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
+ FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
+ FLAG_RUN = 1 << 6, // -r, Specify program to execute
+ FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
+ FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
+ FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
+ FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
+ FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
+ FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
+ FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
+ FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
+ FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
+ FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
+#if ENABLE_FEATURE_PIDFILE
+ FLAG_KILL = 1 << 17, // -k, Kill a running daemon
+#endif
+};
+#if ENABLE_FEATURE_PIDFILE
+# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
+#else
+# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
+#endif
+
+enum { // interface status
+ IFSTATUS_ERR = -1,
+ IFSTATUS_DOWN = 0,
+ IFSTATUS_UP = 1,
+};
+
+enum { // constant fds
+ ioctl_fd = 3,
+ netlink_fd = 4,
+};
+
+struct globals {
+ smallint iface_last_status;
+ smallint iface_prev_status;
+ smallint iface_exists;
+ smallint api_method_num;
+
+ /* Used in getopt32, must have sizeof == sizeof(int) */
+ unsigned poll_time;
+ unsigned delay_up;
+ unsigned delay_down;
+
+ const char *iface;
+ const char *api_mode;
+ const char *script_name;
+ const char *extra_arg;
+};
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ G.iface_last_status = -1; \
+ G.iface_exists = 1; \
+ G.poll_time = 1; \
+ G.delay_down = 5; \
+ G.iface = "eth0"; \
+ G.api_mode = "a"; \
+ G.script_name = "/etc/ifplugd/ifplugd.action"; \
+} while (0)
+
+
+/* Utility routines */
+
+static void set_ifreq_to_ifname(struct ifreq *ifreq)
+{
+ memset(ifreq, 0, sizeof(struct ifreq));
+ strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
+}
+
+static int network_ioctl(int request, void* data, const char *errmsg)
+{
+ int r = ioctl(ioctl_fd, request, data);
+ if (r < 0 && errmsg)
+ bb_perror_msg("%s failed", errmsg);
+ return r;
+}
+
+/* Link detection routines and table */
+
+static smallint detect_link_mii(void)
+{
+ /* char buffer instead of bona-fide struct avoids aliasing warning */
+ char buf[sizeof(struct ifreq)];
+ struct ifreq *const ifreq = (void *)buf;
+
+ struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
+
+ set_ifreq_to_ifname(ifreq);
+
+ if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ mii->reg_num = 1;
+
+ if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
+}
+
+static smallint detect_link_priv(void)
+{
+ /* char buffer instead of bona-fide struct avoids aliasing warning */
+ char buf[sizeof(struct ifreq)];
+ struct ifreq *const ifreq = (void *)buf;
+
+ struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
+
+ set_ifreq_to_ifname(ifreq);
+
+ if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ mii->reg_num = 1;
+
+ if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
+}
+
+static smallint detect_link_ethtool(void)
+{
+ struct ifreq ifreq;
+ struct ethtool_value edata;
+
+ set_ifreq_to_ifname(&ifreq);
+
+ edata.cmd = ETHTOOL_GLINK;
+ ifreq.ifr_data = (void*) &edata;
+
+ if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
+}
+
+static smallint detect_link_iff(void)
+{
+ struct ifreq ifreq;
+
+ set_ifreq_to_ifname(&ifreq);
+
+ if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
+ * regardless of link status. Simply continue to report last status -
+ * no point in reporting spurious link downs if interface is disabled
+ * by admin. When/if it will be brought up,
+ * we'll report real link status.
+ */
+ if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
+ return G.iface_last_status;
+
+ return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
+}
+
+static smallint detect_link_wlan(void)
+{
+ int i;
+ struct iwreq iwrequest;
+ uint8_t mac[ETH_ALEN];
+
+ memset(&iwrequest, 0, sizeof(iwrequest));
+ strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
+
+ if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
+ return IFSTATUS_ERR;
+ }
+
+ memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
+
+ if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
+ for (i = 1; i < ETH_ALEN; ++i) {
+ if (mac[i] != mac[0])
+ return IFSTATUS_UP;
+ }
+ return IFSTATUS_DOWN;
+ }
+
+ return IFSTATUS_UP;
+}
+
+enum { // api mode
+ API_ETHTOOL, // 'e'
+ API_MII, // 'm'
+ API_PRIVATE, // 'p'
+ API_WLAN, // 'w'
+ API_IFF, // 'i'
+ API_AUTO, // 'a'
+};
+
+static const char api_modes[] ALIGN1 = "empwia";
+
+static const struct {
+ const char *name;
+ smallint (*func)(void);
+} method_table[] = {
+ { "SIOCETHTOOL" , &detect_link_ethtool },
+ { "SIOCGMIIPHY" , &detect_link_mii },
+ { "SIOCDEVPRIVATE" , &detect_link_priv },
+ { "wireless extension", &detect_link_wlan },
+ { "IFF_RUNNING" , &detect_link_iff },
+};
+
+
+
+static const char *strstatus(int status)
+{
+ if (status == IFSTATUS_ERR)
+ return "error";
+ return "down\0up" + (status * 5);
+}
+
+static int run_script(const char *action)
+{
+ char *env_PREVIOUS, *env_CURRENT;
+ char *argv[5];
+ int r;
+
+ bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
+
+ argv[0] = (char*) G.script_name;
+ argv[1] = (char*) G.iface;
+ argv[2] = (char*) action;
+ argv[3] = (char*) G.extra_arg;
+ argv[4] = NULL;
+
+ env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
+ putenv(env_PREVIOUS);
+ env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
+ putenv(env_CURRENT);
+
+ /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
+ r = spawn_and_wait(argv);
+
+ unsetenv(IFPLUGD_ENV_PREVIOUS);
+ unsetenv(IFPLUGD_ENV_CURRENT);
+ free(env_PREVIOUS);
+ free(env_CURRENT);
+
+ bb_error_msg("exit code: %d", r & 0xff);
+ return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
+}
+
+static void up_iface(void)
+{
+ struct ifreq ifrequest;
+
+ if (!G.iface_exists)
+ return;
+
+ set_ifreq_to_ifname(&ifrequest);
+ if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
+ G.iface_exists = 0;
+ return;
+ }
+
+ if (!(ifrequest.ifr_flags & IFF_UP)) {
+ ifrequest.ifr_flags |= IFF_UP;
+ /* Let user know we mess up with interface */
+ bb_error_msg("upping interface");
+ if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
+ xfunc_die();
+ }
+
+#if 0 /* why do we mess with IP addr? It's not our business */
+ if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
+ } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
+ bb_perror_msg("the interface is not IP-based");
+ } else {
+ ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
+ network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
+ }
+ network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
+#endif
+}
+
+static void maybe_up_new_iface(void)
+{
+ if (!(option_mask32 & FLAG_NO_AUTO))
+ up_iface();
+
+#if 0 /* bloat */
+ struct ifreq ifrequest;
+ struct ethtool_drvinfo driver_info;
+
+ set_ifreq_to_ifname(&ifrequest);
+ driver_info.cmd = ETHTOOL_GDRVINFO;
+ ifrequest.ifr_data = &driver_info;
+ if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
+ char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
+
+ /* Get MAC */
+ buf[0] = '\0';
+ set_ifreq_to_ifname(&ifrequest);
+ if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
+ sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
+ (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
+ }
+
+ bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
+ G.iface, buf, driver_info.driver, driver_info.version);
+ }
+#endif
+ if (G.api_mode[0] == 'a')
+ G.api_method_num = API_AUTO;
+}
+
+static smallint detect_link(void)
+{
+ smallint status;
+
+ if (!G.iface_exists)
+ return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
+
+ /* Some drivers can't detect link status when the interface is down.
+ * I imagine detect_link_iff() is the most vulnerable.
+ * That's why -a "noauto" in an option, not a hardwired behavior.
+ */
+ if (!(option_mask32 & FLAG_NO_AUTO))
+ up_iface();
+
+ if (G.api_method_num == API_AUTO) {
+ int i;
+ smallint sv_logmode;
+
+ sv_logmode = logmode;
+ for (i = 0; i < ARRAY_SIZE(method_table); i++) {
+ logmode = LOGMODE_NONE;
+ status = method_table[i].func();
+ logmode = sv_logmode;
+ if (status != IFSTATUS_ERR) {
+ G.api_method_num = i;
+ bb_error_msg("using %s detection mode", method_table[i].name);
+ break;
+ }
+ }
+ } else {
+ status = method_table[G.api_method_num].func();
+ }
+
+ if (status == IFSTATUS_ERR) {
+ if (option_mask32 & FLAG_IGNORE_FAIL)
+ status = IFSTATUS_DOWN;
+ else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
+ status = IFSTATUS_UP;
+ else if (G.api_mode[0] == 'a')
+ bb_error_msg("can't detect link status");
+ }
+
+ if (status != G.iface_last_status) {
+ G.iface_prev_status = G.iface_last_status;
+ G.iface_last_status = status;
+ }
+
+ return status;
+}
+
+static NOINLINE int check_existence_through_netlink(void)
+{
+ int iface_len;
+ char replybuf[1024];
+
+ iface_len = strlen(G.iface);
+ while (1) {
+ struct nlmsghdr *mhdr;
+ ssize_t bytes;
+
+ bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
+ if (bytes < 0) {
+ if (errno == EAGAIN)
+ return G.iface_exists;
+ if (errno == EINTR)
+ continue;
+
+ bb_perror_msg("netlink: recv");
+ return -1;
+ }
+
+ mhdr = (struct nlmsghdr*)replybuf;
+ while (bytes > 0) {
+ if (!NLMSG_OK(mhdr, bytes)) {
+ bb_error_msg("netlink packet too small or truncated");
+ return -1;
+ }
+
+ if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
+ struct rtattr *attr;
+ int attr_len;
+
+ if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
+ bb_error_msg("netlink packet too small or truncated");
+ return -1;
+ }
+
+ attr = IFLA_RTA(NLMSG_DATA(mhdr));
+ attr_len = IFLA_PAYLOAD(mhdr);
+
+ while (RTA_OK(attr, attr_len)) {
+ if (attr->rta_type == IFLA_IFNAME) {
+ int len = RTA_PAYLOAD(attr);
+ if (len > IFNAMSIZ)
+ len = IFNAMSIZ;
+ if (iface_len <= len
+ && strncmp(G.iface, RTA_DATA(attr), len) == 0
+ ) {
+ G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
+ }
+ }
+ attr = RTA_NEXT(attr, attr_len);
+ }
+ }
+
+ mhdr = NLMSG_NEXT(mhdr, bytes);
+ }
+ }
+
+ return G.iface_exists;
+}
+
+#if ENABLE_FEATURE_PIDFILE
+static NOINLINE pid_t read_pid(const char *filename)
+{
+ int len;
+ char buf[128];
+
+ len = open_read_close(filename, buf, 127);
+ if (len > 0) {
+ buf[len] = '\0';
+ /* returns ULONG_MAX on error => -1 */
+ return bb_strtoul(buf, NULL, 10);
+ }
+ return 0;
+}
+#endif
+
+int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifplugd_main(int argc UNUSED_PARAM, char **argv)
+{
+ int iface_status;
+ int delay_time;
+ const char *iface_status_str;
+ struct pollfd netlink_pollfd[1];
+ unsigned opts;
+ const char *api_mode_found;
+#if ENABLE_FEATURE_PIDFILE
+ char *pidfile_name;
+ pid_t pid_from_pidfile;
+#endif
+
+ INIT_G();
+
+ opt_complementary = "t+:u+:d+";
+ opts = getopt32(argv, OPTION_STR,
+ &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
+ &G.delay_down, &G.api_mode, &G.extra_arg);
+ G.poll_time *= 1000;
+
+ applet_name = xasprintf("ifplugd(%s)", G.iface);
+
+#if ENABLE_FEATURE_PIDFILE
+ pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
+ pid_from_pidfile = read_pid(pidfile_name);
+
+ if (opts & FLAG_KILL) {
+ if (pid_from_pidfile > 0)
+ kill(pid_from_pidfile, SIGQUIT);
+ return EXIT_SUCCESS;
+ }
+
+ if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
+ bb_error_msg_and_die("daemon already running");
+#endif
+
+ api_mode_found = strchr(api_modes, G.api_mode[0]);
+ if (!api_mode_found)
+ bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
+ G.api_method_num = api_mode_found - api_modes;
+
+ if (!(opts & FLAG_NO_DAEMON))
+ bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+
+ xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
+ if (opts & FLAG_MONITOR) {
+ struct sockaddr_nl addr;
+ int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_LINK;
+ addr.nl_pid = getpid();
+
+ xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
+ xmove_fd(fd, netlink_fd);
+ }
+
+ write_pidfile(pidfile_name);
+
+ /* this can't be moved before socket creation */
+ if (!(opts & FLAG_NO_SYSLOG)) {
+ openlog(applet_name, 0, LOG_DAEMON);
+ logmode |= LOGMODE_SYSLOG;
+ }
+
+ bb_signals(0
+ | (1 << SIGINT )
+ | (1 << SIGTERM)
+ | (1 << SIGQUIT)
+ | (1 << SIGHUP ) /* why we ignore it? */
+ /* | (1 << SIGCHLD) - run_script does not use it anymore */
+ , record_signo);
+
+ bb_error_msg("started: %s", bb_banner);
+
+ if (opts & FLAG_MONITOR) {
+ struct ifreq ifrequest;
+ set_ifreq_to_ifname(&ifrequest);
+ G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
+ }
+
+ if (G.iface_exists)
+ maybe_up_new_iface();
+
+ iface_status = detect_link();
+ if (iface_status == IFSTATUS_ERR)
+ goto exiting;
+ iface_status_str = strstatus(iface_status);
+
+ if (opts & FLAG_MONITOR) {
+ bb_error_msg("interface %s",
+ G.iface_exists ? "exists"
+ : "doesn't exist, waiting");
+ }
+ /* else we assume it always exists, but don't mislead user
+ * by potentially lying that it really exists */
+
+ if (G.iface_exists) {
+ bb_error_msg("link is %s", iface_status_str);
+ }
+
+ if ((!(opts & FLAG_NO_STARTUP)
+ && iface_status == IFSTATUS_UP
+ )
+ || (opts & FLAG_INITIAL_DOWN)
+ ) {
+ if (run_script(iface_status_str) != 0)
+ goto exiting;
+ }
+
+ /* Main loop */
+ netlink_pollfd[0].fd = netlink_fd;
+ netlink_pollfd[0].events = POLLIN;
+ delay_time = 0;
+ while (1) {
+ int iface_status_old;
+ int iface_exists_old;
+
+ switch (bb_got_signal) {
+ case SIGINT:
+ case SIGTERM:
+ bb_got_signal = 0;
+ goto cleanup;
+ case SIGQUIT:
+ bb_got_signal = 0;
+ goto exiting;
+ default:
+ bb_got_signal = 0;
+ break;
+ }
+
+ if (poll(netlink_pollfd,
+ (opts & FLAG_MONITOR) ? 1 : 0,
+ G.poll_time
+ ) < 0
+ ) {
+ if (errno == EINTR)
+ continue;
+ bb_perror_msg("poll");
+ goto exiting;
+ }
+
+ iface_status_old = iface_status;
+ iface_exists_old = G.iface_exists;
+
+ if ((opts & FLAG_MONITOR)
+ && (netlink_pollfd[0].revents & POLLIN)
+ ) {
+ G.iface_exists = check_existence_through_netlink();
+ if (G.iface_exists < 0) /* error */
+ goto exiting;
+ if (iface_exists_old != G.iface_exists) {
+ bb_error_msg("interface %sappeared",
+ G.iface_exists ? "" : "dis");
+ if (G.iface_exists)
+ maybe_up_new_iface();
+ }
+ }
+
+ /* note: if !G.iface_exists, returns DOWN */
+ iface_status = detect_link();
+ if (iface_status == IFSTATUS_ERR) {
+ if (!(opts & FLAG_MONITOR))
+ goto exiting;
+ iface_status = IFSTATUS_DOWN;
+ }
+ iface_status_str = strstatus(iface_status);
+
+ if (iface_status_old != iface_status) {
+ bb_error_msg("link is %s", iface_status_str);
+
+ if (delay_time) {
+ /* link restored its old status before
+ * we run script. don't run the script: */
+ delay_time = 0;
+ } else {
+ delay_time = monotonic_sec();
+ if (iface_status == IFSTATUS_UP)
+ delay_time += G.delay_up;
+ if (iface_status == IFSTATUS_DOWN)
+ delay_time += G.delay_down;
+ if (delay_time == 0)
+ delay_time++;
+ }
+ }
+
+ if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
+ delay_time = 0;
+ if (run_script(iface_status_str) != 0)
+ goto exiting;
+ }
+ } /* while (1) */
+
+ cleanup:
+ if (!(opts & FLAG_NO_SHUTDOWN)
+ && (iface_status == IFSTATUS_UP
+ || (iface_status == IFSTATUS_DOWN && delay_time)
+ )
+ ) {
+ setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
+ setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
+ run_script("down\0up"); /* reusing string */
+ }
+
+ exiting:
+ remove_pidfile(pidfile_name);
+ bb_error_msg_and_die("exiting");
+}
diff --git a/ap/app/busybox/src/networking/ifupdown.c b/ap/app/busybox/src/networking/ifupdown.c
new file mode 100644
index 0000000..8180482
--- /dev/null
+++ b/ap/app/busybox/src/networking/ifupdown.c
@@ -0,0 +1,1344 @@
+/* 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 GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define ifup_trivial_usage
+//usage: "[-an"IF_FEATURE_IFUPDOWN_MAPPING("m")"vf] [-i FILE] IFACE..."
+//usage:#define ifup_full_usage "\n\n"
+//usage: " -a De/configure all interfaces automatically"
+//usage: "\n -i FILE Use FILE for interface definitions"
+//usage: "\n -n Print out what would happen, but don't do it"
+//usage: IF_FEATURE_IFUPDOWN_MAPPING(
+//usage: "\n (note: doesn't disable mappings)"
+//usage: "\n -m Don't run any mappings"
+//usage: )
+//usage: "\n -v Print out what would happen before doing it"
+//usage: "\n -f Force de/configuration"
+//usage:
+//usage:#define ifdown_trivial_usage
+//usage: "[-an"IF_FEATURE_IFUPDOWN_MAPPING("m")"vf] [-i FILE] IFACE..."
+//usage:#define ifdown_full_usage "\n\n"
+//usage: " -a De/configure all interfaces automatically"
+//usage: "\n -i FILE Use FILE for interface definitions"
+//usage: "\n -n Print out what would happen, but don't do it"
+//usage: IF_FEATURE_IFUPDOWN_MAPPING(
+//usage: "\n (note: doesn't disable mappings)"
+//usage: "\n -m Don't run any mappings"
+//usage: )
+//usage: "\n -v Print out what would happen before doing it"
+//usage: "\n -f Force de/configuration"
+
+#include "libbb.h"
+/* After libbb.h, since it needs sys/types.h on some systems */
+#include <sys/utsname.h>
+#include <fnmatch.h>
+
+#define MAX_OPT_DEPTH 10
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+#define MAX_INTERFACE_LENGTH 10
+#endif
+
+#define UDHCPC_CMD_OPTIONS CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS
+
+#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) FAST_FUNC;
+ int (*down)(struct interface_defn_t *ifd, execfn *e) FAST_FUNC;
+};
+
+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 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 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" IF_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)
+
+
+struct globals {
+ char **my_environ;
+ const char *startup_PATH;
+ char *shell;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { } while (0)
+
+
+static const char keywords_up_down[] ALIGN1 =
+ "up\0"
+ "down\0"
+ "pre-up\0"
+ "post-down\0"
+;
+
+
+#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 - (unsigned char)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) {
+ // ubuntu's ifup doesn't do this:
+ //static char *label_buf;
+ //char *result;
+ //free(label_buf);
+ //label_buf = xstrdup(ifd->iface);
+ // Remove virtual iface suffix
+ //result = strchrnul(label_buf, ':');
+ //*result = '\0';
+ //return label_buf;
+
+ return ifd->iface;
+ }
+ 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 };
+ smallint 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])
+ command++;
+ 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, command, 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, command, 1);
+ command++;
+ }
+ break;
+ case '%':
+ {
+ char *nextpercent;
+ char *varvalue;
+
+ command++;
+ nextpercent = strchr(command, '%');
+ if (!nextpercent) {
+ /* Unterminated %var% */
+ 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) {
+ /* Unbalanced bracket */
+ free(result);
+ return NULL;
+ }
+
+ if (!okay[0]) {
+ /* Undefined variable and we aren't in a bracket */
+ 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 /* FEATURE_IFUPDOWN_IPV4 || FEATURE_IFUPDOWN_IPV6 */
+
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV6
+
+static int FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC manual_up_down6(struct interface_defn_t *ifd UNUSED_PARAM, execfn *exec UNUSED_PARAM)
+{
+ return 1;
+}
+
+static int FAST_FUNC 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%]][[ prio %metric%]]", 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%[[ metric %metric%]]]]", ifd, exec);
+# endif
+ return ((result == 3) ? 3 : 0);
+}
+
+static int FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC 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 , },
+ { "manual" , manual_up_down6 , manual_up_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 FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC 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%[[ prio %metric%]]]]", 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%[[ metric %metric%]] %iface%]]", ifd, exec);
+ return ((result == 3) ? 3 : 0);
+# endif
+}
+
+static int FAST_FUNC 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 %client%]][[ -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 " UDHCPC_CMD_OPTIONS " -p /var/run/udhcpc.%iface%.pid -i %iface%[[ -H %hostname%]][[ -c %client%]]"
+ "[[ -s %script%]][[ %udhcpc_opts%]]",
+ "kill `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null",
+ },
+};
+# endif /* FEATURE_IFUPDOWN_EXTERNAL_DHCPC */
+
+# if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int FAST_FUNC 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_UDHCPC
+static int FAST_FUNC 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 " UDHCPC_CMD_OPTIONS " -p /var/run/udhcpc.%iface%.pid "
+ "-i %iface%[[ -H %hostname%]][[ -c %client%]][[ -s %script%]][[ %udhcpc_opts%]]",
+ ifd, exec);
+}
+# else
+static int FAST_FUNC 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 FAST_FUNC 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_UDHCPC
+static int FAST_FUNC dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+ result = execute(
+ "test -f /var/run/udhcpc.%iface%.pid && "
+ "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 FAST_FUNC dhcp_down(struct interface_defn_t *ifd UNUSED_PARAM,
+ execfn *exec UNUSED_PARAM)
+{
+ return 0; /* no dhcp support */
+}
+# endif
+
+static int FAST_FUNC manual_up_down(struct interface_defn_t *ifd UNUSED_PARAM, execfn *exec UNUSED_PARAM)
+{
+ return 1;
+}
+
+static int FAST_FUNC 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 FAST_FUNC ppp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("pon[[ %provider%]]", ifd, exec);
+}
+
+static int FAST_FUNC ppp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("poff[[ %provider%]]", ifd, exec);
+}
+
+static int FAST_FUNC 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 FAST_FUNC 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 /* FEATURE_IFUPDOWN_IPV4 */
+
+
+/* Returns pointer to the next word, or NULL.
+ * In 1st case, advances *buf to the word after this one.
+ */
+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 = skip_whitespace(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 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->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 (llist_find_str(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, "post-up") == 0)
+ first_word += 5; /* "up" */
+ else if (strcmp(first_word, "pre-down") == 0)
+ first_word += 4; /* "down" */
+
+ /* If not one of "up", "down",... words... */
+ if (index_in_strings(keywords_up_down, first_word) < 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);
+ }
+ }
+ debug_noise("\t%s=%s\n", first_word, rest_of_line);
+ currif->option = xrealloc_vector(currif->option, 4, currif->n_options);
+ 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) {
+ currmap->mapping = xrealloc_vector(currmap->mapping, 2, currmap->n_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 *dst;
+ char *src;
+ char c;
+
+ result = xasprintf(format, name, value);
+
+ for (dst = src = result; (c = *src) != '=' && c; src++) {
+ if (c == '-')
+ c = '_';
+ if (c >= 'a' && c <= 'z')
+ c -= ('a' - 'A');
+ if (isalnum(c) || c == '_')
+ *dst++ = c;
+ }
+ overlapping_strcpy(dst, src);
+
+ return result;
+}
+
+static void set_environ(struct interface_defn_t *iface, const char *mode, const char *opt)
+{
+ int i;
+ char **pp;
+
+ if (G.my_environ != NULL) {
+ for (pp = G.my_environ; *pp; pp++) {
+ free(*pp);
+ }
+ free(G.my_environ);
+ }
+
+ /* note: last element will stay NULL: */
+ G.my_environ = xzalloc(sizeof(char *) * (iface->n_options + 7));
+ pp = G.my_environ;
+
+ for (i = 0; i < iface->n_options; i++) {
+ if (index_in_strings(keywords_up_down, iface->option[i].name) >= 0) {
+ continue;
+ }
+ *pp++ = setlocalenv("IF_%s=%s", iface->option[i].name, iface->option[i].value);
+ }
+
+ *pp++ = setlocalenv("%s=%s", "IFACE", iface->iface);
+ *pp++ = setlocalenv("%s=%s", "ADDRFAM", iface->address_family->name);
+ *pp++ = setlocalenv("%s=%s", "METHOD", iface->method->name);
+ *pp++ = setlocalenv("%s=%s", "MODE", mode);
+ *pp++ = setlocalenv("%s=%s", "PHASE", opt);
+ if (G.startup_PATH)
+ *pp++ = setlocalenv("%s=%s", "PATH", G.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_all();
+ child = vfork();
+ if (child < 0) /* failure */
+ return 0;
+ if (child == 0) { /* child */
+ execle(G.shell, G.shell, "-c", str, (char *) NULL, G.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", "pre-up");
+ if (!execute_all(iface, "pre-up")) return 0;
+ if (!iface->method->up(iface, doit)) return 0;
+ set_environ(iface, "start", "post-up");
+ 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", "pre-down");
+ if (!execute_all(iface, "down")) return 0;
+ if (!iface->method->down(iface, doit)) return 0;
+ set_environ(iface, "stop", "post-down");
+ 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_all();
+ pid = xvfork();
+
+ if (pid == 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_or_die(argv);
+ }
+ /* parent */
+ close(infd.rd);
+ close(outfd.wr);
+ *in = xfdopen_for_write(infd.wr);
+ *out = xfdopen_for_read(outfd.rd);
+ 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 UNUSED_PARAM, 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;
+
+ INIT_G();
+
+ G.startup_PATH = getenv("PATH");
+ G.shell = xstrdup(get_shell_name());
+
+ cmds = iface_down;
+ if (applet_name[2] == 'u') {
+ /* ifup command */
+ cmds = iface_up;
+ }
+
+ getopt32(argv, OPTION_STR, &interfaces);
+ argv += optind;
+ if (argv[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);
+
+ /* Create a list of interfaces to work on */
+ if (DO_ALL) {
+ target_list = defn->autointerfaces;
+ } else {
+ llist_add_to_end(&target_list, argv[0]);
+ }
+
+ /* 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);
+ goto next;
+ }
+ } else {
+ /* ifdown */
+ if (!iface_state) {
+ bb_error_msg("interface %s not configured", iface);
+ goto next;
+ }
+ }
+ 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 && !any_failures) {
+ char *newiface = xasprintf("%s=%s", iface, liface);
+ if (!iface_state) {
+ 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);
+ }
+ next:
+ free(iface);
+ free(liface);
+ }
+
+ return any_failures;
+}
diff --git a/ap/app/busybox/src/networking/inetd.c b/ap/app/busybox/src/networking/inetd.c
new file mode 100644
index 0000000..584c5e5
--- /dev/null
+++ b/ap/app/busybox/src/networking/inetd.c
@@ -0,0 +1,1673 @@
+/* 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()
+ */
+
+//usage:#define inetd_trivial_usage
+//usage: "[-fe] [-q N] [-R N] [CONFFILE]"
+//usage:#define inetd_full_usage "\n\n"
+//usage: "Listen for network connections and launch programs\n"
+//usage: "\n -f Run in foreground"
+//usage: "\n -e Log to stderr"
+//usage: "\n -q N Socket listen queue (default: 128)"
+//usage: "\n -R N Pause services after N connects/min"
+//usage: "\n (default: 0 - disabled)"
+
+#include <syslog.h>
+#include <sys/resource.h> /* setrlimit */
+#include <sys/socket.h> /* un.h may need this */
+#include <sys/un.h>
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_INETD_RPC
+# if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__)
+# error "You need to build uClibc with UCLIBC_HAS_RPC for NFS support"
+# endif
+# 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 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 FAST_FUNC echo_stream(int, servtab_t *);
+static void FAST_FUNC echo_dg(int, servtab_t *);
+#endif
+/* Internet /dev/null */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+static void FAST_FUNC discard_stream(int, servtab_t *);
+static void FAST_FUNC discard_dg(int, servtab_t *);
+#endif
+/* Return 32 bit time since 1900 */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+static void FAST_FUNC machtime_stream(int, servtab_t *);
+static void FAST_FUNC machtime_dg(int, servtab_t *);
+#endif
+/* Return human-readable time */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+static void FAST_FUNC daytime_stream(int, servtab_t *);
+static void FAST_FUNC daytime_dg(int, servtab_t *);
+#endif
+/* Familiar character generator */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+static void FAST_FUNC chargen_stream(int, servtab_t *);
+static void FAST_FUNC 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 *) FAST_FUNC;
+ void (*bi_dgram_fn)(int, servtab_t *) FAST_FUNC;
+};
+
+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 */
+} FIX_ALIASING;
+#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)
+
+#if 1
+# define dbg(...) ((void)0)
+#else
+# define dbg(...) \
+do { \
+ int dbg_fd = open("inetd_debug.log", O_WRONLY | O_CREAT | O_APPEND, 0666); \
+ if (dbg_fd >= 0) { \
+ fdprintf(dbg_fd, "%d: ", getpid()); \
+ fdprintf(dbg_fd, __VA_ARGS__); \
+ close(dbg_fd); \
+ } \
+} while (0)
+#endif
+
+static void maybe_close(int fd)
+{
+ if (fd >= 0) {
+ close(fd);
+ dbg("closed fd:%d\n", 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);
+ dbg("stopped listening on fd:%d\n", fd);
+ maxsock = -1;
+ dbg("maxsock:%d\n", maxsock);
+ }
+}
+
+static void add_fd_to_set(int fd)
+{
+ if (fd >= 0) {
+ FD_SET(fd, &allsock);
+ dbg("started listening on fd:%d\n", fd);
+ if (maxsock >= 0 && fd > maxsock) {
+ prev_maxsock = maxsock = fd;
+ dbg("maxsock:%d\n", maxsock);
+ 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++;
+ }
+ dbg("recalculated maxsock:%d\n", maxsock);
+ 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->u.sa, 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);
+ dbg("new sep->se_fd:%d (stream)\n", fd);
+ } else {
+ dbg("new sep->se_fd:%d (!stream)\n", fd);
+ }
+
+ 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 const 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);
+ /* Some inetd.conf files have no argv's, not even argv[0].
+ * Fix them up.
+ * (Technically, programs can be execed with argv[0] = NULL,
+ * but many programs do not like that at all) */
+ if (argc == 0)
+ sep->se_argv[0] = xstrdup(sep->se_program);
+
+ /* 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->u.sa, 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) != NULL) {
+ 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 %u",
+ sep->se_program, WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ bb_error_msg("%s: exit signal %u",
+ 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(CONFIG_PID_FILE_PATH "/inetd.pid");
+ 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)) {
+ /* LOG_NDELAY: connect to syslog daemon NOW.
+ * Otherwise, we may open syslog socket
+ * in vforked child, making opened fds and syslog()
+ * internal state inconsistent.
+ * This was observed to leak file descriptors. */
+ openlog(applet_name, LOG_PID | LOG_NDELAY, 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(CONFIG_PID_FILE_PATH "/inetd.pid");
+
+ /* 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);
+//FIXME: explain why no SA_RESTART
+//FIXME: retry_network_setup is unsafe to run in signal handler (many reasons)!
+ sa.sa_handler = retry_network_setup;
+ sigaction_set(SIGALRM, &sa);
+//FIXME: reread_config_file is unsafe to run in signal handler(many reasons)!
+ sa.sa_handler = reread_config_file;
+ sigaction_set(SIGHUP, &sa);
+//FIXME: reap_child is unsafe to run in signal handler (uses stdio)!
+ sa.sa_handler = reap_child;
+ sigaction_set(SIGCHLD, &sa);
+//FIXME: clean_up_and_exit is unsafe to run in signal handler (uses stdio)!
+ 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;
+ }
+ dbg("ready_fd_cnt:%d\n", ready_fd_cnt);
+
+ for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) {
+ if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable))
+ continue;
+
+ dbg("ready fd:%d\n", sep->se_fd);
+ 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);
+ dbg("accepted_fd:%d\n", accepted_fd);
+ 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);
+ dbg("new_udp_fd:%d\n", new_udp_fd);
+ 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 fork 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) {
+ dbg("bind(new_udp_fd) failed\n");
+ close(new_udp_fd);
+ goto udp_err;
+ }
+ dbg("bind(new_udp_fd) succeeded\n");
+ }
+ }
+
+ 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(new_udp_fd);
+ 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("vfork"+1);
+ sleep(1);
+ restore_sigmask(&omask);
+ maybe_close(new_udp_fd);
+ 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 didn't fork */
+
+ if (pid > 0) { /* parent */
+ if (sep->se_wait) {
+ /* wait: we passed 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);
+ dbg("moved new_udp_fd:%d to sep->se_fd:%d\n", 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 fork at all */
+#ifdef INETD_BUILTINS_ENABLED
+ if (sep->se_builtin) {
+ if (pid) { /* "pid" is -1: we did fork */
+ close(sep->se_fd); /* listening socket */
+ dbg("closed sep->se_fd:%d\n", sep->se_fd);
+ logmode = LOGMODE_NONE; /* 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 fork */
+ _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;
+ int r;
+
+ close(new_udp_fd);
+ dbg("closed new_udp_fd:%d\n", new_udp_fd);
+ lsa = xzalloc_lsa(sep->se_family);
+ /* peek at the packet and remember peer addr */
+ 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);
+ dbg("connected ctrl:%d to remote peer\n", ctrl);
+ 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 != 0) {
+ 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(); - WRONG. we are after vfork,
+ * this may confuse syslog() internal state.
+ * Let's hope libc sets syslog fd to CLOEXEC...
+ */
+ xmove_fd(ctrl, STDIN_FILENO);
+ xdup2(STDIN_FILENO, STDOUT_FILENO);
+ dbg("moved ctrl:%d to fd 0,1[,2]\n", ctrl);
+ /* manpages of inetd I managed to find either say
+ * that stderr is also redirected to the network,
+ * or do not talk about redirection at all (!) */
+ if (!sep->se_wait) /* only for usual "tcp nowait" */
+ xdup2(STDIN_FILENO, STDERR_FILENO);
+ /* NB: among others, this loop closes listening sockets
+ * for nowait stream children */
+ for (sep2 = serv_list; sep2; sep2 = sep2->se_next)
+ if (sep2->se_fd != ctrl)
+ maybe_close(sep2->se_fd);
+ sigaction_set(SIGPIPE, &saved_pipe_handler);
+ restore_sigmask(&omask);
+ dbg("execing:'%s'\n", sep->se_program);
+ BB_EXECVP(sep->se_program, sep->se_argv);
+ bb_perror_msg("can't execute '%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 FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC 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 = ' '; i < 127; i++)
+ *end_ring++ = i;
+}
+/* Character generator. MMU arches only. */
+/* ARGSUSED */
+static void FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC machtime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+ uint32_t result;
+
+ result = machtime();
+ full_write(s, &result, sizeof(result));
+}
+static void FAST_FUNC 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 FAST_FUNC daytime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+ time_t t;
+
+ t = time(NULL);
+ fdprintf(s, "%.24s\r\n", ctime(&t));
+}
+static void FAST_FUNC 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/ap/app/busybox/src/networking/interface.c b/ap/app/busybox/src/networking/interface.c
new file mode 100644
index 0000000..9ae8b3f
--- /dev/null
+++ b/ap/app/busybox/src/networking/interface.c
@@ -0,0 +1,1230 @@
+/* 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 source tree.
+ *
+ * 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 "libbb.h"
+#include "inet_common.h"
+#include <net/if.h>
+#include <net/if_arp.h>
+#ifdef HAVE_NET_ETHERNET_H
+# include <net/ethernet.h>
+#endif
+
+#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 from 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; /* defaults to NULL */
+
+ 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_IFNAMSIZ(new->name, name);
+ 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
+ */
+ char *nameend;
+ char *namestart = skip_whitespace(p);
+
+ nameend = namestart;
+ while (*nameend && *nameend != ':' && !isspace(*nameend))
+ nameend++;
+ if (*nameend == ':') {
+ if ((nameend - namestart) < IFNAMSIZ) {
+ memcpy(name, namestart, nameend - namestart);
+ name[nameend - namestart] = '\0';
+ 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_IFNAMSIZ(ifr.ifr_name, ifname);
+ if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
+ close(skfd);
+ return -1;
+ }
+ ife->flags = ifr.ifr_flags;
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ 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_IFNAMSIZ(ifr.ifr_name, ifname);
+ ife->metric = 0;
+ if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0)
+ ife->metric = ifr.ifr_metric;
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ ife->mtu = 0;
+ if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0)
+ ife->mtu = ifr.ifr_mtu;
+
+ memset(&ife->map, 0, sizeof(struct ifmap));
+#ifdef SIOCGIFMAP
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0)
+ ife->map = ifr.ifr_map;
+#endif
+
+#ifdef HAVE_TXQUEUELEN
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ 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_IFNAMSIZ(ifr.ifr_name, ifname);
+ 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_IFNAMSIZ(ifr.ifr_name, ifname);
+ memset(&ife->dstaddr, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0)
+ ife->dstaddr = ifr.ifr_dstaddr;
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ memset(&ife->broadaddr, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
+ ife->broadaddr = ifr.ifr_broadaddr;
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+ 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
+};
+
+/* 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;
+}
+
+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);
+}
+
+
+#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 */
+
+
+static void ife_print6(struct interface *ptr)
+{
+ FILE *f;
+ char addr6[40], devname[20];
+ struct sockaddr_in6 sap;
+ int plen, scope, dad_status, if_idx;
+ char addr6p[8][5];
+
+ f = fopen_for_read(_PATH_PROCNET_IFINET6);
+ if (f == NULL)
+ return;
+
+ 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);
+}
+#else
+#define ife_print6(a) ((void)0)
+#endif
+
+static void ife_print(struct interface *ptr)
+{
+ const struct aftype *ap;
+ const struct hwtype *hw;
+ int hf;
+ int can_compress = 0;
+
+ 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("%-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));
+ }
+
+ ife_print6(ptr);
+
+ 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)
+{
+ sap->sa_family = ib_hwtype.type;
+//TODO: error check?
+ hex2bin((char*)sap->sa_data, bufp, INFINIBAND_ALEN);
+# ifdef HWIB_DEBUG
+ fprintf(stderr, "in_ib(%s): %s\n", bufp, 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/ap/app/busybox/src/networking/ip.c b/ap/app/busybox/src/networking/ip.c
new file mode 100644
index 0000000..98fe621
--- /dev/null
+++ b/ap/app/busybox/src/networking/ip.c
@@ -0,0 +1,177 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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
+ */
+
+/* would need to make the " | " optional depending on more than one selected: */
+//usage:#define ip_trivial_usage
+//usage: "[OPTIONS] {"
+//usage: IF_FEATURE_IP_ADDRESS("address | ")
+//usage: IF_FEATURE_IP_ROUTE("route | ")
+//usage: IF_FEATURE_IP_LINK("link | ")
+//usage: IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage: IF_FEATURE_IP_RULE("rule")
+//usage: "} {COMMAND}"
+//usage:#define ip_full_usage "\n\n"
+//usage: "ip [OPTIONS] OBJECT {COMMAND}\n"
+//usage: "where OBJECT := {"
+//usage: IF_FEATURE_IP_ADDRESS("address | ")
+//usage: IF_FEATURE_IP_ROUTE("route | ")
+//usage: IF_FEATURE_IP_LINK("link | ")
+//usage: IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage: IF_FEATURE_IP_RULE("rule")
+//usage: "}\n"
+//usage: "OPTIONS := { -f[amily] { inet | inet6 | link } | -o[neline] }"
+//usage:
+//usage:#define ipaddr_trivial_usage
+//usage: "{ {add|del} IFADDR dev STRING | {show|flush}\n"
+//usage: " [dev STRING] [to PREFIX] }"
+//usage:#define ipaddr_full_usage "\n\n"
+//usage: "ipaddr {add|delete} IFADDR dev STRING\n"
+//usage: "ipaddr {show|flush} [dev STRING] [scope SCOPE-ID]\n"
+//usage: " [to PREFIX] [label PATTERN]\n"
+//usage: " IFADDR := PREFIX | ADDR peer PREFIX\n"
+//usage: " [broadcast ADDR] [anycast ADDR]\n"
+//usage: " [label STRING] [scope SCOPE-ID]\n"
+//usage: " SCOPE-ID := [host | link | global | NUMBER]"
+//usage:
+//usage:#define iplink_trivial_usage
+//usage: "{ set DEVICE { up | down | arp { on | off } | show [DEVICE] }"
+//usage:#define iplink_full_usage "\n\n"
+//usage: "iplink set DEVICE { up | down | arp | multicast { on | off } |\n"
+//usage: " dynamic { on | off } |\n"
+//usage: " mtu MTU }\n"
+//usage: "iplink show [DEVICE]"
+//usage:
+//usage:#define iproute_trivial_usage
+//usage: "{ list | flush | add | del | change | append |\n"
+//usage: " replace | test } ROUTE"
+//usage:#define iproute_full_usage "\n\n"
+//usage: "iproute { list | flush } SELECTOR\n"
+//usage: "iproute get ADDRESS [from ADDRESS iif STRING]\n"
+//usage: " [oif STRING] [tos TOS]\n"
+//usage: "iproute { add | del | change | append | replace | test } ROUTE\n"
+//usage: " SELECTOR := [root PREFIX] [match PREFIX] [proto RTPROTO]\n"
+//usage: " ROUTE := [TYPE] PREFIX [tos TOS] [proto RTPROTO] [metric METRIC]"
+//usage:
+//usage:#define iprule_trivial_usage
+//usage: "{[list | add | del] RULE}"
+//usage:#define iprule_full_usage "\n\n"
+//usage: "iprule [list | add | del] SELECTOR ACTION\n"
+//usage: " SELECTOR := [from PREFIX] [to PREFIX] [tos TOS] [fwmark FWMARK]\n"
+//usage: " [dev STRING] [pref NUMBER]\n"
+//usage: " ACTION := [table TABLE_ID] [nat ADDRESS]\n"
+//usage: " [prohibit | reject | unreachable]\n"
+//usage: " [realms [SRCREALM/]DSTREALM]\n"
+//usage: " TABLE_ID := [local | main | default | NUMBER]"
+//usage:
+//usage:#define iptunnel_trivial_usage
+//usage: "{ add | change | del | show } [NAME]\n"
+//usage: " [mode { ipip | gre | sit }]\n"
+//usage: " [remote ADDR] [local ADDR] [ttl TTL]"
+//usage:#define iptunnel_full_usage "\n\n"
+//usage: "iptunnel { add | change | del | show } [NAME]\n"
+//usage: " [mode { ipip | gre | sit }] [remote ADDR] [local ADDR]\n"
+//usage: " [[i|o]seq] [[i|o]key KEY] [[i|o]csum]\n"
+//usage: " [ttl TTL] [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]"
+
+#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 FAST_FUNC ip_print_help(char **argv UNUSED_PARAM)
+{
+ bb_show_usage();
+}
+
+typedef int FAST_FUNC (*ip_func_ptr_t)(char**);
+
+static int ip_do(ip_func_ptr_t ip_func, 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 =
+ IF_FEATURE_IP_ADDRESS("address\0")
+ IF_FEATURE_IP_ROUTE("route\0")
+ IF_FEATURE_IP_ROUTE("r\0")
+ IF_FEATURE_IP_LINK("link\0")
+ IF_FEATURE_IP_TUNNEL("tunnel\0")
+ IF_FEATURE_IP_TUNNEL("tunl\0")
+ IF_FEATURE_IP_RULE("rule\0")
+ ;
+ static const ip_func_ptr_t ip_func_ptrs[] = {
+ ip_print_help,
+ IF_FEATURE_IP_ADDRESS(do_ipaddr,)
+ IF_FEATURE_IP_ROUTE(do_iproute,)
+ IF_FEATURE_IP_ROUTE(do_iproute,)
+ IF_FEATURE_IP_LINK(do_iplink,)
+ IF_FEATURE_IP_TUNNEL(do_iptunnel,)
+ IF_FEATURE_IP_TUNNEL(do_iptunnel,)
+ IF_FEATURE_IP_RULE(do_iprule,)
+ };
+ ip_func_ptr_t ip_func;
+ int key;
+
+ argv = ip_parse_common_args(argv + 1);
+ key = *argv ? index_in_substrings(keywords, *argv++) : -1;
+ ip_func = ip_func_ptrs[key + 1];
+
+ return ip_func(argv);
+}
+
+#endif /* any of ENABLE_FEATURE_IP_xxx is 1 */
diff --git a/ap/app/busybox/src/networking/ipcalc.c b/ap/app/busybox/src/networking/ipcalc.c
new file mode 100644
index 0000000..3c8b8bf
--- /dev/null
+++ b/ap/app/busybox/src/networking/ipcalc.c
@@ -0,0 +1,213 @@
+/* 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 source tree.
+ */
+
+//usage:#define ipcalc_trivial_usage
+//usage: "[OPTIONS] ADDRESS[[/]NETMASK] [NETMASK]"
+//usage:#define ipcalc_full_usage "\n\n"
+//usage: "Calculate IP network settings from a IP address\n"
+//usage: IF_FEATURE_IPCALC_LONG_OPTIONS(
+//usage: "\n -b,--broadcast Display calculated broadcast address"
+//usage: "\n -n,--network Display calculated network address"
+//usage: "\n -m,--netmask Display default netmask for IP"
+//usage: IF_FEATURE_IPCALC_FANCY(
+//usage: "\n -p,--prefix Display the prefix for IP/NETMASK"
+//usage: "\n -h,--hostname Display first resolved host name"
+//usage: "\n -s,--silent Don't ever display error messages"
+//usage: )
+//usage: )
+//usage: IF_NOT_FEATURE_IPCALC_LONG_OPTIONS(
+//usage: "\n -b Display calculated broadcast address"
+//usage: "\n -n Display calculated network address"
+//usage: "\n -m Display default netmask for IP"
+//usage: IF_FEATURE_IPCALC_FANCY(
+//usage: "\n -p Display the prefix for IP/NETMASK"
+//usage: "\n -h Display first resolved host name"
+//usage: "\n -s Don't ever display error messages"
+//usage: )
+//usage: )
+
+#include "libbb.h"
+/* After libbb.h, because on some systems it needs other includes */
+#include <arpa/inet.h>
+
+#define CLASS_A_NETMASK ntohl(0xFF000000)
+#define CLASS_B_NETMASK ntohl(0xFFFF0000)
+#define CLASS_C_NETMASK ntohl(0xFFFFFF00)
+
+static unsigned long get_netmask(unsigned long ipaddr)
+{
+ ipaddr = htonl(ipaddr);
+
+ if ((ipaddr & 0xC0000000) == 0xC0000000)
+ return CLASS_C_NETMASK;
+ else if ((ipaddr & 0x80000000) == 0x80000000)
+ return CLASS_B_NETMASK;
+ else if ((ipaddr & 0x80000000) == 0)
+ return CLASS_A_NETMASK;
+ else
+ return 0;
+}
+
+#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" // netmask from IP (assuming complete class A, B, or C network)
+ "broadcast\0" No_argument "b" // broadcast from IP [netmask]
+ "network\0" No_argument "n" // network from IP [netmask]
+# if ENABLE_FEATURE_IPCALC_FANCY
+ "prefix\0" No_argument "p" // prefix from IP[/prefix] [netmask]
+ "hostname\0" No_argument "h" // hostname from IP
+ "silent\0" No_argument "s" // don’t ever display error messages
+# endif
+ ;
+#endif
+
+int ipcalc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipcalc_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned opt;
+ bool have_netmask = 0;
+ struct in_addr s_netmask, s_broadcast, s_network, s_ipaddr;
+ /* struct in_addr { in_addr_t s_addr; } and in_addr_t
+ * (which in turn is just a typedef to uint32_t)
+ * are essentially the same type. A few macros for less verbosity: */
+#define netmask (s_netmask.s_addr)
+#define broadcast (s_broadcast.s_addr)
+#define network (s_network.s_addr)
+#define ipaddr (s_ipaddr.s_addr)
+ char *ipstr;
+
+#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS
+ applet_long_options = ipcalc_longopts;
+#endif
+ opt_complementary = "-1:?2"; /* minimum 1 arg, maximum 2 args */
+ opt = getopt32(argv, "mbn" IF_FEATURE_IPCALC_FANCY("phs"));
+ argv += optind;
+ if (opt & SILENT)
+ logmode = LOGMODE_NONE; /* suppress error_msg() output */
+ opt &= ~SILENT;
+ if (!(opt & (BROADCAST | NETWORK | NETPREFIX))) {
+ /* if no options at all or
+ * (no broadcast,network,prefix) and (two args)... */
+ if (!opt || argv[1])
+ bb_show_usage();
+ }
+
+ ipstr = argv[0];
+ if (ENABLE_FEATURE_IPCALC_FANCY) {
+ unsigned long netprefix = 0;
+ char *prefixstr;
+
+ prefixstr = ipstr;
+
+ while (*prefixstr) {
+ if (*prefixstr == '/') {
+ *prefixstr++ = '\0';
+ 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++;
+ }
+ }
+
+ if (inet_aton(ipstr, &s_ipaddr) == 0) {
+ bb_error_msg_and_die("bad IP address: %s", argv[0]);
+ }
+
+ if (argv[1]) {
+ if (ENABLE_FEATURE_IPCALC_FANCY && have_netmask) {
+ bb_error_msg_and_die("use prefix or netmask, not both");
+ }
+ if (inet_aton(argv[1], &s_netmask) == 0) {
+ bb_error_msg_and_die("bad netmask: %s", argv[1]);
+ }
+ } 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(s_netmask));
+ }
+
+ if (opt & BROADCAST) {
+ broadcast = (ipaddr & netmask) | ~netmask;
+ printf("BROADCAST=%s\n", inet_ntoa(s_broadcast));
+ }
+
+ if (opt & NETWORK) {
+ network = ipaddr & netmask;
+ printf("NETWORK=%s\n", inet_ntoa(s_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("can't find hostname for %s", argv[0]);
+ }
+ str_tolower(hostinfo->h_name);
+
+ printf("HOSTNAME=%s\n", hostinfo->h_name);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/ap/app/busybox/src/networking/isrv.c b/ap/app/busybox/src/networking/isrv.c
new file mode 100644
index 0000000..1c6491e
--- /dev/null
+++ b/ap/app/busybox/src/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 GPLv2, see file LICENSE in this source tree.
+ */
+
+#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/ap/app/busybox/src/networking/isrv.h b/ap/app/busybox/src/networking/isrv.h
new file mode 100644
index 0000000..4c7e01d
--- /dev/null
+++ b/ap/app/busybox/src/networking/isrv.h
@@ -0,0 +1,37 @@
+/* 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 GPLv2, see file LICENSE in this source tree.
+ */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* 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
+);
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/ap/app/busybox/src/networking/isrv_identd.c b/ap/app/busybox/src/networking/isrv_identd.c
new file mode 100644
index 0000000..a41405c
--- /dev/null
+++ b/ap/app/busybox/src/networking/isrv_identd.c
@@ -0,0 +1,157 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Fake identd server.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//usage:#define fakeidentd_trivial_usage
+//usage: "[-fiw] [-b ADDR] [STRING]"
+//usage:#define fakeidentd_full_usage "\n\n"
+//usage: "Provide fake ident (auth) service\n"
+//usage: "\n -f Run in foreground"
+//usage: "\n -i Inetd mode"
+//usage: "\n -w Inetd 'wait' mode"
+//usage: "\n -b ADDR Bind to specified address"
+//usage: "\n STRING Ident answer string (default: nobody)"
+
+#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, LOG_PID, 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/ap/app/busybox/src/networking/libiproute/Kbuild.src b/ap/app/busybox/src/networking/libiproute/Kbuild.src
new file mode 100644
index 0000000..7c78f3c
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/Kbuild.src
@@ -0,0 +1,66 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under GPLv2 or later, see file LICENSE in this source tree.
+#
+
+lib-y:=
+
+INSERT
+
+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/ap/app/busybox/src/networking/libiproute/ip_common.h b/ap/app/busybox/src/networking/libiproute/ip_common.h
new file mode 100644
index 0000000..30c7e59
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ip_common.h
@@ -0,0 +1,36 @@
+/* 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
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+char FAST_FUNC **ip_parse_common_args(char **argv);
+//int FAST_FUNC print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+int FAST_FUNC ipaddr_list_or_flush(char **argv, int flush);
+//int FAST_FUNC iproute_monitor(char **argv);
+//void FAST_FUNC ipneigh_reset_filter(void);
+
+int FAST_FUNC do_ipaddr(char **argv);
+int FAST_FUNC do_iproute(char **argv);
+int FAST_FUNC do_iprule(char **argv);
+//int FAST_FUNC do_ipneigh(char **argv);
+int FAST_FUNC do_iptunnel(char **argv);
+int FAST_FUNC do_iplink(char **argv);
+//int FAST_FUNC do_ipmonitor(char **argv);
+//int FAST_FUNC do_multiaddr(char **argv);
+//int FAST_FUNC do_multiroute(char **argv);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/ip_parse_common_args.c b/ap/app/busybox/src/networking/libiproute/ip_parse_common_args.c
new file mode 100644
index 0000000..59c759b
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ip_parse_common_args.c
@@ -0,0 +1,81 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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** FAST_FUNC 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/ap/app/busybox/src/networking/libiproute/ipaddress.c b/ap/app/busybox/src/networking/libiproute/ipaddress.c
new file mode 100644
index 0000000..3fd3f44
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ipaddress.c
@@ -0,0 +1,768 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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
+
+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;
+} FIX_ALIASING;
+typedef struct filter_t filter_t;
+
+#define G_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('<');
+ if (flags & IFF_UP && !(flags & IFF_RUNNING))
+ printf("NO-CARRIER,");
+ 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_IFNAMSIZ(ifr.ifr_name, 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 NOINLINE int print_linkinfo(const struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return -1;
+
+ if (G_filter.ifindex && ifi->ifi_index != G_filter.ifindex)
+ return 0;
+ if (G_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 (G_filter.label
+ && (!G_filter.family || G_filter.family == AF_PACKET)
+ && fnmatch(G_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>" - we checked tb[IFLA_IFNAME] above*/
+ (char*)RTA_DATA(tb[IFLA_IFNAME])
+ );
+
+ {
+ unsigned m_flag = 0;
+ 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
+/* IFLA_OPERSTATE was added to kernel with the same commit as IFF_DORMANT */
+#ifdef IFF_DORMANT
+ if (tb[IFLA_OPERSTATE]) {
+ static const char operstate_labels[] ALIGN1 =
+ "UNKNOWN\0""NOTPRESENT\0""DOWN\0""LOWERLAYERDOWN\0"
+ "TESTING\0""DORMANT\0""UP\0";
+ printf("state %s ", nth_string(operstate_labels,
+ *(uint8_t *)RTA_DATA(tb[IFLA_OPERSTATE])));
+ }
+#endif
+ if (G_filter.showqueue)
+ print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME]));
+
+ if (!G_filter.family || G_filter.family == AF_PACKET) {
+ SPRINT_BUF(b1);
+ printf("%c link/%s ", _SL_, ll_type_n2a(ifi->ifi_type, 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_all();*/
+ return 0;
+}
+
+static int flush_update(void)
+{
+ if (rtnl_send(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) {
+ bb_perror_msg("can't send flush request");
+ return -1;
+ }
+ G_filter.flushp = 0;
+ return 0;
+}
+
+static int FAST_FUNC 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 (G_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 (G_filter.ifindex && G_filter.ifindex != ifa->ifa_index)
+ return 0;
+ if ((G_filter.scope ^ ifa->ifa_scope) & G_filter.scopemask)
+ return 0;
+ if ((G_filter.flags ^ ifa->ifa_flags) & G_filter.flagmask)
+ return 0;
+ if (G_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(G_filter.label, label, 0) != 0)
+ return 0;
+ }
+ if (G_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, &G_filter.pfx, G_filter.pfx.bitlen))
+ return 0;
+ }
+ }
+
+ if (G_filter.flushb) {
+ struct nlmsghdr *fn;
+ if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
+ if (flush_update())
+ return -1;
+ }
+ fn = (struct nlmsghdr*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELADDR;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++G_filter.rth->seq;
+ G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb;
+ G_filter.flushed = 1;
+ return 0;
+ }
+
+ if (n->nlmsg_type == RTM_DELADDR)
+ printf("Deleted ");
+
+ if (G_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_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_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_DATA(rta_tb[IFA_BROADCAST]),
+ abuf, sizeof(abuf))
+ );
+ }
+ if (rta_tb[IFA_ANYCAST]) {
+ printf("any %s ",
+ rt_addr_n2a(ifa->ifa_family,
+ RTA_DATA(rta_tb[IFA_ANYCAST]),
+ abuf, sizeof(abuf))
+ );
+ }
+ printf("scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, 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_all();*/
+ 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
+ || (G_filter.family && G_filter.family != ifa->ifa_family)
+ ) {
+ continue;
+ }
+ print_addrinfo(NULL, n, NULL);
+ }
+ return 0;
+}
+
+
+static int FAST_FUNC 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 = xzalloc(n->nlmsg_len + sizeof(void*));
+
+ memcpy(&h->h, n, n->nlmsg_len);
+ /*h->next = NULL; - xzalloc did it */
+
+ 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(&G_filter, 0, sizeof(G_filter));
+ G_filter.oneline = _oneline;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int FAST_FUNC 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);
+ G_filter.showqueue = 1;
+
+ if (G_filter.family == AF_UNSPEC)
+ G_filter.family = preferred_family;
+
+ if (flush) {
+ if (!*argv) {
+ bb_error_msg_and_die(bb_msg_requires_arg, "flush");
+ }
+ if (G_filter.family == AF_PACKET) {
+ bb_error_msg_and_die("can't flush link addresses");
+ }
+ }
+
+ while (*argv) {
+ const smalluint key = index_in_strings(option, *argv);
+ if (key == 0) { /* to */
+ NEXT_ARG();
+ get_prefix(&G_filter.pfx, *argv, G_filter.family);
+ if (G_filter.family == AF_UNSPEC) {
+ G_filter.family = G_filter.pfx.family;
+ }
+ } else if (key == 1) { /* scope */
+ uint32_t scope = 0;
+ NEXT_ARG();
+ G_filter.scopemask = -1;
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ if (strcmp(*argv, "all") != 0) {
+ invarg(*argv, "scope");
+ }
+ scope = RT_SCOPE_NOWHERE;
+ G_filter.scopemask = 0;
+ }
+ G_filter.scope = scope;
+ } else if (key == 2) { /* up */
+ G_filter.up = 1;
+ } else if (key == 3) { /* label */
+ NEXT_ARG();
+ G_filter.label = *argv;
+ } else {
+ if (key == 4) /* dev */
+ NEXT_ARG();
+ 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) {
+ G_filter.ifindex = xll_name_to_index(filter_dev);
+ }
+
+ if (flush) {
+ char flushb[4096-512];
+
+ G_filter.flushb = flushb;
+ G_filter.flushp = 0;
+ G_filter.flushe = sizeof(flushb);
+ G_filter.rth = &rth;
+
+ for (;;) {
+ xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETADDR);
+ G_filter.flushed = 0;
+ xrtnl_dump_filter(&rth, print_addrinfo, NULL);
+ if (G_filter.flushed == 0) {
+ return 0;
+ }
+ if (flush_update() < 0) {
+ return 1;
+ }
+ }
+ }
+
+ if (G_filter.family != AF_PACKET) {
+ xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETADDR);
+ xrtnl_dump_filter(&rth, store_nlmsg, &ainfo);
+ }
+
+
+ if (G_filter.family && G_filter.family != AF_PACKET) {
+ struct nlmsg_list **lp;
+ lp = &linfo;
+
+ if (G_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
+ || (G_filter.family && G_filter.family != ifa->ifa_family)
+ ) {
+ continue;
+ }
+ if ((G_filter.scope ^ ifa->ifa_scope) & G_filter.scopemask)
+ continue;
+ if ((G_filter.flags ^ ifa->ifa_flags) & G_filter.flagmask)
+ continue;
+ if (G_filter.pfx.family || G_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 (G_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, &G_filter.pfx, G_filter.pfx.bitlen))
+ continue;
+ }
+ if (G_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(G_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 (G_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 smalluint arg = index_in_strings(option, *argv);
+ if (arg <= 1) { /* peer, 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;
+ } else if (arg <= 3) { /* broadcast, 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;
+ }
+ } else if (arg == 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;
+ } else if (arg == 5) { /* scope */
+ uint32_t scope = 0;
+ NEXT_ARG();
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ invarg(*argv, "scope");
+ }
+ req.ifa.ifa_scope = scope;
+ scoped = 1;
+ } else if (arg == 6) { /* dev */
+ NEXT_ARG();
+ d = *argv;
+ } else if (arg == 7) { /* label */
+ NEXT_ARG();
+ l = *argv;
+ addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l) + 1);
+ } else {
+ if (arg == 8) /* local */
+ NEXT_ARG();
+ 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) {
+ /* There was no "dev IFACE", but we need that */
+ bb_error_msg_and_die("need \"dev IFACE\"");
+ }
+ 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 FAST_FUNC do_ipaddr(char **argv)
+{
+ static const char commands[] ALIGN1 =
+ "add\0""delete\0""list\0""show\0""lst\0""flush\0";
+ smalluint cmd = 2;
+ if (*argv) {
+ cmd = index_in_substrings(commands, *argv);
+ if (cmd > 5)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ if (cmd <= 1)
+ return ipaddr_modify((cmd == 0) ? RTM_NEWADDR : RTM_DELADDR, argv);
+ }
+ /* 2 == list, 3 == show, 4 == lst */
+ return ipaddr_list_or_flush(argv, cmd == 5);
+}
diff --git a/ap/app/busybox/src/networking/libiproute/iplink.c b/ap/app/busybox/src/networking/libiproute/iplink.c
new file mode 100644
index 0000000..bad2017
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/iplink.c
@@ -0,0 +1,384 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+#include <net/if.h>
+#include <net/if_packet.h>
+#include <netpacket/packet.h>
+#include <netinet/if_ether.h>
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef IFLA_LINKINFO
+# define IFLA_LINKINFO 18
+# define IFLA_INFO_KIND 1
+#endif
+
+/* 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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_IFNAMSIZ(ifr.ifr_name, dev);
+ strncpy_IFNAMSIZ(ifr.ifr_newname, newdev);
+ 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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);
+ getsockname(s, (struct sockaddr*)&me, &alen);
+ //never happens:
+ //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_IFNAMSIZ(ifr->ifr_name, dev);
+ 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""qlen\0""multicast\0"
+ "arp\0""address\0""dev\0";
+ enum { ARG_up = 0, ARG_down, ARG_name, ARG_mtu, ARG_qlen, 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;
+ } else if (key == ARG_down) {
+ mask |= IFF_UP;
+ flags &= ~IFF_UP;
+ } else if (key == ARG_name) {
+ NEXT_ARG();
+ newname = *argv;
+ } else if (key == ARG_mtu) {
+ NEXT_ARG();
+ if (mtu != -1)
+ duparg("mtu", *argv);
+ mtu = get_unsigned(*argv, "mtu");
+ } else if (key == ARG_qlen) {
+ NEXT_ARG();
+ if (qlen != -1)
+ duparg("qlen", *argv);
+ qlen = get_unsigned(*argv, "qlen");
+ } else if (key == ARG_addr) {
+ NEXT_ARG();
+ newaddr = *argv;
+ } else if (key >= ARG_dev) {
+ if (key == ARG_dev) {
+ NEXT_ARG();
+ }
+ if (dev)
+ duparg2("dev", *argv);
+ dev = *argv;
+ } else {
+ int param;
+ NEXT_ARG();
+ param = index_in_strings(str_on_off, *argv);
+ if (key == ARG_multicast) {
+ if (param < 0)
+ die_must_be_on_off("multicast");
+ mask |= IFF_MULTICAST;
+ if (param == PARM_on)
+ flags |= IFF_MULTICAST;
+ else
+ flags &= ~IFF_MULTICAST;
+ } else if (key == ARG_arp) {
+ if (param < 0)
+ die_must_be_on_off("arp");
+ mask |= IFF_NOARP;
+ if (param == PARM_on)
+ flags &= ~IFF_NOARP;
+ else
+ flags |= IFF_NOARP;
+ }
+ }
+ 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);
+ set_address(&ifr0, 0);
+ }
+ if (newbrd) {
+ parse_address(dev, htype, halen, newbrd, &ifr1);
+ set_address(&ifr1, 1);
+ }
+ }
+
+ 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 (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);
+}
+
+#ifndef NLMSG_TAIL
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+#endif
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_change(char **argv, const unsigned rtm)
+{
+ static const char keywords[] ALIGN1 =
+ "link\0""name\0""type\0""dev\0";
+ enum {
+ ARG_link,
+ ARG_name,
+ ARG_type,
+ ARG_dev,
+ };
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ char buf[1024];
+ } req;
+ smalluint arg;
+ char *name_str = NULL, *link_str = NULL, *type_str = NULL, *dev_str = NULL;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = rtm;
+ req.i.ifi_family = preferred_family;
+ if (rtm == RTM_NEWLINK)
+ req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+
+ while (*argv) {
+ arg = index_in_substrings(keywords, *argv);
+ if (arg == ARG_link) {
+ NEXT_ARG();
+ link_str = *argv;
+ } else if (arg == ARG_name) {
+ NEXT_ARG();
+ name_str = *argv;
+ } else if (arg == ARG_type) {
+ NEXT_ARG();
+ type_str = *argv;
+ } else {
+ if (arg == ARG_dev) {
+ if (dev_str)
+ duparg(*argv, "dev");
+ NEXT_ARG();
+ }
+ dev_str = *argv;
+ }
+ argv++;
+ }
+ xrtnl_open(&rth);
+ ll_init_map(&rth);
+ if (type_str) {
+ struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
+
+ addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
+ addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type_str,
+ strlen(type_str));
+ linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
+ }
+ if (rtm != RTM_NEWLINK) {
+ if (!dev_str)
+ return 1; /* Need a device to delete */
+ req.i.ifi_index = xll_name_to_index(dev_str);
+ } else {
+ if (!name_str)
+ name_str = dev_str;
+ if (link_str) {
+ int idx = xll_name_to_index(link_str);
+ addattr_l(&req.n, sizeof(req), IFLA_LINK, &idx, 4);
+ }
+ }
+ if (name_str) {
+ const size_t name_len = strlen(name_str) + 1;
+ if (name_len < 2 || name_len > IFNAMSIZ)
+ invarg(name_str, "name");
+ addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name_str, name_len);
+ }
+ 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 FAST_FUNC do_iplink(char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "add\0""delete\0""set\0""show\0""lst\0""list\0";
+ if (*argv) {
+ smalluint key = index_in_substrings(keywords, *argv);
+ if (key > 5) /* invalid argument */
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ if (key <= 1) /* add/delete */
+ return do_change(argv, key ? RTM_DELLINK : RTM_NEWLINK);
+ else if (key == 2) /* set */
+ return do_set(argv);
+ }
+ /* show, lst, list */
+ return ipaddr_list_link(argv);
+}
diff --git a/ap/app/busybox/src/networking/libiproute/iproute.c b/ap/app/busybox/src/networking/libiproute/iproute.c
new file mode 100644
index 0000000..f8a67d9
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/iproute.c
@@ -0,0 +1,946 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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
+
+
+struct filter_t {
+ int tb;
+ smallint flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ struct rtnl_handle *rth;
+ //int protocol, protocolmask; - write-only fields?!
+ //int scope, scopemask; - unused
+ //int type; - read-only
+ //int typemask; - unused
+ //int tos, tosmask; - unused
+ int iif;
+ int oif;
+ //int realm, realmmask; - unused
+ //inet_prefix rprefsrc; - read-only
+ inet_prefix rvia;
+ inet_prefix rdst;
+ inet_prefix mdst;
+ inet_prefix rsrc;
+ inet_prefix msrc;
+} FIX_ALIASING;
+typedef struct filter_t filter_t;
+
+#define G_filter (*(filter_t*)&bb_common_bufsiz1)
+
+static int flush_update(void)
+{
+ if (rtnl_send(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) {
+ bb_perror_msg("can't send flush request");
+ return -1;
+ }
+ G_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 FAST_FUNC 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 (G_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 (G_filter.tb) {
+ if (G_filter.tb < 0) {
+ if (!(r->rtm_flags & RTM_F_CLONED)) {
+ return 0;
+ }
+ } else {
+ if (r->rtm_flags & RTM_F_CLONED) {
+ return 0;
+ }
+ if (G_filter.tb == RT_TABLE_LOCAL) {
+ if (r->rtm_type != RTN_LOCAL) {
+ return 0;
+ }
+ } else if (G_filter.tb == RT_TABLE_MAIN) {
+ if (r->rtm_type == RTN_LOCAL) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ }
+ } else {
+ if (G_filter.tb > 0 && G_filter.tb != r->rtm_table) {
+ return 0;
+ }
+ }
+ if (G_filter.rdst.family
+ && (r->rtm_family != G_filter.rdst.family || G_filter.rdst.bitlen > r->rtm_dst_len)
+ ) {
+ return 0;
+ }
+ if (G_filter.mdst.family
+ && (r->rtm_family != G_filter.mdst.family
+ || (G_filter.mdst.bitlen >= 0 && G_filter.mdst.bitlen < r->rtm_dst_len)
+ )
+ ) {
+ return 0;
+ }
+ if (G_filter.rsrc.family
+ && (r->rtm_family != G_filter.rsrc.family || G_filter.rsrc.bitlen > r->rtm_src_len)
+ ) {
+ return 0;
+ }
+ if (G_filter.msrc.family
+ && (r->rtm_family != G_filter.msrc.family
+ || (G_filter.msrc.bitlen >= 0 && G_filter.msrc.bitlen < r->rtm_src_len)
+ )
+ ) {
+ return 0;
+ }
+
+ memset(tb, 0, sizeof(tb));
+ memset(&src, 0, sizeof(src));
+ memset(&dst, 0, sizeof(dst));
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (tb[RTA_SRC]) {
+ src.bitlen = r->rtm_src_len;
+ src.bytelen = (r->rtm_family == AF_INET6 ? 16 : 4);
+ memcpy(src.data, RTA_DATA(tb[RTA_SRC]), src.bytelen);
+ }
+ if (tb[RTA_DST]) {
+ dst.bitlen = r->rtm_dst_len;
+ dst.bytelen = (r->rtm_family == AF_INET6 ? 16 : 4);
+ memcpy(dst.data, RTA_DATA(tb[RTA_DST]), dst.bytelen);
+ }
+
+ if (G_filter.rdst.family
+ && inet_addr_match(&dst, &G_filter.rdst, G_filter.rdst.bitlen)
+ ) {
+ return 0;
+ }
+ if (G_filter.mdst.family
+ && G_filter.mdst.bitlen >= 0
+ && inet_addr_match(&dst, &G_filter.mdst, r->rtm_dst_len)
+ ) {
+ return 0;
+ }
+ if (G_filter.rsrc.family
+ && inet_addr_match(&src, &G_filter.rsrc, G_filter.rsrc.bitlen)
+ ) {
+ return 0;
+ }
+ if (G_filter.msrc.family && G_filter.msrc.bitlen >= 0
+ && inet_addr_match(&src, &G_filter.msrc, r->rtm_src_len)
+ ) {
+ return 0;
+ }
+ if (G_filter.oif != 0) {
+ if (!tb[RTA_OIF])
+ return 0;
+ if (G_filter.oif != *(int*)RTA_DATA(tb[RTA_OIF]))
+ return 0;
+ }
+
+ if (G_filter.flushb) {
+ struct nlmsghdr *fn;
+
+ /* We are creating route flush commands */
+
+ if (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 (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
+ if (flush_update())
+ bb_error_msg_and_die("flush");
+ }
+ fn = (void*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELROUTE;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++G_filter.rth->seq;
+ G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb;
+ G_filter.flushed = 1;
+ return 0;
+ }
+
+ /* We are printing routes */
+
+ if (n->nlmsg_type == RTM_DELROUTE) {
+ printf("Deleted ");
+ }
+ if (r->rtm_type != RTN_UNICAST /* && !G_filter.type - always 0 */) {
+ printf("%s ", rtnl_rtntype_n2a(r->rtm_type, b1));
+ }
+
+ if (tb[RTA_DST]) {
+ if (r->rtm_dst_len != host_len) {
+ printf("%s/%u ", rt_addr_n2a(r->rtm_family,
+ 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_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] && G_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]) {
+ printf("dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF])));
+ }
+
+ /* Todo: parse & show "proto kernel", "scope link" here */
+
+ if (tb[RTA_PREFSRC] && /*G_filter.rprefsrc.bitlen - always 0*/ 0 != 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_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] && G_filter.iif == 0) {
+ 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"IF_FEATURE_IP_RULE("table\0")
+ "dev\0""oif\0""to\0""metric\0";
+ enum {
+ ARG_src,
+ ARG_via,
+ ARG_mtu, PARM_lock,
+ ARG_protocol,
+IF_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();
+ }
+ mtu = get_unsigned(*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();
+ metric = get_u32(*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("can't flush routing cache");
+ return;
+ }
+ close(flush_fd);
+}
+
+static void iproute_reset_filter(void)
+{
+ memset(&G_filter, 0, sizeof(G_filter));
+ G_filter.mdst.bitlen = -1;
+ G_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();
+ G_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();
+ //G_filter.protocolmask = -1;
+ if (rtnl_rtprot_a2n(&prot, *argv)) {
+ if (index_in_strings(keywords, *argv) != KW_all)
+ invarg(*argv, "protocol");
+ prot = 0;
+ //G_filter.protocolmask = 0;
+ }
+ //G_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(&G_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)
+ G_filter.tb = -1;
+ else if (parm == KW_all)
+ G_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");
+ G_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' */
+ G_filter.tb = -1;
+ } else if (arg == KW_from) {
+ NEXT_ARG();
+ parm = index_in_substrings(keywords, *argv);
+ if (parm == KW_root) {
+ NEXT_ARG();
+ get_prefix(&G_filter.rsrc, *argv, do_ipv6);
+ } else if (parm == KW_match) {
+ NEXT_ARG();
+ get_prefix(&G_filter.msrc, *argv, do_ipv6);
+ } else {
+ if (parm == KW_exact)
+ NEXT_ARG();
+ get_prefix(&G_filter.msrc, *argv, do_ipv6);
+ G_filter.rsrc = G_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(&G_filter.rdst, *argv, do_ipv6);
+ } else if (arg == KW_match) {
+ NEXT_ARG();
+ get_prefix(&G_filter.mdst, *argv, do_ipv6);
+ } else { /* "to exact" is the default */
+ if (arg == KW_exact)
+ NEXT_ARG();
+ get_prefix(&G_filter.mdst, *argv, do_ipv6);
+ G_filter.rdst = G_filter.mdst;
+ }
+ }
+ argv++;
+ }
+
+ if (do_ipv6 == AF_UNSPEC && G_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);
+ G_filter.iif = idx;
+ }
+ if (od) {
+ idx = xll_name_to_index(od);
+ G_filter.oif = idx;
+ }
+ }
+
+ if (flush) {
+ char flushb[4096-512];
+
+ if (G_filter.tb == -1) { /* "flush table cache" */
+ if (do_ipv6 != AF_INET6)
+ iproute_flush_cache();
+ if (do_ipv6 == AF_INET)
+ return 0;
+ }
+
+ G_filter.flushb = flushb;
+ G_filter.flushp = 0;
+ G_filter.flushe = sizeof(flushb);
+ G_filter.rth = &rth;
+
+ for (;;) {
+ xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
+ G_filter.flushed = 0;
+ xrtnl_dump_filter(&rth, print_route, NULL);
+ if (G_filter.flushed == 0)
+ return 0;
+ if (flush_update())
+ return 1;
+ }
+ }
+
+ if (G_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("can't 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("can't 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 FAST_FUNC 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/ap/app/busybox/src/networking/libiproute/iprule.c b/ap/app/busybox/src/networking/libiproute/iprule.c
new file mode 100644
index 0000000..241a6bf
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/iprule.c
@@ -0,0 +1,319 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 FAST_FUNC 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;
+*/
+ printf("%u:\t", tb[RTA_PRIORITY] ?
+ *(unsigned*)RTA_DATA(tb[RTA_PRIORITY])
+ : 0);
+ printf("from ");
+ if (tb[RTA_SRC]) {
+ if (r->rtm_src_len != host_len) {
+ printf("%s/%u", rt_addr_n2a(r->rtm_family,
+ 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_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));
+ }
+ 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));
+
+ 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));
+ }
+ printf("%s ",
+ rtnl_rtrealm_n2a(to, 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), stdout);
+
+ bb_putchar('\n');
+ /*fflush_all();*/
+ 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(*argv);
+ 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();
+ pref = get_u32(*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();
+ fwmark = get_u32(*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 FAST_FUNC do_iprule(char **argv)
+{
+ static const char ip_rule_commands[] ALIGN1 =
+ "add\0""delete\0""list\0""show\0";
+ if (*argv) {
+ smalluint cmd = index_in_substrings(ip_rule_commands, *argv);
+ if (cmd > 3)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ if (cmd < 2)
+ return iprule_modify((cmd == 0) ? RTM_NEWRULE : RTM_DELRULE, argv);
+ }
+ return iprule_list(argv);
+}
diff --git a/ap/app/busybox/src/networking/libiproute/iptunnel.c b/ap/app/busybox/src/networking/libiproute/iptunnel.c
new file mode 100644
index 0000000..2b651b9
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/iptunnel.c
@@ -0,0 +1,576 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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_IFNAMSIZ(ifr.ifr_name, dev);
+ 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_IFNAMSIZ(ifr.ifr_name, basedev);
+ 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_IFNAMSIZ(ifr.ifr_name, p->name);
+ } else {
+ strncpy_IFNAMSIZ(ifr.ifr_name, basedev);
+ }
+ 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_IFNAMSIZ(ifr.ifr_name, p->name);
+ } else {
+ strncpy_IFNAMSIZ(ifr.ifr_name, basedev);
+ }
+ 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));
+ medium[0] = '\0';
+
+ 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", "can't 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 {
+ uval = get_unsigned(*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 {
+ uval = get_unsigned(*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 {
+ uval = get_unsigned(*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_IFNAMSIZ(medium, *argv);
+ } else if (key == ARG_ttl) {
+ unsigned uval;
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key != ARG_inherit) {
+ uval = get_unsigned(*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_IFNAMSIZ(p->name, *argv);
+ 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("can't 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));
+ }
+ 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("can't 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 FAST_FUNC 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 };
+
+ if (*argv) {
+ smalluint key = index_in_substrings(keywords, *argv);
+ if (key > 5)
+ 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/ap/app/busybox/src/networking/libiproute/libnetlink.c b/ap/app/busybox/src/networking/libiproute/libnetlink.c
new file mode 100644
index 0000000..c7533a4
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/libnetlink.c
@@ -0,0 +1,401 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 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);
+ 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);
+ getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len);
+
+/* too much paranoia
+ 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);
+}
+
+int FAST_FUNC xrtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtgenmsg g;
+ } req;
+
+ 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 rtnl_send(rth, (void*)&req, sizeof(req));
+}
+
+//TODO: pass rth->fd instead of full rth?
+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 *) FAST_FUNC,
+ 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 *) FAST_FUNC,
+ 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("can't 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;
+ move_to_unaligned32(RTA_DATA(rta), data);
+ 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;
+ move_to_unaligned32(RTA_DATA(subrta), data);
+ 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;
+}
+
+
+void 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);
+ }
+}
diff --git a/ap/app/busybox/src/networking/libiproute/libnetlink.h b/ap/app/busybox/src/networking/libiproute/libnetlink.h
new file mode 100644
index 0000000..51bee2d
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/libnetlink.h
@@ -0,0 +1,49 @@
+/* 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>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+struct rtnl_handle {
+ int fd;
+ struct sockaddr_nl local;
+ struct sockaddr_nl peer;
+ uint32_t seq;
+ uint32_t dump;
+};
+
+extern void xrtnl_open(struct rtnl_handle *rth) FAST_FUNC;
+#define rtnl_close(rth) (close((rth)->fd))
+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*) FAST_FUNC,
+ 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 void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/ll_addr.c b/ap/app/busybox/src/networking/libiproute/ll_addr.c
new file mode 100644
index 0000000..33a54ea
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ll_addr.c
@@ -0,0 +1,78 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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* FAST_FUNC 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 FAST_FUNC ll_addr_a2n(unsigned char *lladdr, int len, char *arg)
+{
+ int i;
+
+ 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;
+ }
+
+ 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/ap/app/busybox/src/networking/libiproute/ll_map.c b/ap/app/busybox/src/networking/libiproute/ll_map.c
new file mode 100644
index 0000000..27cd90f
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ll_map.c
@@ -0,0 +1,202 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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; /* treat as *idxmap[16] */
+
+static struct idxmap *find_by_index(int idx)
+{
+ struct idxmap *im;
+
+ if (idxmap)
+ for (im = idxmap[idx & 0xF]; im; im = im->next)
+ if (im->index == idx)
+ return im;
+ return NULL;
+}
+
+int FAST_FUNC 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;
+
+ if (!idxmap)
+ idxmap = xzalloc(sizeof(idxmap[0]) * 16);
+
+ 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 FAST_FUNC *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 FAST_FUNC *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 FAST_FUNC 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 FAST_FUNC xll_name_to_index(const char *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;
+ }
+ if (idxmap) {
+ 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 >= 0) {
+ struct ifreq ifr;
+ int tmp;
+
+ strncpy_IFNAMSIZ(ifr.ifr_name, name);
+ 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("can't find device '%s'", name);
+ return ret;
+}
+
+int FAST_FUNC ll_init_map(struct rtnl_handle *rth)
+{
+ xrtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK);
+ xrtnl_dump_filter(rth, ll_remember_index, NULL);
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/libiproute/ll_map.h b/ap/app/busybox/src/networking/libiproute/ll_map.h
new file mode 100644
index 0000000..c5d3834
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ll_map.h
@@ -0,0 +1,17 @@
+/* vi: set sw=4 ts=4: */
+#ifndef LL_MAP_H
+#define LL_MAP_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+int ll_remember_index(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) FAST_FUNC;
+int ll_init_map(struct rtnl_handle *rth) FAST_FUNC;
+int xll_name_to_index(const char *name) FAST_FUNC;
+const char *ll_index_to_name(int idx) FAST_FUNC;
+const char *ll_idx_n2a(int idx, char *buf) FAST_FUNC;
+/* int ll_index_to_type(int idx); */
+unsigned ll_index_to_flags(int idx) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/ll_proto.c b/ap/app/busybox/src/networking/libiproute/ll_proto.c
new file mode 100644
index 0000000..da2b53c
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ll_proto.c
@@ -0,0 +1,184 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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"
+
+#include <netinet/if_ether.h>
+
+/* Please conditionalize exotic protocols on CONFIG_something */
+
+static const uint16_t llproto_ids[] = {
+#define __PF(f,n) ETH_P_##f,
+__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,
+ETH_P_IP
+};
+#undef __PF
+
+/* Keep declarations above and below in sync! */
+
+static const char llproto_names[] =
+#define __PF(f,n) #n "\0"
+__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
+
+"802.1Q" "\0"
+"ipv4" "\0"
+;
+#undef __PF
+
+
+const char* FAST_FUNC ll_proto_n2a(unsigned short id, char *buf, int len)
+{
+ unsigned i;
+ id = ntohs(id);
+ for (i = 0; i < ARRAY_SIZE(llproto_ids); i++) {
+ if (llproto_ids[i] == id)
+ return nth_string(llproto_names, i);
+ }
+ snprintf(buf, len, "[%u]", id);
+ return buf;
+}
+
+int FAST_FUNC ll_proto_a2n(unsigned short *id, char *buf)
+{
+ unsigned i;
+ const char *name = llproto_names;
+ for (i = 0; i < ARRAY_SIZE(llproto_ids); i++) {
+ if (strcasecmp(name, buf) == 0) {
+ i = llproto_ids[i];
+ goto good;
+ }
+ name += strlen(name) + 1;
+ }
+ errno = 0;
+ i = bb_strtou(buf, NULL, 0);
+ if (errno || i > 0xffff)
+ return -1;
+ good:
+ *id = htons(i);
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/libiproute/ll_types.c b/ap/app/busybox/src/networking/libiproute/ll_types.c
new file mode 100644
index 0000000..bb42e26
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/ll_types.c
@@ -0,0 +1,204 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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> /* linux/if_arp.h needs it on some systems */
+#include <arpa/inet.h>
+#include <linux/if_arp.h>
+
+#include "libbb.h"
+#include "rt_names.h"
+
+const char* FAST_FUNC ll_type_n2a(int type, char *buf)
+{
+ 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;
+ }
+ sprintf(buf, "[%d]", type);
+ return buf;
+}
diff --git a/ap/app/busybox/src/networking/libiproute/rt_names.c b/ap/app/busybox/src/networking/libiproute/rt_names.c
new file mode 100644
index 0000000..c474ab9
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/rt_names.c
@@ -0,0 +1,256 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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"
+
+typedef struct rtnl_tab_t {
+ const char *cached_str;
+ unsigned cached_result;
+ const char *tab[256];
+} rtnl_tab_t;
+
+static void rtnl_tab_initialize(const char *file, const char **tab)
+{
+ char *token[2];
+ parser_t *parser = config_open2(file, fopen_for_read);
+
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+ unsigned id = bb_strtou(token[0], NULL, 0);
+ if (id > 256) {
+ bb_error_msg("database %s is corrupted at line %d",
+ file, parser->lineno);
+ break;
+ }
+ tab[id] = xstrdup(token[1]);
+ }
+ config_close(parser);
+}
+
+static int rtnl_a2n(rtnl_tab_t *tab, uint32_t *id, const char *arg, int base)
+{
+ unsigned i;
+
+ if (tab->cached_str && strcmp(tab->cached_str, arg) == 0) {
+ *id = tab->cached_result;
+ return 0;
+ }
+
+ for (i = 0; i < 256; i++) {
+ if (tab->tab[i]
+ && strcmp(tab->tab[i], arg) == 0
+ ) {
+ tab->cached_str = tab->tab[i];
+ tab->cached_result = i;
+ *id = i;
+ return 0;
+ }
+ }
+
+ i = bb_strtou(arg, NULL, base);
+ if (i > 255)
+ return -1;
+ *id = i;
+ return 0;
+}
+
+
+static rtnl_tab_t *rtnl_rtprot_tab;
+
+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(sizeof(*rtnl_rtprot_tab));
+ memcpy(rtnl_rtprot_tab->tab, init_tab, sizeof(init_tab));
+ rtnl_tab_initialize("/etc/iproute2/rt_protos", rtnl_rtprot_tab->tab);
+}
+
+const char* FAST_FUNC rtnl_rtprot_n2a(int id, char *buf)
+{
+ if (id < 0 || id >= 256) {
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtprot_initialize();
+
+ if (rtnl_rtprot_tab->tab[id])
+ return rtnl_rtprot_tab->tab[id];
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "%d", id);
+ return buf;
+}
+
+int FAST_FUNC rtnl_rtprot_a2n(uint32_t *id, char *arg)
+{
+ rtnl_rtprot_initialize();
+ return rtnl_a2n(rtnl_rtprot_tab, id, arg, 0);
+}
+
+
+static rtnl_tab_t *rtnl_rtscope_tab;
+
+static void rtnl_rtscope_initialize(void)
+{
+ if (rtnl_rtscope_tab)
+ return;
+ rtnl_rtscope_tab = xzalloc(sizeof(*rtnl_rtscope_tab));
+ rtnl_rtscope_tab->tab[0] = "global";
+ rtnl_rtscope_tab->tab[255] = "nowhere";
+ rtnl_rtscope_tab->tab[254] = "host";
+ rtnl_rtscope_tab->tab[253] = "link";
+ rtnl_rtscope_tab->tab[200] = "site";
+ rtnl_tab_initialize("/etc/iproute2/rt_scopes", rtnl_rtscope_tab->tab);
+}
+
+const char* FAST_FUNC rtnl_rtscope_n2a(int id, char *buf)
+{
+ if (id < 0 || id >= 256) {
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtscope_initialize();
+
+ if (rtnl_rtscope_tab->tab[id])
+ return rtnl_rtscope_tab->tab[id];
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "%d", id);
+ return buf;
+}
+
+int FAST_FUNC rtnl_rtscope_a2n(uint32_t *id, char *arg)
+{
+ rtnl_rtscope_initialize();
+ return rtnl_a2n(rtnl_rtscope_tab, id, arg, 0);
+}
+
+
+static rtnl_tab_t *rtnl_rtrealm_tab;
+
+static void rtnl_rtrealm_initialize(void)
+{
+ if (rtnl_rtrealm_tab) return;
+ rtnl_rtrealm_tab = xzalloc(sizeof(*rtnl_rtrealm_tab));
+ rtnl_rtrealm_tab->tab[0] = "unknown";
+ rtnl_tab_initialize("/etc/iproute2/rt_realms", rtnl_rtrealm_tab->tab);
+}
+
+int FAST_FUNC rtnl_rtrealm_a2n(uint32_t *id, char *arg)
+{
+ rtnl_rtrealm_initialize();
+ return rtnl_a2n(rtnl_rtrealm_tab, id, arg, 0);
+}
+
+#if ENABLE_FEATURE_IP_RULE
+const char* FAST_FUNC rtnl_rtrealm_n2a(int id, char *buf)
+{
+ if (id < 0 || id >= 256) {
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtrealm_initialize();
+
+ if (rtnl_rtrealm_tab->tab[id])
+ return rtnl_rtrealm_tab->tab[id];
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "%d", id);
+ return buf;
+}
+#endif
+
+
+static rtnl_tab_t *rtnl_rtdsfield_tab;
+
+static void rtnl_rtdsfield_initialize(void)
+{
+ if (rtnl_rtdsfield_tab) return;
+ rtnl_rtdsfield_tab = xzalloc(sizeof(*rtnl_rtdsfield_tab));
+ rtnl_rtdsfield_tab->tab[0] = "0";
+ rtnl_tab_initialize("/etc/iproute2/rt_dsfield", rtnl_rtdsfield_tab->tab);
+}
+
+const char* FAST_FUNC rtnl_dsfield_n2a(int id, char *buf)
+{
+ if (id < 0 || id >= 256) {
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtdsfield_initialize();
+
+ if (rtnl_rtdsfield_tab->tab[id])
+ return rtnl_rtdsfield_tab->tab[id];
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "0x%02x", id);
+ return buf;
+}
+
+int FAST_FUNC rtnl_dsfield_a2n(uint32_t *id, char *arg)
+{
+ rtnl_rtdsfield_initialize();
+ return rtnl_a2n(rtnl_rtdsfield_tab, id, arg, 16);
+}
+
+
+#if ENABLE_FEATURE_IP_RULE
+static rtnl_tab_t *rtnl_rttable_tab;
+
+static void rtnl_rttable_initialize(void)
+{
+ if (rtnl_rtdsfield_tab) return;
+ rtnl_rttable_tab = xzalloc(sizeof(*rtnl_rttable_tab));
+ rtnl_rttable_tab->tab[0] = "unspec";
+ rtnl_rttable_tab->tab[255] = "local";
+ rtnl_rttable_tab->tab[254] = "main";
+ rtnl_rttable_tab->tab[253] = "default";
+ rtnl_tab_initialize("/etc/iproute2/rt_tables", rtnl_rttable_tab->tab);
+}
+
+const char* FAST_FUNC rtnl_rttable_n2a(int id, char *buf)
+{
+ if (id < 0 || id >= 256) {
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+
+ rtnl_rttable_initialize();
+
+ if (rtnl_rttable_tab->tab[id])
+ return rtnl_rttable_tab->tab[id];
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "%d", id);
+ return buf;
+}
+
+int FAST_FUNC rtnl_rttable_a2n(uint32_t *id, char *arg)
+{
+ rtnl_rttable_initialize();
+ return rtnl_a2n(rtnl_rttable_tab, id, arg, 0);
+}
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/rt_names.h b/ap/app/busybox/src/networking/libiproute/rt_names.h
new file mode 100644
index 0000000..e73aa85
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/rt_names.h
@@ -0,0 +1,30 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RT_NAMES_H
+#define RT_NAMES_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* buf is SPRINT_BSIZE big */
+extern const char* rtnl_rtprot_n2a(int id, char *buf) FAST_FUNC;
+extern const char* rtnl_rtscope_n2a(int id, char *buf) FAST_FUNC;
+extern const char* rtnl_rtrealm_n2a(int id, char *buf) FAST_FUNC;
+extern const char* rtnl_dsfield_n2a(int id, char *buf) FAST_FUNC;
+extern const char* rtnl_rttable_n2a(int id, char *buf) FAST_FUNC;
+extern int rtnl_rtprot_a2n(uint32_t *id, char *arg) FAST_FUNC;
+extern int rtnl_rtscope_a2n(uint32_t *id, char *arg) FAST_FUNC;
+extern int rtnl_rtrealm_a2n(uint32_t *id, char *arg) FAST_FUNC;
+extern int rtnl_dsfield_a2n(uint32_t *id, char *arg) FAST_FUNC;
+extern int rtnl_rttable_a2n(uint32_t *id, char *arg) FAST_FUNC;
+
+extern const char* ll_type_n2a(int type, char *buf) FAST_FUNC;
+
+extern const char* ll_addr_n2a(unsigned char *addr, int alen, int type,
+ char *buf, int blen) FAST_FUNC;
+extern int ll_addr_a2n(unsigned char *lladdr, int len, char *arg) FAST_FUNC;
+
+extern const char* ll_proto_n2a(unsigned short id, char *buf, int len) FAST_FUNC;
+extern int ll_proto_a2n(unsigned short *id, char *buf) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/rtm_map.c b/ap/app/busybox/src/networking/libiproute/rtm_map.c
new file mode 100644
index 0000000..3bab53b
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/rtm_map.c
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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* FAST_FUNC rtnl_rtntype_n2a(int id, char *buf)
+{
+ 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:
+ /* buf is SPRINT_BSIZE big */
+ sprintf(buf, "%d", id);
+ return buf;
+ }
+}
+
+
+int FAST_FUNC 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 == arg || *end || res > 255)
+ return -1;
+ }
+ *id = res;
+ return 0;
+}
+
+int FAST_FUNC 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/ap/app/busybox/src/networking/libiproute/rtm_map.h b/ap/app/busybox/src/networking/libiproute/rtm_map.h
new file mode 100644
index 0000000..4377bd5
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/rtm_map.h
@@ -0,0 +1,14 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RTM_MAP_H
+#define RTM_MAP_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+const char *rtnl_rtntype_n2a(int id, char *buf) FAST_FUNC;
+int rtnl_rtntype_a2n(int *id, char *arg) FAST_FUNC;
+
+int get_rt_realms(uint32_t *realms, char *arg) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/utils.c b/ap/app/busybox/src/networking/libiproute/utils.c
new file mode 100644
index 0000000..d0fe306
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/utils.c
@@ -0,0 +1,296 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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"
+
+unsigned get_unsigned(char *arg, const char *errmsg)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (*arg) {
+ res = strtoul(arg, &ptr, 0);
+//FIXME: "" will be accepted too, is it correct?!
+ if (!*ptr && res <= UINT_MAX) {
+ return res;
+ }
+ }
+ invarg(arg, errmsg); /* does not return */
+}
+
+uint32_t get_u32(char *arg, const char *errmsg)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (*arg) {
+ res = strtoul(arg, &ptr, 0);
+//FIXME: "" will be accepted too, is it correct?!
+ if (!*ptr && res <= 0xFFFFFFFFUL) {
+ return res;
+ }
+ }
+ invarg(arg, errmsg); /* does not return */
+}
+
+uint16_t get_u16(char *arg, const char *errmsg)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (*arg) {
+ res = strtoul(arg, &ptr, 0);
+//FIXME: "" will be accepted too, is it correct?!
+ if (!*ptr && res <= 0xFFFF) {
+ return res;
+ }
+ }
+ invarg(arg, errmsg); /* does not return */
+}
+
+int get_addr_1(inet_prefix *addr, char *name, int family)
+{
+ memset(addr, 0, sizeof(*addr));
+
+ if (strcmp(name, "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;
+ }
+
+ if (family != AF_UNSPEC && family != AF_INET)
+ return -1;
+
+ /* Try to parse it as IPv4 */
+ addr->family = AF_INET;
+#if 0 /* Doesn't handle e.g. "10.10", for example, "ip r l root 10.10/16" */
+ if (inet_pton(AF_INET, name, addr->data) <= 0)
+ return -1;
+#else
+ {
+ unsigned i = 0;
+ unsigned n = 0;
+ const char *cp = name - 1;
+ while (*++cp) {
+ if ((unsigned char)(*cp - '0') <= 9) {
+ n = 10 * n + (unsigned char)(*cp - '0');
+ if (n >= 256)
+ return -1;
+ ((uint8_t*)addr->data)[i] = n;
+ continue;
+ }
+ if (*cp == '.' && ++i <= 3) {
+ n = 0;
+ continue;
+ }
+ return -1;
+ }
+ }
+#endif
+ addr->bytelen = 4;
+ addr->bitlen = -1;
+
+ return 0;
+}
+
+static void get_prefix_1(inet_prefix *dst, char *arg, int family)
+{
+ char *slash;
+
+ memset(dst, 0, sizeof(*dst));
+
+ if (strcmp(arg, "default") == 0
+ || strcmp(arg, "all") == 0
+ || strcmp(arg, "any") == 0
+ ) {
+ dst->family = family;
+ /*dst->bytelen = 0; - done by memset */
+ /*dst->bitlen = 0;*/
+ return;
+ }
+
+ slash = strchr(arg, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (get_addr_1(dst, arg, family) == 0) {
+ dst->bitlen = (dst->family == AF_INET6) ? 128 : 32;
+ if (slash) {
+ unsigned plen;
+ inet_prefix netmask_pfx;
+
+ netmask_pfx.family = AF_UNSPEC;
+ plen = bb_strtou(slash + 1, NULL, 0);
+ if ((errno || plen > dst->bitlen)
+ && get_addr_1(&netmask_pfx, slash + 1, family) != 0
+ ) {
+ goto bad;
+ }
+ 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))
+ goto bad;
+
+ for (plen = 0; mask; mask <<= 1)
+ ++plen;
+ if (plen > dst->bitlen)
+ goto bad;
+ /* dst->flags |= PREFIXLEN_SPECIFIED; */
+ }
+ dst->bitlen = plen;
+ }
+ }
+
+ if (slash)
+ *slash = '/';
+ return;
+ bad:
+ bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "prefix", arg);
+}
+
+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;
+}
+
+void 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");
+ }
+ get_prefix_1(dst, arg, family);
+}
+
+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(const inet_prefix *a, const inet_prefix *b, int bits)
+{
+ const uint32_t *a1 = a->data;
+ const uint32_t *a2 = b->data;
+ int words = bits >> 5;
+
+ 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,
+ void *addr, char *buf, int buflen)
+{
+ switch (af) {
+ case AF_INET:
+ case AF_INET6:
+ return inet_ntop(af, addr, buf, buflen);
+ default:
+ return "???";
+ }
+}
+
+#ifdef RESOLVE_HOSTNAMES
+const char *format_host(int af, int len, void *addr, char *buf, int buflen)
+{
+ 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;
+ }
+ }
+ }
+ return rt_addr_n2a(af, addr, buf, buflen);
+}
+#endif
diff --git a/ap/app/busybox/src/networking/libiproute/utils.h b/ap/app/busybox/src/networking/libiproute/utils.h
new file mode 100644
index 0000000..5fb4a86
--- /dev/null
+++ b/ap/app/busybox/src/networking/libiproute/utils.h
@@ -0,0 +1,90 @@
+/* vi: set sw=4 ts=4: */
+#ifndef UTILS_H
+#define UTILS_H 1
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "rtm_map.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+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 void get_prefix_1(inet_prefix *dst, char *arg, int family);*/
+extern int get_addr(inet_prefix *dst, char *arg, int family);
+extern void get_prefix(inet_prefix *dst, char *arg, int family);
+
+extern unsigned get_unsigned(char *arg, const char *errmsg);
+extern uint32_t get_u32(char *arg, const char *errmsg);
+extern uint16_t get_u16(char *arg, const char *errmsg);
+
+extern const char *rt_addr_n2a(int af, void *addr, char *buf, int buflen);
+#ifdef RESOLVE_HOSTNAMES
+extern const char *format_host(int af, int len, void *addr, char *buf, int buflen);
+#else
+#define format_host(af, len, addr, buf, buflen) \
+ rt_addr_n2a(af, addr, buf, buflen)
+#endif
+
+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(const inet_prefix *a, const 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);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/nameif.c b/ap/app/busybox/src/networking/nameif.c
new file mode 100644
index 0000000..5d7e8f9
--- /dev/null
+++ b/ap/app/busybox/src/networking/nameif.c
@@ -0,0 +1,324 @@
+/* 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 GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//config:config NAMEIF
+//config: bool "nameif"
+//config: default y
+//config: select PLATFORM_LINUX
+//config: select FEATURE_SYSLOG
+//config: help
+//config: nameif is used to rename network interface by its MAC address.
+//config: Renamed interfaces MUST be in the down state.
+//config: It is possible to use a file (default: /etc/mactab)
+//config: with list of new interface names and MACs.
+//config: Maximum interface name length: IFNAMSIZ = 16
+//config: File fields are separated by space or tab.
+//config: File format:
+//config: # Comment
+//config: new_interface_name XX:XX:XX:XX:XX:XX
+//config:
+//config:config FEATURE_NAMEIF_EXTENDED
+//config: bool "Extended nameif"
+//config: default y
+//config: depends on NAMEIF
+//config: help
+//config: This extends the nameif syntax to support the bus_info, driver,
+//config: phyaddr selectors. The syntax is compatible to the normal nameif.
+//config: File format:
+//config: new_interface_name driver=asix bus=usb-0000:00:08.2-3
+//config: new_interface_name bus=usb-0000:00:08.2-3 00:80:C8:38:91:B5
+//config: new_interface_name phy_address=2 00:80:C8:38:91:B5
+//config: new_interface_name mac=00:80:C8:38:91:B5
+//config: new_interface_name 00:80:C8:38:91:B5
+
+//usage:#define nameif_trivial_usage
+//usage: IF_NOT_FEATURE_NAMEIF_EXTENDED(
+//usage: "[-s] [-c FILE] [IFNAME HWADDR]..."
+//usage: )
+//usage: IF_FEATURE_NAMEIF_EXTENDED(
+//usage: "[-s] [-c FILE] [IFNAME SELECTOR]..."
+//usage: )
+//usage:#define nameif_full_usage "\n\n"
+//usage: "Rename network interface while it in the down state."
+//usage: IF_NOT_FEATURE_NAMEIF_EXTENDED(
+//usage: "\nThe device with address HWADDR is renamed to IFACE."
+//usage: )
+//usage: IF_FEATURE_NAMEIF_EXTENDED(
+//usage: "\nThe device matched by SELECTOR is renamed to IFACE."
+//usage: "\nSELECTOR can be a combination of:"
+//usage: "\n driver=STRING"
+//usage: "\n bus=STRING"
+//usage: "\n phy_address=NUM"
+//usage: "\n [mac=]XX:XX:XX:XX:XX:XX"
+//usage: )
+//usage: "\n"
+//usage: "\n -c FILE Configuration file (default: /etc/mactab)"
+//usage: "\n -s Log to syslog"
+//usage:
+//usage:#define nameif_example_usage
+//usage: "$ nameif -s dmz0 00:A0:C9:8C:F6:3F\n"
+//usage: " or\n"
+//usage: "$ nameif -c /etc/my_mactab_file\n"
+
+#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;
+ int32_t phy_address;
+#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) */
+};
+
+struct ethtool_cmd {
+ uint32_t cmd;
+ uint32_t supported; /* Features this interface supports */
+ uint32_t advertising; /* Features this interface advertises */
+ uint16_t speed; /* The forced speed, 10Mb, 100Mb, gigabit */
+ uint8_t duplex; /* Duplex, half or full */
+ uint8_t port; /* Which connector port */
+ uint8_t phy_address;
+ uint8_t transceiver; /* Which transceiver to use */
+ uint8_t autoneg; /* Enable or disable autonegotiation */
+ uint32_t maxtxpkt; /* Tx pkts before generating tx int */
+ uint32_t maxrxpkt; /* Rx pkts before generating rx int */
+ uint16_t speed_hi;
+ uint16_t reserved2;
+ uint32_t reserved[3];
+};
+
+#define ETHTOOL_GSET 0x00000001 /* Get settings. */
+#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
+ ch->phy_address = -1;
+ 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 if (strncmp(selector, "phyaddr=", 8) == 0) {
+ ch->phy_address = xatoi_positive(selector + 8);
+ found_selector++;
+ } else {
+#endif
+ lmac = xmalloc(ETH_ALEN);
+ ch->mac = ether_aton_r(selector + (strncmp(selector, "mac=", 4) != 0 ? 0 : 4), lmac);
+ if (ch->mac == NULL)
+ bb_error_msg_and_die("can't 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 UNUSED_PARAM, 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);
+ /* Why not just "="? I assume logging to stderr
+ * can't hurt. 2>/dev/null if you don't like it: */
+ logmode |= LOGMODE_SYSLOG;
+ }
+ argv += optind;
+
+ if (argv[0]) {
+ do {
+ if (!argv[1])
+ bb_show_usage();
+ prepend_new_eth_table(&clist, argv[0], argv[1]);
+ argv += 2;
+ } while (*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;
+ struct ethtool_cmd eth_settings;
+#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_IFNAMSIZ(ifr.ifr_name, token[0]);
+
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ /* Check for phy address */
+ memset(ð_settings, 0, sizeof(eth_settings));
+ eth_settings.cmd = ETHTOOL_GSET;
+ ifr.ifr_data = (caddr_t) ð_settings;
+ ioctl(ctl_sk, SIOCETHTOOL, &ifr);
+
+ /* Check for driver etc. */
+ memset(&drvinfo, 0, sizeof(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;
+ if (ch->phy_address != -1 && ch->phy_address != eth_settings.phy_address)
+ 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,
+ "can't 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/ap/app/busybox/src/networking/nbd-client.c b/ap/app/busybox/src/networking/nbd-client.c
new file mode 100644
index 0000000..cadda52
--- /dev/null
+++ b/ap/app/busybox/src/networking/nbd-client.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2010 Rob Landley <rob@landley.net>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "libbb.h"
+#include <netinet/tcp.h>
+#include <linux/fs.h>
+
+//applet:IF_NBDCLIENT(APPLET_ODDNAME(nbd-client, nbdclient, BB_DIR_USR_SBIN, BB_SUID_DROP, nbdclient))
+
+//kbuild:lib-$(CONFIG_NBDCLIENT) += nbd-client.o
+
+//config:config NBDCLIENT
+//config: bool "nbd-client"
+//config: default y
+//config: help
+//config: Network block device client
+
+#define NBD_SET_SOCK _IO(0xab, 0)
+#define NBD_SET_BLKSIZE _IO(0xab, 1)
+#define NBD_SET_SIZE _IO(0xab, 2)
+#define NBD_DO_IT _IO(0xab, 3)
+#define NBD_CLEAR_SOCK _IO(0xab, 4)
+#define NBD_CLEAR_QUEUE _IO(0xab, 5)
+#define NBD_PRINT_DEBUG _IO(0xab, 6)
+#define NBD_SET_SIZE_BLOCKS _IO(0xab, 7)
+#define NBD_DISCONNECT _IO(0xab, 8)
+#define NBD_SET_TIMEOUT _IO(0xab, 9)
+
+//usage:#define nbdclient_trivial_usage
+//usage: "HOST PORT BLOCKDEV"
+//usage:#define nbdclient_full_usage "\n\n"
+//usage: "Connect to HOST and provide a network block device on BLOCKDEV"
+
+//TODO: more compat with nbd-client version 2.9.13 -
+//Usage: nbd-client [bs=blocksize] [timeout=sec] host port nbd_device [-swap] [-persist] [-nofork]
+//Or : nbd-client -d nbd_device
+//Or : nbd-client -c nbd_device
+//Default value for blocksize is 1024 (recommended for ethernet)
+//Allowed values for blocksize are 512,1024,2048,4096
+//Note, that kernel 2.4.2 and older ones do not work correctly with
+//blocksizes other than 1024 without patches
+
+int nbdclient_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nbdclient_main(int argc, char **argv)
+{
+ unsigned long timeout = 0;
+#if BB_MMU
+ int nofork = 0;
+#endif
+ char *host, *port, *device;
+ struct nbd_header_t {
+ uint64_t magic1; // "NBDMAGIC"
+ uint64_t magic2; // 0x420281861253 big endian
+ uint64_t devsize;
+ uint32_t flags;
+ char data[124];
+ } nbd_header;
+ struct bug_check {
+ char c[offsetof(struct nbd_header_t, data) == 8+8+8+4 ? 1 : -1];
+ };
+
+ // Parse command line stuff (just a stub now)
+ if (argc != 4)
+ bb_show_usage();
+
+#if !BB_MMU
+ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+#endif
+
+ host = argv[1];
+ port = argv[2];
+ device = argv[3];
+
+ // Repeat until spanked (-persist behavior)
+ for (;;) {
+ int sock, nbd;
+ int ro;
+
+ // Make sure the /dev/nbd exists
+ nbd = xopen(device, O_RDWR);
+
+ // Find and connect to server
+ sock = create_and_connect_stream_or_die(host, xatou16(port));
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
+
+ // Log on to the server
+ xread(sock, &nbd_header, 8+8+8+4 + 124);
+ if (memcmp(&nbd_header.magic1, "NBDMAGIC""\x00\x00\x42\x02\x81\x86\x12\x53", 16) != 0)
+ bb_error_msg_and_die("login failed");
+
+ // Set 4k block size. Everything uses that these days
+ ioctl(nbd, NBD_SET_BLKSIZE, 4096);
+ ioctl(nbd, NBD_SET_SIZE_BLOCKS, SWAP_BE64(nbd_header.devsize) / 4096);
+ ioctl(nbd, NBD_CLEAR_SOCK);
+
+ // If the sucker was exported read only, respect that locally
+ ro = (nbd_header.flags & SWAP_BE32(2)) / SWAP_BE32(2);
+ if (ioctl(nbd, BLKROSET, &ro) < 0)
+ bb_perror_msg_and_die("BLKROSET");
+
+ if (timeout)
+ if (ioctl(nbd, NBD_SET_TIMEOUT, timeout))
+ bb_perror_msg_and_die("NBD_SET_TIMEOUT");
+ if (ioctl(nbd, NBD_SET_SOCK, sock))
+ bb_perror_msg_and_die("NBD_SET_SOCK");
+
+ // if (swap) mlockall(MCL_CURRENT|MCL_FUTURE);
+
+#if BB_MMU
+ // Open the device to force reread of the partition table.
+ // Need to do it in a separate process, since open(device)
+ // needs some other process to sit in ioctl(nbd, NBD_DO_IT).
+ if (fork() == 0) {
+ char *s = strrchr(device, '/');
+ sprintf(nbd_header.data, "/sys/block/%.32s/pid", s ? s + 1 : device);
+ // Is it up yet?
+ for (;;) {
+ int fd = open(nbd_header.data, O_RDONLY);
+ if (fd >= 0) {
+ //close(fd);
+ break;
+ }
+ sleep(1);
+ }
+ open(device, O_RDONLY);
+ return 0;
+ }
+
+ // Daemonize here
+ if (!nofork) {
+ daemon(0, 0);
+ nofork = 1;
+ }
+#endif
+
+ // This turns us (the process that calls this ioctl)
+ // into a dedicated NBD request handler.
+ // We block here for a long time.
+ // When exactly ioctl returns? On a signal,
+ // or if someone does ioctl(NBD_DISCONNECT) [nbd-client -d].
+ if (ioctl(nbd, NBD_DO_IT) >= 0 || errno == EBADR) {
+ // Flush queue and exit
+ ioctl(nbd, NBD_CLEAR_QUEUE);
+ ioctl(nbd, NBD_CLEAR_SOCK);
+ break;
+ }
+
+ close(sock);
+ close(nbd);
+ }
+
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/nc.c b/ap/app/busybox/src/networking/nc.c
new file mode 100644
index 0000000..0c843a6
--- /dev/null
+++ b/ap/app/busybox/src/networking/nc.c
@@ -0,0 +1,279 @@
+/* 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 source tree.
+ */
+
+#include "libbb.h"
+
+//config:config NC
+//config: bool "nc"
+//config: default y
+//config: help
+//config: A simple Unix utility which reads and writes data across network
+//config: connections.
+//config:
+//config:config NC_SERVER
+//config: bool "Netcat server options (-l)"
+//config: default y
+//config: depends on NC
+//config: help
+//config: Allow netcat to act as a server.
+//config:
+//config:config NC_EXTRA
+//config: bool "Netcat extensions (-eiw and filename)"
+//config: default y
+//config: depends on NC
+//config: help
+//config: Add -e (support for executing the rest of the command line after
+//config: making or receiving a successful connection), -i (delay interval for
+//config: lines sent), -w (timeout for initial connection).
+//config:
+//config:config NC_110_COMPAT
+//config: bool "Netcat 1.10 compatibility (+2.5k)"
+//config: default n # off specially for Rob
+//config: depends on NC
+//config: help
+//config: This option makes nc closely follow original nc-1.10.
+//config: The code is about 2.5k bigger. It enables
+//config: -s ADDR, -n, -u, -v, -o FILE, -z options, but loses
+//config: busybox-specific extensions: -f FILE and -ll.
+
+#if ENABLE_NC_110_COMPAT
+# include "nc_bloaty.c"
+#else
+
+//usage:#if !ENABLE_NC_110_COMPAT
+//usage:
+//usage:#if ENABLE_NC_SERVER || ENABLE_NC_EXTRA
+//usage:#define NC_OPTIONS_STR "\n"
+//usage:#else
+//usage:#define NC_OPTIONS_STR
+//usage:#endif
+//usage:
+//usage:#define nc_trivial_usage
+//usage: IF_NC_EXTRA("[-iN] [-wN] ")IF_NC_SERVER("[-l] [-p PORT] ")
+//usage: "["IF_NC_EXTRA("-f FILE|")"IPADDR PORT]"IF_NC_EXTRA(" [-e PROG]")
+//usage:#define nc_full_usage "\n\n"
+//usage: "Open a pipe to IP:PORT" IF_NC_EXTRA(" or FILE")
+//usage: NC_OPTIONS_STR
+//usage: IF_NC_EXTRA(
+//usage: "\n -e PROG Run PROG after connect"
+//usage: IF_NC_SERVER(
+//usage: "\n -l Listen mode, for inbound connects"
+//usage: IF_NC_EXTRA(
+//usage: "\n (use -l twice with -e for persistent server)")
+//usage: "\n -p PORT Local port"
+//usage: )
+//usage: "\n -w SEC Timeout for connect"
+//usage: "\n -i SEC Delay interval for lines sent"
+//usage: "\n -f FILE Use file (ala /dev/ttyS0) instead of network"
+//usage: )
+//usage:
+//usage:#define nc_notes_usage ""
+//usage: IF_NC_EXTRA(
+//usage: "To use netcat as a terminal emulator on a serial port:\n\n"
+//usage: "$ stty 115200 -F /dev/ttyS0\n"
+//usage: "$ stty raw -echo -ctlecho && nc -f /dev/ttyS0\n"
+//usage: )
+//usage:
+//usage:#define nc_example_usage
+//usage: "$ nc foobar.somedomain.com 25\n"
+//usage: "220 foobar ESMTP Exim 3.12 #1 Sat, 15 Apr 2000 00:03:02 -0600\n"
+//usage: "help\n"
+//usage: "214-Commands supported:\n"
+//usage: "214- HELO EHLO MAIL RCPT DATA AUTH\n"
+//usage: "214 NOOP QUIT RSET HELP\n"
+//usage: "quit\n"
+//usage: "221 foobar closing connection\n"
+//usage:
+//usage:#endif
+
+/* 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;
+ IF_NOT_NC_SERVER(const) unsigned do_listen = 0;
+ IF_NOT_NC_EXTRA (const) unsigned wsecs = 0;
+ IF_NOT_NC_EXTRA (const) unsigned delay = 0;
+ IF_NOT_NC_EXTRA (const int execparam = 0;)
+ IF_NC_EXTRA (char **execparam = NULL;)
+ 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,
+ "" IF_NC_SERVER("lp:") IF_NC_EXTRA("w:i:f:e:") )) > 0
+ ) {
+ if (ENABLE_NC_SERVER && opt == 'l')
+ IF_NC_SERVER(do_listen++);
+ else if (ENABLE_NC_SERVER && opt == 'p')
+ IF_NC_SERVER(lport = bb_lookup_port(optarg, "tcp", 0));
+ else if (ENABLE_NC_EXTRA && opt == 'w')
+ IF_NC_EXTRA( wsecs = xatou(optarg));
+ else if (ENABLE_NC_EXTRA && opt == 'i')
+ IF_NC_EXTRA( delay = xatou(optarg));
+ else if (ENABLE_NC_EXTRA && opt == 'f')
+ IF_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"). */
+ IF_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();
+ // File mode needs need zero arguments, listen mode needs zero or one,
+ // client mode needs one or two
+ if (cfd) {
+ if (argc) bb_show_usage();
+ } else if (do_listen) {
+ if (argc > 1) 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) {
+ sfd = create_and_bind_stream_or_die(argv[0], lport);
+ xlisten(sfd, do_listen); /* can be > 1 */
+#if 0 /* nc-1.10 does not do this (without -v) */
+ /* If we didn't specify a port number,
+ * query and print it after listen() */
+ if (!lport) {
+ len_and_sockaddr lsa;
+ lsa.len = LSA_SIZEOF_SA;
+ getsockname(sfd, &lsa.u.sa, &lsa.len);
+ lport = get_nport(&lsa.u.sa);
+ fdprintf(2, "%d\n", ntohs(lport));
+ }
+#endif
+ 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 signals revert to SIG_DFL on exec anyway */
+ /*signal(SIGALRM, SIG_DFL);*/
+ }
+
+ /* -e given? */
+ if (execparam) {
+ pid_t pid;
+ /* With more than one -l, repeatedly act as server */
+ if (do_listen > 1 && (pid = xvfork()) != 0) {
+ /* parent */
+ /* prevent zombies */
+ signal(SIGCHLD, SIG_IGN);
+ close(cfd);
+ goto accept_again;
+ }
+ /* child, or main thread if only one -l */
+ xmove_fd(cfd, 0);
+ xdup2(0, 1);
+ xdup2(0, 2);
+ IF_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(cfd + 1, &testfds, NULL, NULL, NULL) < 0)
+ bb_perror_msg_and_die("select");
+
+#define iobuf bb_common_bufsiz1
+ fd = STDIN_FILENO;
+ while (1) {
+ 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);
+ }
+ if (fd == cfd)
+ break;
+ fd = cfd;
+ }
+ }
+}
+#endif
diff --git a/ap/app/busybox/src/networking/nc_bloaty.c b/ap/app/busybox/src/networking/nc_bloaty.c
new file mode 100644
index 0000000..62a0251
--- /dev/null
+++ b/ap/app/busybox/src/networking/nc_bloaty.c
@@ -0,0 +1,901 @@
+/* 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 source tree.
+ */
+
+/* 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 ARGS (and options).
+ * Because of this -e option must be last.
+//TODO: remove -e incompatibility?
+ * - we don't redirect stderr to the network socket for the -e PROG.
+ * (PROG can do it itself if needed, but sometimes it is NOT wanted!)
+ * - 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 we don't exit - we continue to listen/accept.
+ */
+
+/* done in nc.c: #include "libbb.h" */
+
+//usage:#if ENABLE_NC_110_COMPAT
+//usage:
+//usage:#define nc_trivial_usage
+//usage: "[OPTIONS] HOST PORT - connect"
+//usage: IF_NC_SERVER("\n"
+//usage: "nc [OPTIONS] -l -p PORT [HOST] [PORT] - listen"
+//usage: )
+//usage:#define nc_full_usage "\n\n"
+//usage: " -e PROG Run PROG after connect (must be last)"
+//usage: IF_NC_SERVER(
+//usage: "\n -l Listen mode, for inbound connects"
+//usage: )
+//usage: "\n -p PORT Local port"
+//usage: "\n -s ADDR Local address"
+//usage: "\n -w SEC Timeout for connects and final net reads"
+//usage: IF_NC_EXTRA(
+//usage: "\n -i SEC Delay interval for lines sent" /* ", ports scanned" */
+//usage: )
+//usage: "\n -n Don't do DNS resolution"
+//usage: "\n -u UDP mode"
+//usage: "\n -v Verbose"
+//usage: IF_NC_EXTRA(
+//usage: "\n -o FILE Hex dump traffic"
+//usage: "\n -z Zero-I/O mode (scanning)"
+//usage: )
+//usage:#endif
+
+/* "\n -r Randomize local and remote ports" */
+/* "\n -g gateway Source-routing hop point[s], up to 8" */
+/* "\n -G num Source-routing pointer: 4, 8, 12, ..." */
+/* "\nport numbers can be individual or ranges: lo-hi [inclusive]" */
+
+/* -e PROG can take ARGS too: "nc ... -e ls -l", but we don't document it
+ * in help text: nc 1.10 does not allow that. We don't want to entice
+ * users to use this incompatibility */
+
+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
+ char *proggie0saved;
+ /* 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 */
+
+ 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_n = (1 << 0),
+ OPT_p = (1 << 1),
+ OPT_s = (1 << 2),
+ OPT_u = (1 << 3),
+ OPT_v = (1 << 4),
+ OPT_w = (1 << 5),
+ OPT_l = (1 << 6) * ENABLE_NC_SERVER,
+ OPT_i = (1 << (6+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+ OPT_o = (1 << (7+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+ OPT_z = (1 << (8+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_all(); 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)
+{
+ if (G.proggie0saved)
+ proggie[0] = G.proggie0saved;
+ 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 */
+ BB_EXECVP_or_die(proggie);
+}
+
+/* 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;
+ 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) {
+ int sv_port, port, r;
+
+ sv_port = get_nport(&remend.u.sa); /* save */
+ port = get_nport(&themaddr->u.sa);
+ if (port == 0) {
+ /* "nc -nl -p LPORT RHOST" (w/o RPORT!):
+ * we should accept any remote port */
+ set_nport(&remend.u.sa, 0); /* blot out remote port# */
+ }
+ r = memcmp(&remend.u.sa, &themaddr->u.sa, remend.len);
+ set_nport(&remend.u.sa, sv_port); /* restore */
+ if (r != 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. */
+ 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 && x) { /* we've got options, lessee em... */
+ *bin2hex(bigbuf_net, optbuf, 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->u.sa, 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(STDIN_FILENO);
+// Does it make sense to shutdown(net_fd, SHUT_WR)
+// to let other side know that we won't write anything anymore?
+// (and what about keeping compat if we do that?)
+ } 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 UNUSED_PARAM, char **argv)
+{
+ char *str_p, *str_s;
+ IF_NC_EXTRA(char *str_i, *str_o;)
+ char *themdotted = themdotted; /* for compiler */
+ 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;
+ proggie++;
+ goto e_found;
+ }
+ /* -<other_opts>e PROG [ARGS] ? */
+ /* (aboriginal linux uses this form) */
+ if (proggie[0][0] == '-') {
+ char *optpos = *proggie + 1;
+ /* Skip all valid opts w/o params */
+ optpos = optpos + strspn(optpos, "nuv"IF_NC_SERVER("l")IF_NC_EXTRA("z"));
+ if (*optpos == 'e' && !optpos[1]) {
+ *optpos = '\0';
+ proggie++;
+ G.proggie0saved = *proggie;
+ *proggie = NULL; /* terminate argv for getopt32 */
+ 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, "np:s:uvw:" IF_NC_SERVER("l")
+ IF_NC_EXTRA("i:o:z"),
+ &str_p, &str_s, &o_wait
+ IF_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->u.sa, htons(o_lport));
+ }
+ xmove_fd(x, netfd);
+ setsockopt_reuseaddr(netfd);
+ if (o_udpmode)
+ socket_want_pktinfo(netfd);
+ if (!ENABLE_FEATURE_UNIX_LOCAL
+ || o_listen
+ || ouraddr->u.sa.sa_family != AF_UNIX
+ ) {
+ 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_show_usage();
+
+ 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/ap/app/busybox/src/networking/netstat.c b/ap/app/busybox/src/networking/netstat.c
new file mode 100644
index 0000000..c0c6ba5
--- /dev/null
+++ b/ap/app/busybox/src/networking/netstat.c
@@ -0,0 +1,746 @@
+/* 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 source tree.
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+//usage:#define netstat_trivial_usage
+//usage: "[-"IF_ROUTE("r")"al] [-tuwx] [-en"IF_FEATURE_NETSTAT_WIDE("W")IF_FEATURE_NETSTAT_PRG("p")"]"
+//usage:#define netstat_full_usage "\n\n"
+//usage: "Display networking information\n"
+//usage: IF_ROUTE(
+//usage: "\n -r Routing table"
+//usage: )
+//usage: "\n -a All sockets"
+//usage: "\n -l Listening sockets"
+//usage: "\n Else: connected sockets"
+//usage: "\n -t TCP sockets"
+//usage: "\n -u UDP sockets"
+//usage: "\n -w Raw sockets"
+//usage: "\n -x Unix sockets"
+//usage: "\n Else: all socket types"
+//usage: "\n -e Other/more information"
+//usage: "\n -n Don't resolve names"
+//usage: IF_FEATURE_NETSTAT_WIDE(
+//usage: "\n -W Wide display"
+//usage: )
+//usage: IF_FEATURE_NETSTAT_PRG(
+//usage: "\n -p Show PID/program name for sockets"
+//usage: )
+
+#define NETSTAT_OPTS "laentuwx" \
+ IF_ROUTE( "r") \
+ IF_FEATURE_NETSTAT_WIDE("W") \
+ IF_FEATURE_NETSTAT_PRG( "p")
+
+enum {
+ 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
+ OPTBIT_x = 7,
+ IF_ROUTE( OPTBIT_ROUTE,)
+ IF_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE ,)
+ IF_FEATURE_NETSTAT_PRG( OPTBIT_PRG ,)
+ OPT_route = IF_ROUTE( (1 << OPTBIT_ROUTE)) + 0, // r
+ OPT_wide = IF_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE )) + 0, // W
+ OPT_prg = IF_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 */
+
+#define ADDR_NORMAL_WIDTH 23
+/* 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 ADDR_WIDE 51 /* INET6_ADDRSTRLEN + 5 for the port number */
+#if ENABLE_FEATURE_NETSTAT_WIDE
+# define FMT_NET_CONN_DATA "%s %6ld %6ld %-*s %-*s %-12s"
+# define FMT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-*s %-*s State %s\n"
+#else
+# define FMT_NET_CONN_DATA "%s %6ld %6ld %-23s %-23s %-12s"
+# define FMT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-23s %-23s State %s\n"
+#endif
+
+#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 {
+ smallint flags;
+#if ENABLE_FEATURE_NETSTAT_PRG
+ smallint prg_cache_loaded;
+ struct prg_node *prg_hash[PRG_HASH_SIZE];
+#endif
+#if ENABLE_FEATURE_NETSTAT_PRG
+ const char *progname_banner;
+#endif
+#if ENABLE_FEATURE_NETSTAT_WIDE
+ unsigned addr_width;
+#endif
+};
+#define G (*ptr_to_globals)
+#define flags (G.flags )
+#define prg_cache_loaded (G.prg_cache_loaded)
+#define prg_hash (G.prg_hash )
+#if ENABLE_FEATURE_NETSTAT_PRG
+# define progname_banner (G.progname_banner )
+#else
+# define progname_banner ""
+#endif
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \
+} while (0)
+
+
+#if ENABLE_FEATURE_NETSTAT_PRG
+
+/* Deliberately truncating long to unsigned *int* */
+#define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE)
+
+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_strtoul(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_strtoul(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 add_to_prg_cache_if_socket(const char *fileName,
+ struct stat *statbuf UNUSED_PARAM,
+ void *pid_slash_progname,
+ 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 *)pid_slash_progname);
+ }
+ return TRUE;
+}
+
+static int FAST_FUNC dir_act(const char *fileName,
+ struct stat *statbuf UNUSED_PARAM,
+ void *userData UNUSED_PARAM,
+ int depth)
+{
+ const char *pid;
+ char *pid_slash_progname;
+ char proc_pid_fname[sizeof("/proc/%u/cmdline") + sizeof(long)*3];
+ char cmdline_buf[512];
+ int n, len;
+
+ if (depth == 0) /* "/proc" itself */
+ return TRUE; /* continue looking one level below /proc */
+
+ pid = fileName + sizeof("/proc/")-1; /* point after "/proc/" */
+ if (!isdigit(pid[0])) /* skip /proc entries which aren't processes */
+ return SKIP;
+
+ len = snprintf(proc_pid_fname, sizeof(proc_pid_fname), "%s/cmdline", fileName);
+ n = open_read_close(proc_pid_fname, cmdline_buf, sizeof(cmdline_buf) - 1);
+ if (n < 0)
+ return FALSE;
+ cmdline_buf[n] = '\0';
+
+ /* go through all files in /proc/PID/fd and check whether they are sockets */
+ strcpy(proc_pid_fname + len - (sizeof("cmdline")-1), "fd");
+ pid_slash_progname = concat_path_file(pid, bb_basename(cmdline_buf)); /* "PID/argv0" */
+ n = recursive_action(proc_pid_fname,
+ ACTION_RECURSE | ACTION_QUIET,
+ add_to_prg_cache_if_socket,
+ NULL,
+ (void *)pid_slash_progname,
+ 0);
+ free(pid_slash_progname);
+
+ if (!n)
+ 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)
+
+#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, &localaddr->sin6_addr);
+
+ localaddr->sin6_family = AF_INET6;
+}
+#endif
+
+static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr)
+{
+ sscanf(local_addr, "%X", &localaddr->sin_addr.s_addr);
+ localaddr->sin_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;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#if ENABLE_FEATURE_IPV6
+ struct sockaddr_in6 sin6;
+#endif
+ } localaddr, remaddr;
+ unsigned long rxq, txq, inode;
+};
+
+static int scan_inet_proc_line(struct inet_params *param, char *line)
+{
+ int num;
+ /* IPv6 /proc files use 32-char hex representation
+ * of IPv6 address, followed by :PORT_IN_HEX
+ */
+ char local_addr[33], rem_addr[33]; /* 32 + 1 for NUL */
+
+ num = sscanf(line,
+ "%*d: %32[0-9A-Fa-f]:%X "
+ "%32[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.sin6);
+ build_ipv6_addr(rem_addr, ¶m->remaddr.sin6);
+#endif
+ } else {
+ build_ipv4_addr(local_addr, ¶m->localaddr.sin);
+ build_ipv4_addr(rem_addr, ¶m->remaddr.sin);
+ }
+ 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(
+ ¶m->localaddr.sa, param->local_port,
+ proto, flags & NETSTAT_NUMERIC);
+ char *r = ip_port_str(
+ ¶m->remaddr.sa, param->rem_port,
+ proto, flags & NETSTAT_NUMERIC);
+ printf(FMT_NET_CONN_DATA,
+ proto, param->rxq, param->txq,
+ IF_FEATURE_NETSTAT_WIDE(G.addr_width,) l,
+ IF_FEATURE_NETSTAT_WIDE(G.addr_width,) 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;
+
+ memset(¶m, 0, sizeof(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 NOT_NULL_ADDR(A) ( \
+ ( (A.sa.sa_family == AF_INET6) \
+ && (A.sin6.sin6_addr.s6_addr32[0] | A.sin6.sin6_addr.s6_addr32[1] | \
+ A.sin6.sin6_addr.s6_addr32[2] | A.sin6.sin6_addr.s6_addr32[3]) \
+ ) || ( \
+ (A.sa.sa_family == AF_INET) \
+ && A.sin.sin_addr.s_addr != 0 \
+ ) \
+)
+#else
+# define NOT_NULL_ADDR(A) (A.sin.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;
+
+ memset(¶m, 0, sizeof(param)); /* otherwise we display garbage IPv6 scope_ids */
+ 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 = NOT_NULL_ADDR(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 = NOT_NULL_ADDR(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)
+{
+ unsigned opt;
+
+ INIT_G();
+
+ /* Option string must match NETSTAT_xxx constants */
+ opt = getopt32(argv, NETSTAT_OPTS);
+ if (opt & OPT_sock_listen) { // -l
+ flags &= ~NETSTAT_CONNECTED;
+ flags |= NETSTAT_LISTENING;
+ }
+ if (opt & OPT_sock_all) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a
+ //if (opt & OPT_extended) // -e
+ if (opt & OPT_noresolve) flags |= NETSTAT_NUMERIC; // -n
+ //if (opt & OPT_sock_tcp) // -t: NETSTAT_TCP
+ //if (opt & OPT_sock_udp) // -u: NETSTAT_UDP
+ //if (opt & OPT_sock_raw) // -w: NETSTAT_RAW
+ //if (opt & OPT_sock_unix) // -x: NETSTAT_UNIX
+#if ENABLE_ROUTE
+ if (opt & OPT_route) { // -r
+ bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
+ return 0;
+ }
+#endif
+#if ENABLE_FEATURE_NETSTAT_WIDE
+ G.addr_width = ADDR_NORMAL_WIDTH;
+ if (opt & OPT_wide) { // -W
+ G.addr_width = ADDR_WIDE;
+ }
+#endif
+#if ENABLE_FEATURE_NETSTAT_PRG
+ progname_banner = "";
+ if (opt & OPT_prg) { // -p
+ progname_banner = PROGNAME_BANNER;
+ 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(FMT_NET_CONN_HEADER,
+ IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Local Address",
+ IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Foreign Address",
+ progname_banner
+ );
+ }
+ 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 %sPath\n", progname_banner);
+ do_info("/proc/net/unix", unix_do_one);
+ }
+ prg_cache_clear();
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/nslookup.c b/ap/app/busybox/src/networking/nslookup.c
new file mode 100644
index 0000000..b8fc8c8
--- /dev/null
+++ b/ap/app/busybox/src/networking/nslookup.c
@@ -0,0 +1,195 @@
+/* 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 source tree.
+ */
+
+//usage:#define nslookup_trivial_usage
+//usage: "[HOST] [SERVER]"
+//usage:#define nslookup_full_usage "\n\n"
+//usage: "Query the nameserver for the IP address of the given HOST\n"
+//usage: "optionally using a specified DNS server"
+//usage:
+//usage:#define nslookup_example_usage
+//usage: "$ nslookup localhost\n"
+//usage: "Server: default\n"
+//usage: "Address: default\n"
+//usage: "\n"
+//usage: "Name: debian\n"
+//usage: "Address: 127.0.0.1\n"
+
+#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 == 0) {
+ 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 && result)
+ freeaddrinfo(result);
+ return (rc != 0);
+}
+
+/* lookup the default nameserver and display it */
+static void server_print(void)
+{
+#ifndef __UC_LIBC__
+ char *server;
+ struct sockaddr *sa;
+
+#if ENABLE_FEATURE_IPV6
+ sa = (struct sockaddr*)_res._u._ext.nsaddrs[0];
+ if (!sa)
+#endif
+ sa = (struct sockaddr*)&_res.nsaddr_list[0];
+ server = xmalloc_sockaddr2dotted_noport(sa);
+
+ print_host(server, "Server:");
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(server);
+ bb_putchar('\n');
+#endif
+}
+
+/* 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(const char *server)
+{
+ len_and_sockaddr *lsa;
+
+ /* NB: this works even with, say, "[::1]:5353"! :) */
+ lsa = xhost2sockaddr(server, 53);
+
+ if (lsa->u.sa.sa_family == AF_INET) {
+ _res.nscount = 1;
+ /* struct copy */
+ _res.nsaddr_list[0] = lsa->u.sin;
+ }
+#if ENABLE_FEATURE_IPV6
+ /* Hoped libc can cope with IPv4 address there too.
+ * No such luck, glibc 2.4 segfaults even with IPv6,
+ * maybe I misunderstand how to make glibc use IPv6 addr?
+ * (uclibc 0.9.31+ should work) */
+ if (lsa->u.sa.sa_family == AF_INET6) {
+ // glibc neither SEGVs nor sends any dgrams with this
+ // (strace shows no socket ops):
+ //_res.nscount = 0;
+ _res._u._ext.nscount = 1;
+ /* store a pointer to part of malloc'ed lsa */
+ _res._u._ext.nsaddrs[0] = &lsa->u.sin6;
+ /* must not free(lsa)! */
+ }
+#endif
+}
+
+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();
+
+#ifndef __UC_LIBC__
+ /* initialize DNS structure _res used in printing the default
+ * name server and in the explicit name server option feature. */
+ res_init();
+#endif
+
+ /* 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/ap/app/busybox/src/networking/ntpd.c b/ap/app/busybox/src/networking/ntpd.c
new file mode 100644
index 0000000..df26e09
--- /dev/null
+++ b/ap/app/busybox/src/networking/ntpd.c
@@ -0,0 +1,2393 @@
+/*
+ * NTP client/server, based on OpenNTPD 3.9p1
+ *
+ * Author: Adam Tkac <vonsch@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ *
+ * Parts of OpenNTPD clock syncronization code is replaced by
+ * code which is based on ntp-4.2.6, whuch carries the following
+ * copyright notice:
+ *
+ ***********************************************************************
+ * *
+ * Copyright (c) University of Delaware 1992-2009 *
+ * *
+ * Permission to use, copy, modify, and distribute this software and *
+ * its documentation for any purpose with or without fee is hereby *
+ * granted, provided that the above copyright notice appears in all *
+ * copies and that both the copyright notice and this permission *
+ * notice appear in supporting documentation, and that the name *
+ * University of Delaware not be used in advertising or publicity *
+ * pertaining to distribution of the software without specific, *
+ * written prior permission. The University of Delaware makes no *
+ * representations about the suitability this software for any *
+ * purpose. It is provided "as is" without express or implied *
+ * warranty. *
+ * *
+ ***********************************************************************
+ */
+
+//usage:#define ntpd_trivial_usage
+//usage: "[-dnqNw"IF_FEATURE_NTPD_SERVER("l")"] [-S PROG] [-p PEER]..."
+//usage:#define ntpd_full_usage "\n\n"
+//usage: "NTP client/server\n"
+//usage: "\n -d Verbose"
+//usage: "\n -n Do not daemonize"
+//usage: "\n -q Quit after clock is set"
+//usage: "\n -N Run at high priority"
+//usage: "\n -w Do not set time (only query peers), implies -n"
+//usage: IF_FEATURE_NTPD_SERVER(
+//usage: "\n -l Run as server on port 123"
+//usage: )
+//usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 mins"
+//usage: "\n -p PEER Obtain time from PEER (may be repeated)"
+
+#include "libbb.h"
+#include <math.h>
+#include <netinet/ip.h> /* For IPTOS_LOWDELAY definition */
+#include <sys/resource.h> /* setpriority */
+#include <sys/timex.h>
+#ifndef IPTOS_LOWDELAY
+# define IPTOS_LOWDELAY 0x10
+#endif
+#ifndef IP_PKTINFO
+# error "Sorry, your kernel has to support IP_PKTINFO"
+#endif
+
+
+/* Verbosity control (max level of -dddd options accepted).
+ * max 5 is very talkative (and bloated). 2 is non-bloated,
+ * production level setting.
+ */
+#define MAX_VERBOSE 2
+
+
+/* High-level description of the algorithm:
+ *
+ * We start running with very small poll_exp, BURSTPOLL,
+ * in order to quickly accumulate INITIAL_SAMPLES datapoints
+ * for each peer. Then, time is stepped if the offset is larger
+ * than STEP_THRESHOLD, otherwise it isn't; anyway, we enlarge
+ * poll_exp to MINPOLL and enter frequency measurement step:
+ * we collect new datapoints but ignore them for WATCH_THRESHOLD
+ * seconds. After WATCH_THRESHOLD seconds we look at accumulated
+ * offset and estimate frequency drift.
+ *
+ * (frequency measurement step seems to not be strictly needed,
+ * it is conditionally disabled with USING_INITIAL_FREQ_ESTIMATION
+ * define set to 0)
+ *
+ * After this, we enter "steady state": we collect a datapoint,
+ * we select the best peer, if this datapoint is not a new one
+ * (IOW: if this datapoint isn't for selected peer), sleep
+ * and collect another one; otherwise, use its offset to update
+ * frequency drift, if offset is somewhat large, reduce poll_exp,
+ * otherwise increase poll_exp.
+ *
+ * If offset is larger than STEP_THRESHOLD, which shouldn't normally
+ * happen, we assume that something "bad" happened (computer
+ * was hibernated, someone set totally wrong date, etc),
+ * then the time is stepped, all datapoints are discarded,
+ * and we go back to steady state.
+ */
+
+#define RETRY_INTERVAL 5 /* on error, retry in N secs */
+#define RESPONSE_INTERVAL 15 /* wait for reply up to N secs */
+#define INITIAL_SAMPLES 4 /* how many samples do we want for init */
+
+/* Clock discipline parameters and constants */
+
+/* Step threshold (sec). std ntpd uses 0.128.
+ * Using exact power of 2 (1/8) results in smaller code */
+#define STEP_THRESHOLD 0.125
+#define WATCH_THRESHOLD 128 /* stepout threshold (sec). std ntpd uses 900 (11 mins (!)) */
+/* NB: set WATCH_THRESHOLD to ~60 when debugging to save time) */
+//UNUSED: #define PANIC_THRESHOLD 1000 /* panic threshold (sec) */
+
+#define FREQ_TOLERANCE 0.000015 /* frequency tolerance (15 PPM) */
+#define BURSTPOLL 0 /* initial poll */
+#define MINPOLL 5 /* minimum poll interval. std ntpd uses 6 (6: 64 sec) */
+/* If offset > discipline_jitter * POLLADJ_GATE, and poll interval is >= 2^BIGPOLL,
+ * then it is decreased _at once_. (If < 2^BIGPOLL, it will be decreased _eventually_).
+ */
+#define BIGPOLL 10 /* 2^10 sec ~= 17 min */
+#define MAXPOLL 12 /* maximum poll interval (12: 1.1h, 17: 36.4h). std ntpd uses 17 */
+/* Actively lower poll when we see such big offsets.
+ * With STEP_THRESHOLD = 0.125, it means we try to sync more aggressively
+ * if offset increases over ~0.04 sec */
+#define POLLDOWN_OFFSET (STEP_THRESHOLD / 3)
+#define MINDISP 0.01 /* minimum dispersion (sec) */
+#define MAXDISP 16 /* maximum dispersion (sec) */
+#define MAXSTRAT 16 /* maximum stratum (infinity metric) */
+#define MAXDIST 1 /* distance threshold (sec) */
+#define MIN_SELECTED 1 /* minimum intersection survivors */
+#define MIN_CLUSTERED 3 /* minimum cluster survivors */
+
+#define MAXDRIFT 0.000500 /* frequency drift we can correct (500 PPM) */
+
+/* Poll-adjust threshold.
+ * When we see that offset is small enough compared to discipline jitter,
+ * we grow a counter: += MINPOLL. When counter goes over POLLADJ_LIMIT,
+ * we poll_exp++. If offset isn't small, counter -= poll_exp*2,
+ * and when it goes below -POLLADJ_LIMIT, we poll_exp--.
+ * (Bumped from 30 to 40 since otherwise I often see poll_exp going *2* steps down)
+ */
+#define POLLADJ_LIMIT 40
+/* If offset < discipline_jitter * POLLADJ_GATE, then we decide to increase
+ * poll interval (we think we can't improve timekeeping
+ * by staying at smaller poll).
+ */
+#define POLLADJ_GATE 4
+#define TIMECONST_HACK_GATE 2
+/* Compromise Allan intercept (sec). doc uses 1500, std ntpd uses 512 */
+#define ALLAN 512
+/* PLL loop gain */
+#define PLL 65536
+/* FLL loop gain [why it depends on MAXPOLL??] */
+#define FLL (MAXPOLL + 1)
+/* Parameter averaging constant */
+#define AVG 4
+
+
+enum {
+ NTP_VERSION = 4,
+ NTP_MAXSTRATUM = 15,
+
+ NTP_DIGESTSIZE = 16,
+ NTP_MSGSIZE_NOAUTH = 48,
+ NTP_MSGSIZE = (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE),
+
+ /* Status Masks */
+ MODE_MASK = (7 << 0),
+ VERSION_MASK = (7 << 3),
+ VERSION_SHIFT = 3,
+ LI_MASK = (3 << 6),
+
+ /* Leap Second Codes (high order two bits of m_status) */
+ LI_NOWARNING = (0 << 6), /* no warning */
+ LI_PLUSSEC = (1 << 6), /* add a second (61 seconds) */
+ LI_MINUSSEC = (2 << 6), /* minus a second (59 seconds) */
+ LI_ALARM = (3 << 6), /* alarm condition */
+
+ /* Mode values */
+ MODE_RES0 = 0, /* reserved */
+ MODE_SYM_ACT = 1, /* symmetric active */
+ MODE_SYM_PAS = 2, /* symmetric passive */
+ MODE_CLIENT = 3, /* client */
+ MODE_SERVER = 4, /* server */
+ MODE_BROADCAST = 5, /* broadcast */
+ MODE_RES1 = 6, /* reserved for NTP control message */
+ MODE_RES2 = 7, /* reserved for private use */
+};
+
+//TODO: better base selection
+#define OFFSET_1900_1970 2208988800UL /* 1970 - 1900 in seconds */
+
+#define NUM_DATAPOINTS 8
+
+typedef struct {
+ uint32_t int_partl;
+ uint32_t fractionl;
+} l_fixedpt_t;
+
+typedef struct {
+ uint16_t int_parts;
+ uint16_t fractions;
+} s_fixedpt_t;
+
+typedef struct {
+ uint8_t m_status; /* status of local clock and leap info */
+ uint8_t m_stratum;
+ uint8_t m_ppoll; /* poll value */
+ int8_t m_precision_exp;
+ s_fixedpt_t m_rootdelay;
+ s_fixedpt_t m_rootdisp;
+ uint32_t m_refid;
+ l_fixedpt_t m_reftime;
+ l_fixedpt_t m_orgtime;
+ l_fixedpt_t m_rectime;
+ l_fixedpt_t m_xmttime;
+ uint32_t m_keyid;
+ uint8_t m_digest[NTP_DIGESTSIZE];
+} msg_t;
+
+typedef struct {
+ double d_offset;
+ double d_recv_time;
+ double d_dispersion;
+} datapoint_t;
+
+typedef struct {
+ len_and_sockaddr *p_lsa;
+ char *p_dotted;
+ int p_fd;
+ int datapoint_idx;
+ uint32_t lastpkt_refid;
+ uint8_t lastpkt_status;
+ uint8_t lastpkt_stratum;
+ uint8_t reachable_bits;
+ /* when to send new query (if p_fd == -1)
+ * or when receive times out (if p_fd >= 0): */
+ double next_action_time;
+ double p_xmttime;
+ double lastpkt_recv_time;
+ double lastpkt_delay;
+ double lastpkt_rootdelay;
+ double lastpkt_rootdisp;
+ /* produced by filter algorithm: */
+ double filter_offset;
+ double filter_dispersion;
+ double filter_jitter;
+ datapoint_t filter_datapoint[NUM_DATAPOINTS];
+ /* last sent packet: */
+ msg_t p_xmt_msg;
+} peer_t;
+
+
+#define USING_KERNEL_PLL_LOOP 1
+#define USING_INITIAL_FREQ_ESTIMATION 0
+
+enum {
+ OPT_n = (1 << 0),
+ OPT_q = (1 << 1),
+ OPT_N = (1 << 2),
+ OPT_x = (1 << 3),
+ /* Insert new options above this line. */
+ /* Non-compat options: */
+ OPT_w = (1 << 4),
+ OPT_p = (1 << 5),
+ OPT_S = (1 << 6),
+ OPT_l = (1 << 7) * ENABLE_FEATURE_NTPD_SERVER,
+ /* We hijack some bits for other purposes */
+ OPT_qq = (1 << 31),
+};
+
+struct globals {
+ double cur_time;
+ /* total round trip delay to currently selected reference clock */
+ double rootdelay;
+ /* reference timestamp: time when the system clock was last set or corrected */
+ double reftime;
+ /* total dispersion to currently selected reference clock */
+ double rootdisp;
+
+ double last_script_run;
+ char *script_name;
+ llist_t *ntp_peers;
+#if ENABLE_FEATURE_NTPD_SERVER
+ int listen_fd;
+# define G_listen_fd (G.listen_fd)
+#else
+# define G_listen_fd (-1)
+#endif
+ unsigned verbose;
+ unsigned peer_cnt;
+ /* refid: 32-bit code identifying the particular server or reference clock
+ * in stratum 0 packets this is a four-character ASCII string,
+ * called the kiss code, used for debugging and monitoring
+ * in stratum 1 packets this is a four-character ASCII string
+ * assigned to the reference clock by IANA. Example: "GPS "
+ * in stratum 2+ packets, it's IPv4 address or 4 first bytes
+ * of MD5 hash of IPv6
+ */
+ uint32_t refid;
+ uint8_t ntp_status;
+ /* precision is defined as the larger of the resolution and time to
+ * read the clock, in log2 units. For instance, the precision of a
+ * mains-frequency clock incrementing at 60 Hz is 16 ms, even when the
+ * system clock hardware representation is to the nanosecond.
+ *
+ * Delays, jitters of various kinds are clamped down to precision.
+ *
+ * If precision_sec is too large, discipline_jitter gets clamped to it
+ * and if offset is smaller than discipline_jitter * POLLADJ_GATE, poll
+ * interval grows even though we really can benefit from staying at
+ * smaller one, collecting non-lagged datapoits and correcting offset.
+ * (Lagged datapoits exist when poll_exp is large but we still have
+ * systematic offset error - the time distance between datapoints
+ * is significant and older datapoints have smaller offsets.
+ * This makes our offset estimation a bit smaller than reality)
+ * Due to this effect, setting G_precision_sec close to
+ * STEP_THRESHOLD isn't such a good idea - offsets may grow
+ * too big and we will step. I observed it with -6.
+ *
+ * OTOH, setting precision_sec far too small would result in futile
+ * attempts to syncronize to an unachievable precision.
+ *
+ * -6 is 1/64 sec, -7 is 1/128 sec and so on.
+ * -8 is 1/256 ~= 0.003906 (worked well for me --vda)
+ * -9 is 1/512 ~= 0.001953 (let's try this for some time)
+ */
+#define G_precision_exp -9
+ /*
+ * G_precision_exp is used only for construction outgoing packets.
+ * It's ok to set G_precision_sec to a slightly different value
+ * (One which is "nicer looking" in logs).
+ * Exact value would be (1.0 / (1 << (- G_precision_exp))):
+ */
+#define G_precision_sec 0.002
+ uint8_t stratum;
+ /* Bool. After set to 1, never goes back to 0: */
+ smallint initial_poll_complete;
+
+#define STATE_NSET 0 /* initial state, "nothing is set" */
+//#define STATE_FSET 1 /* frequency set from file */
+#define STATE_SPIK 2 /* spike detected */
+//#define STATE_FREQ 3 /* initial frequency */
+#define STATE_SYNC 4 /* clock synchronized (normal operation) */
+ uint8_t discipline_state; // doc calls it c.state
+ uint8_t poll_exp; // s.poll
+ int polladj_count; // c.count
+ long kernel_freq_drift;
+ peer_t *last_update_peer;
+ double last_update_offset; // c.last
+ double last_update_recv_time; // s.t
+ double discipline_jitter; // c.jitter
+ /* Since we only compare it with ints, can simplify code
+ * by not making this variable floating point:
+ */
+ unsigned offset_to_jitter_ratio;
+ //double cluster_offset; // s.offset
+ //double cluster_jitter; // s.jitter
+#if !USING_KERNEL_PLL_LOOP
+ double discipline_freq_drift; // c.freq
+ /* Maybe conditionally calculate wander? it's used only for logging */
+ double discipline_wander; // c.wander
+#endif
+};
+#define G (*ptr_to_globals)
+
+static const int const_IPTOS_LOWDELAY = IPTOS_LOWDELAY;
+
+
+#define VERB1 if (MAX_VERBOSE && G.verbose)
+#define VERB2 if (MAX_VERBOSE >= 2 && G.verbose >= 2)
+#define VERB3 if (MAX_VERBOSE >= 3 && G.verbose >= 3)
+#define VERB4 if (MAX_VERBOSE >= 4 && G.verbose >= 4)
+#define VERB5 if (MAX_VERBOSE >= 5 && G.verbose >= 5)
+
+
+static double LOG2D(int a)
+{
+ if (a < 0)
+ return 1.0 / (1UL << -a);
+ return 1UL << a;
+}
+static ALWAYS_INLINE double SQUARE(double x)
+{
+ return x * x;
+}
+static ALWAYS_INLINE double MAXD(double a, double b)
+{
+ if (a > b)
+ return a;
+ return b;
+}
+static ALWAYS_INLINE double MIND(double a, double b)
+{
+ if (a < b)
+ return a;
+ return b;
+}
+static NOINLINE double my_SQRT(double X)
+{
+ union {
+ float f;
+ int32_t i;
+ } v;
+ double invsqrt;
+ double Xhalf = X * 0.5;
+
+ /* Fast and good approximation to 1/sqrt(X), black magic */
+ v.f = X;
+ /*v.i = 0x5f3759df - (v.i >> 1);*/
+ v.i = 0x5f375a86 - (v.i >> 1); /* - this constant is slightly better */
+ invsqrt = v.f; /* better than 0.2% accuracy */
+
+ /* Refining it using Newton's method: x1 = x0 - f(x0)/f'(x0)
+ * f(x) = 1/(x*x) - X (f==0 when x = 1/sqrt(X))
+ * f'(x) = -2/(x*x*x)
+ * f(x)/f'(x) = (X - 1/(x*x)) / (2/(x*x*x)) = X*x*x*x/2 - x/2
+ * x1 = x0 - (X*x0*x0*x0/2 - x0/2) = 1.5*x0 - X*x0*x0*x0/2 = x0*(1.5 - (X/2)*x0*x0)
+ */
+ invsqrt = invsqrt * (1.5 - Xhalf * invsqrt * invsqrt); /* ~0.05% accuracy */
+ /* invsqrt = invsqrt * (1.5 - Xhalf * invsqrt * invsqrt); 2nd iter: ~0.0001% accuracy */
+ /* With 4 iterations, more than half results will be exact,
+ * at 6th iterations result stabilizes with about 72% results exact.
+ * We are well satisfied with 0.05% accuracy.
+ */
+
+ return X * invsqrt; /* X * 1/sqrt(X) ~= sqrt(X) */
+}
+static ALWAYS_INLINE double SQRT(double X)
+{
+ /* If this arch doesn't use IEEE 754 floats, fall back to using libm */
+ if (sizeof(float) != 4)
+ return sqrt(X);
+
+ /* This avoids needing libm, saves about 0.5k on x86-32 */
+ return my_SQRT(X);
+}
+
+static double
+gettime1900d(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL); /* never fails */
+ G.cur_time = tv.tv_sec + (1.0e-6 * tv.tv_usec) + OFFSET_1900_1970;
+ return G.cur_time;
+}
+
+static void
+d_to_tv(double d, struct timeval *tv)
+{
+ tv->tv_sec = (long)d;
+ tv->tv_usec = (d - tv->tv_sec) * 1000000;
+}
+
+static double
+lfp_to_d(l_fixedpt_t lfp)
+{
+ double ret;
+ lfp.int_partl = ntohl(lfp.int_partl);
+ lfp.fractionl = ntohl(lfp.fractionl);
+ ret = (double)lfp.int_partl + ((double)lfp.fractionl / UINT_MAX);
+ return ret;
+}
+static double
+sfp_to_d(s_fixedpt_t sfp)
+{
+ double ret;
+ sfp.int_parts = ntohs(sfp.int_parts);
+ sfp.fractions = ntohs(sfp.fractions);
+ ret = (double)sfp.int_parts + ((double)sfp.fractions / USHRT_MAX);
+ return ret;
+}
+#if ENABLE_FEATURE_NTPD_SERVER
+static l_fixedpt_t
+d_to_lfp(double d)
+{
+ l_fixedpt_t lfp;
+ lfp.int_partl = (uint32_t)d;
+ lfp.fractionl = (uint32_t)((d - lfp.int_partl) * UINT_MAX);
+ lfp.int_partl = htonl(lfp.int_partl);
+ lfp.fractionl = htonl(lfp.fractionl);
+ return lfp;
+}
+static s_fixedpt_t
+d_to_sfp(double d)
+{
+ s_fixedpt_t sfp;
+ sfp.int_parts = (uint16_t)d;
+ sfp.fractions = (uint16_t)((d - sfp.int_parts) * USHRT_MAX);
+ sfp.int_parts = htons(sfp.int_parts);
+ sfp.fractions = htons(sfp.fractions);
+ return sfp;
+}
+#endif
+
+static double
+dispersion(const datapoint_t *dp)
+{
+ return dp->d_dispersion + FREQ_TOLERANCE * (G.cur_time - dp->d_recv_time);
+}
+
+static double
+root_distance(peer_t *p)
+{
+ /* The root synchronization distance is the maximum error due to
+ * all causes of the local clock relative to the primary server.
+ * It is defined as half the total delay plus total dispersion
+ * plus peer jitter.
+ */
+ return MAXD(MINDISP, p->lastpkt_rootdelay + p->lastpkt_delay) / 2
+ + p->lastpkt_rootdisp
+ + p->filter_dispersion
+ + FREQ_TOLERANCE * (G.cur_time - p->lastpkt_recv_time)
+ + p->filter_jitter;
+}
+
+static void
+set_next(peer_t *p, unsigned t)
+{
+ p->next_action_time = G.cur_time + t;
+}
+
+/*
+ * Peer clock filter and its helpers
+ */
+static void
+filter_datapoints(peer_t *p)
+{
+ int i, idx;
+ double sum, wavg;
+ datapoint_t *fdp;
+
+#if 0
+/* Simulations have shown that use of *averaged* offset for p->filter_offset
+ * is in fact worse than simply using last received one: with large poll intervals
+ * (>= 2048) averaging code uses offset values which are outdated by hours,
+ * and time/frequency correction goes totally wrong when fed essentially bogus offsets.
+ */
+ int got_newest;
+ double minoff, maxoff, w;
+ double x = x; /* for compiler */
+ double oldest_off = oldest_off;
+ double oldest_age = oldest_age;
+ double newest_off = newest_off;
+ double newest_age = newest_age;
+
+ fdp = p->filter_datapoint;
+
+ minoff = maxoff = fdp[0].d_offset;
+ for (i = 1; i < NUM_DATAPOINTS; i++) {
+ if (minoff > fdp[i].d_offset)
+ minoff = fdp[i].d_offset;
+ if (maxoff < fdp[i].d_offset)
+ maxoff = fdp[i].d_offset;
+ }
+
+ idx = p->datapoint_idx; /* most recent datapoint's index */
+ /* Average offset:
+ * Drop two outliers and take weighted average of the rest:
+ * most_recent/2 + older1/4 + older2/8 ... + older5/32 + older6/32
+ * we use older6/32, not older6/64 since sum of weights should be 1:
+ * 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/32 = 1
+ */
+ wavg = 0;
+ w = 0.5;
+ /* n-1
+ * --- dispersion(i)
+ * filter_dispersion = \ -------------
+ * / (i+1)
+ * --- 2
+ * i=0
+ */
+ got_newest = 0;
+ sum = 0;
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ VERB4 {
+ bb_error_msg("datapoint[%d]: off:%f disp:%f(%f) age:%f%s",
+ i,
+ fdp[idx].d_offset,
+ fdp[idx].d_dispersion, dispersion(&fdp[idx]),
+ G.cur_time - fdp[idx].d_recv_time,
+ (minoff == fdp[idx].d_offset || maxoff == fdp[idx].d_offset)
+ ? " (outlier by offset)" : ""
+ );
+ }
+
+ sum += dispersion(&fdp[idx]) / (2 << i);
+
+ if (minoff == fdp[idx].d_offset) {
+ minoff -= 1; /* so that we don't match it ever again */
+ } else
+ if (maxoff == fdp[idx].d_offset) {
+ maxoff += 1;
+ } else {
+ oldest_off = fdp[idx].d_offset;
+ oldest_age = G.cur_time - fdp[idx].d_recv_time;
+ if (!got_newest) {
+ got_newest = 1;
+ newest_off = oldest_off;
+ newest_age = oldest_age;
+ }
+ x = oldest_off * w;
+ wavg += x;
+ w /= 2;
+ }
+
+ idx = (idx - 1) & (NUM_DATAPOINTS - 1);
+ }
+ p->filter_dispersion = sum;
+ wavg += x; /* add another older6/64 to form older6/32 */
+ /* Fix systematic underestimation with large poll intervals.
+ * Imagine that we still have a bit of uncorrected drift,
+ * and poll interval is big (say, 100 sec). Offsets form a progression:
+ * 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 - 0.7 is most recent.
+ * The algorithm above drops 0.0 and 0.7 as outliers,
+ * and then we have this estimation, ~25% off from 0.7:
+ * 0.1/32 + 0.2/32 + 0.3/16 + 0.4/8 + 0.5/4 + 0.6/2 = 0.503125
+ */
+ x = oldest_age - newest_age;
+ if (x != 0) {
+ x = newest_age / x; /* in above example, 100 / (600 - 100) */
+ if (x < 1) { /* paranoia check */
+ x = (newest_off - oldest_off) * x; /* 0.5 * 100/500 = 0.1 */
+ wavg += x;
+ }
+ }
+ p->filter_offset = wavg;
+
+#else
+
+ fdp = p->filter_datapoint;
+ idx = p->datapoint_idx; /* most recent datapoint's index */
+
+ /* filter_offset: simply use the most recent value */
+ p->filter_offset = fdp[idx].d_offset;
+
+ /* n-1
+ * --- dispersion(i)
+ * filter_dispersion = \ -------------
+ * / (i+1)
+ * --- 2
+ * i=0
+ */
+ wavg = 0;
+ sum = 0;
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ sum += dispersion(&fdp[idx]) / (2 << i);
+ wavg += fdp[idx].d_offset;
+ idx = (idx - 1) & (NUM_DATAPOINTS - 1);
+ }
+ wavg /= NUM_DATAPOINTS;
+ p->filter_dispersion = sum;
+#endif
+
+ /* +----- -----+ ^ 1/2
+ * | n-1 |
+ * | --- |
+ * | 1 \ 2 |
+ * filter_jitter = | --- * / (avg-offset_j) |
+ * | n --- |
+ * | j=0 |
+ * +----- -----+
+ * where n is the number of valid datapoints in the filter (n > 1);
+ * if filter_jitter < precision then filter_jitter = precision
+ */
+ sum = 0;
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ sum += SQUARE(wavg - fdp[i].d_offset);
+ }
+ sum = SQRT(sum / NUM_DATAPOINTS);
+ p->filter_jitter = sum > G_precision_sec ? sum : G_precision_sec;
+
+ VERB3 bb_error_msg("filter offset:%+f disp:%f jitter:%f",
+ p->filter_offset,
+ p->filter_dispersion,
+ p->filter_jitter);
+}
+
+static void
+reset_peer_stats(peer_t *p, double offset)
+{
+ int i;
+ bool small_ofs = fabs(offset) < 16 * STEP_THRESHOLD;
+
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ if (small_ofs) {
+ p->filter_datapoint[i].d_recv_time += offset;
+ if (p->filter_datapoint[i].d_offset != 0) {
+ p->filter_datapoint[i].d_offset -= offset;
+ //bb_error_msg("p->filter_datapoint[%d].d_offset %f -> %f",
+ // i,
+ // p->filter_datapoint[i].d_offset + offset,
+ // p->filter_datapoint[i].d_offset);
+ }
+ } else {
+ p->filter_datapoint[i].d_recv_time = G.cur_time;
+ p->filter_datapoint[i].d_offset = 0;
+ p->filter_datapoint[i].d_dispersion = MAXDISP;
+ }
+ }
+ if (small_ofs) {
+ p->lastpkt_recv_time += offset;
+ } else {
+ p->reachable_bits = 0;
+ p->lastpkt_recv_time = G.cur_time;
+ }
+ filter_datapoints(p); /* recalc p->filter_xxx */
+ VERB5 bb_error_msg("%s->lastpkt_recv_time=%f", p->p_dotted, p->lastpkt_recv_time);
+}
+
+static void
+add_peers(char *s)
+{
+ peer_t *p;
+
+ p = xzalloc(sizeof(*p));
+ p->p_lsa = xhost2sockaddr(s, 123);
+ p->p_dotted = xmalloc_sockaddr2dotted_noport(&p->p_lsa->u.sa);
+ p->p_fd = -1;
+ p->p_xmt_msg.m_status = MODE_CLIENT | (NTP_VERSION << 3);
+ p->next_action_time = G.cur_time; /* = set_next(p, 0); */
+ reset_peer_stats(p, 16 * STEP_THRESHOLD);
+
+ llist_add_to(&G.ntp_peers, p);
+ G.peer_cnt++;
+}
+
+static int
+do_sendto(int fd,
+ const struct sockaddr *from, const struct sockaddr *to, socklen_t addrlen,
+ msg_t *msg, ssize_t len)
+{
+ ssize_t ret;
+
+ errno = 0;
+ if (!from) {
+ ret = sendto(fd, msg, len, MSG_DONTWAIT, to, addrlen);
+ } else {
+ ret = send_to_from(fd, msg, len, MSG_DONTWAIT, to, from, addrlen);
+ }
+ if (ret != len) {
+ bb_perror_msg("send failed");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+send_query_to_peer(peer_t *p)
+{
+ /* Why do we need to bind()?
+ * See what happens when we don't bind:
+ *
+ * socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
+ * setsockopt(3, SOL_IP, IP_TOS, [16], 4) = 0
+ * gettimeofday({1259071266, 327885}, NULL) = 0
+ * sendto(3, "xxx", 48, MSG_DONTWAIT, {sa_family=AF_INET, sin_port=htons(123), sin_addr=inet_addr("10.34.32.125")}, 16) = 48
+ * ^^^ we sent it from some source port picked by kernel.
+ * time(NULL) = 1259071266
+ * write(2, "ntpd: entering poll 15 secs\n", 28) = 28
+ * poll([{fd=3, events=POLLIN}], 1, 15000) = 1 ([{fd=3, revents=POLLIN}])
+ * recv(3, "yyy", 68, MSG_DONTWAIT) = 48
+ * ^^^ this recv will receive packets to any local port!
+ *
+ * Uncomment this and use strace to see it in action:
+ */
+#define PROBE_LOCAL_ADDR /* { len_and_sockaddr lsa; lsa.len = LSA_SIZEOF_SA; getsockname(p->query.fd, &lsa.u.sa, &lsa.len); } */
+
+ if (p->p_fd == -1) {
+ int fd, family;
+ len_and_sockaddr *local_lsa;
+
+ family = p->p_lsa->u.sa.sa_family;
+ p->p_fd = fd = xsocket_type(&local_lsa, family, SOCK_DGRAM);
+ /* local_lsa has "null" address and port 0 now.
+ * bind() ensures we have a *particular port* selected by kernel
+ * and remembered in p->p_fd, thus later recv(p->p_fd)
+ * receives only packets sent to this port.
+ */
+ PROBE_LOCAL_ADDR
+ xbind(fd, &local_lsa->u.sa, local_lsa->len);
+ PROBE_LOCAL_ADDR
+#if ENABLE_FEATURE_IPV6
+ if (family == AF_INET)
+#endif
+ setsockopt(fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_IPTOS_LOWDELAY));
+ free(local_lsa);
+ }
+
+ /* Emit message _before_ attempted send. Think of a very short
+ * roundtrip networks: we need to go back to recv loop ASAP,
+ * to reduce delay. Printing messages after send works against that.
+ */
+ VERB1 bb_error_msg("sending query to %s", p->p_dotted);
+
+ /*
+ * Send out a random 64-bit number as our transmit time. The NTP
+ * server will copy said number into the originate field on the
+ * response that it sends us. This is totally legal per the SNTP spec.
+ *
+ * The impact of this is two fold: we no longer send out the current
+ * system time for the world to see (which may aid an attacker), and
+ * it gives us a (not very secure) way of knowing that we're not
+ * getting spoofed by an attacker that can't capture our traffic
+ * but can spoof packets from the NTP server we're communicating with.
+ *
+ * Save the real transmit timestamp locally.
+ */
+ p->p_xmt_msg.m_xmttime.int_partl = random();
+ p->p_xmt_msg.m_xmttime.fractionl = random();
+ p->p_xmttime = gettime1900d();
+
+ if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len,
+ &p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1
+ ) {
+ close(p->p_fd);
+ p->p_fd = -1;
+ set_next(p, RETRY_INTERVAL);
+ return;
+ }
+
+ p->reachable_bits <<= 1;
+ set_next(p, RESPONSE_INTERVAL);
+}
+
+
+/* Note that there is no provision to prevent several run_scripts
+ * to be done in quick succession. In fact, it happens rather often
+ * if initial syncronization results in a step.
+ * You will see "step" and then "stratum" script runs, sometimes
+ * as close as only 0.002 seconds apart.
+ * Script should be ready to deal with this.
+ */
+static void run_script(const char *action, double offset)
+{
+ char *argv[3];
+ char *env1, *env2, *env3, *env4;
+
+ if (!G.script_name)
+ return;
+
+ argv[0] = (char*) G.script_name;
+ argv[1] = (char*) action;
+ argv[2] = NULL;
+
+ VERB1 bb_error_msg("executing '%s %s'", G.script_name, action);
+
+ env1 = xasprintf("%s=%u", "stratum", G.stratum);
+ putenv(env1);
+ env2 = xasprintf("%s=%ld", "freq_drift_ppm", G.kernel_freq_drift);
+ putenv(env2);
+ env3 = xasprintf("%s=%u", "poll_interval", 1 << G.poll_exp);
+ putenv(env3);
+ env4 = xasprintf("%s=%f", "offset", offset);
+ putenv(env4);
+ /* Other items of potential interest: selected peer,
+ * rootdelay, reftime, rootdisp, refid, ntp_status,
+ * last_update_offset, last_update_recv_time, discipline_jitter,
+ * how many peers have reachable_bits = 0?
+ */
+
+ /* Don't want to wait: it may run hwclock --systohc, and that
+ * may take some time (seconds): */
+ /*spawn_and_wait(argv);*/
+ spawn(argv);
+
+ unsetenv("stratum");
+ unsetenv("freq_drift_ppm");
+ unsetenv("poll_interval");
+ unsetenv("offset");
+ free(env1);
+ free(env2);
+ free(env3);
+ free(env4);
+
+ G.last_script_run = G.cur_time;
+}
+
+static NOINLINE void
+step_time(double offset)
+{
+ llist_t *item;
+ double dtime;
+ struct timeval tvc, tvn;
+ char buf[sizeof("yyyy-mm-dd hh:mm:ss") + /*paranoia:*/ 4];
+ time_t tval;
+
+ gettimeofday(&tvc, NULL); /* never fails */
+ dtime = tvc.tv_sec + (1.0e-6 * tvc.tv_usec) + offset;
+ d_to_tv(dtime, &tvn);
+ if (settimeofday(&tvn, NULL) == -1)
+ bb_perror_msg_and_die("settimeofday");
+
+ VERB2 {
+ tval = tvc.tv_sec;
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&tval));
+ bb_error_msg("current time is %s.%06u", buf, (unsigned)tvc.tv_usec);
+ }
+ tval = tvn.tv_sec;
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&tval));
+ bb_error_msg("setting time to %s.%06u (offset %+fs)", buf, (unsigned)tvn.tv_usec, offset);
+
+ /* Correct various fields which contain time-relative values: */
+
+ /* Globals: */
+ G.cur_time += offset;
+ G.last_update_recv_time += offset;
+ G.last_script_run += offset;
+
+ /* p->lastpkt_recv_time, p->next_action_time and such: */
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *pp = (peer_t *) item->data;
+ reset_peer_stats(pp, offset);
+ //bb_error_msg("offset:%+f pp->next_action_time:%f -> %f",
+ // offset, pp->next_action_time, pp->next_action_time + offset);
+ pp->next_action_time += offset;
+ if (pp->p_fd >= 0) {
+ /* We wait for reply from this peer too.
+ * But due to step we are doing, reply's data is no longer
+ * useful (in fact, it'll be bogus). Stop waiting for it.
+ */
+ close(pp->p_fd);
+ pp->p_fd = -1;
+ set_next(pp, RETRY_INTERVAL);
+ }
+ }
+}
+
+
+/*
+ * Selection and clustering, and their helpers
+ */
+typedef struct {
+ peer_t *p;
+ int type;
+ double edge;
+ double opt_rd; /* optimization */
+} point_t;
+static int
+compare_point_edge(const void *aa, const void *bb)
+{
+ const point_t *a = aa;
+ const point_t *b = bb;
+ if (a->edge < b->edge) {
+ return -1;
+ }
+ return (a->edge > b->edge);
+}
+typedef struct {
+ peer_t *p;
+ double metric;
+} survivor_t;
+static int
+compare_survivor_metric(const void *aa, const void *bb)
+{
+ const survivor_t *a = aa;
+ const survivor_t *b = bb;
+ if (a->metric < b->metric) {
+ return -1;
+ }
+ return (a->metric > b->metric);
+}
+static int
+fit(peer_t *p, double rd)
+{
+ if ((p->reachable_bits & (p->reachable_bits-1)) == 0) {
+ /* One or zero bits in reachable_bits */
+ VERB3 bb_error_msg("peer %s unfit for selection: unreachable", p->p_dotted);
+ return 0;
+ }
+#if 0 /* we filter out such packets earlier */
+ if ((p->lastpkt_status & LI_ALARM) == LI_ALARM
+ || p->lastpkt_stratum >= MAXSTRAT
+ ) {
+ VERB3 bb_error_msg("peer %s unfit for selection: bad status/stratum", p->p_dotted);
+ return 0;
+ }
+#endif
+ /* rd is root_distance(p) */
+ if (rd > MAXDIST + FREQ_TOLERANCE * (1 << G.poll_exp)) {
+ VERB3 bb_error_msg("peer %s unfit for selection: root distance too high", p->p_dotted);
+ return 0;
+ }
+//TODO
+// /* Do we have a loop? */
+// if (p->refid == p->dstaddr || p->refid == s.refid)
+// return 0;
+ return 1;
+}
+static peer_t*
+select_and_cluster(void)
+{
+ peer_t *p;
+ llist_t *item;
+ int i, j;
+ int size = 3 * G.peer_cnt;
+ /* for selection algorithm */
+ point_t point[size];
+ unsigned num_points, num_candidates;
+ double low, high;
+ unsigned num_falsetickers;
+ /* for cluster algorithm */
+ survivor_t survivor[size];
+ unsigned num_survivors;
+
+ /* Selection */
+
+ num_points = 0;
+ item = G.ntp_peers;
+ if (G.initial_poll_complete) while (item != NULL) {
+ double rd, offset;
+
+ p = (peer_t *) item->data;
+ rd = root_distance(p);
+ offset = p->filter_offset;
+ if (!fit(p, rd)) {
+ item = item->link;
+ continue;
+ }
+
+ VERB4 bb_error_msg("interval: [%f %f %f] %s",
+ offset - rd,
+ offset,
+ offset + rd,
+ p->p_dotted
+ );
+ point[num_points].p = p;
+ point[num_points].type = -1;
+ point[num_points].edge = offset - rd;
+ point[num_points].opt_rd = rd;
+ num_points++;
+ point[num_points].p = p;
+ point[num_points].type = 0;
+ point[num_points].edge = offset;
+ point[num_points].opt_rd = rd;
+ num_points++;
+ point[num_points].p = p;
+ point[num_points].type = 1;
+ point[num_points].edge = offset + rd;
+ point[num_points].opt_rd = rd;
+ num_points++;
+ item = item->link;
+ }
+ num_candidates = num_points / 3;
+ if (num_candidates == 0) {
+ VERB3 bb_error_msg("no valid datapoints, no peer selected");
+ return NULL;
+ }
+//TODO: sorting does not seem to be done in reference code
+ qsort(point, num_points, sizeof(point[0]), compare_point_edge);
+
+ /* Start with the assumption that there are no falsetickers.
+ * Attempt to find a nonempty intersection interval containing
+ * the midpoints of all truechimers.
+ * If a nonempty interval cannot be found, increase the number
+ * of assumed falsetickers by one and try again.
+ * If a nonempty interval is found and the number of falsetickers
+ * is less than the number of truechimers, a majority has been found
+ * and the midpoint of each truechimer represents
+ * the candidates available to the cluster algorithm.
+ */
+ num_falsetickers = 0;
+ while (1) {
+ int c;
+ unsigned num_midpoints = 0;
+
+ low = 1 << 9;
+ high = - (1 << 9);
+ c = 0;
+ for (i = 0; i < num_points; i++) {
+ /* We want to do:
+ * if (point[i].type == -1) c++;
+ * if (point[i].type == 1) c--;
+ * and it's simpler to do it this way:
+ */
+ c -= point[i].type;
+ if (c >= num_candidates - num_falsetickers) {
+ /* If it was c++ and it got big enough... */
+ low = point[i].edge;
+ break;
+ }
+ if (point[i].type == 0)
+ num_midpoints++;
+ }
+ c = 0;
+ for (i = num_points-1; i >= 0; i--) {
+ c += point[i].type;
+ if (c >= num_candidates - num_falsetickers) {
+ high = point[i].edge;
+ break;
+ }
+ if (point[i].type == 0)
+ num_midpoints++;
+ }
+ /* If the number of midpoints is greater than the number
+ * of allowed falsetickers, the intersection contains at
+ * least one truechimer with no midpoint - bad.
+ * Also, interval should be nonempty.
+ */
+ if (num_midpoints <= num_falsetickers && low < high)
+ break;
+ num_falsetickers++;
+ if (num_falsetickers * 2 >= num_candidates) {
+ VERB3 bb_error_msg("too many falsetickers:%d (candidates:%d), no peer selected",
+ num_falsetickers, num_candidates);
+ return NULL;
+ }
+ }
+ VERB3 bb_error_msg("selected interval: [%f, %f]; candidates:%d falsetickers:%d",
+ low, high, num_candidates, num_falsetickers);
+
+ /* Clustering */
+
+ /* Construct a list of survivors (p, metric)
+ * from the chime list, where metric is dominated
+ * first by stratum and then by root distance.
+ * All other things being equal, this is the order of preference.
+ */
+ num_survivors = 0;
+ for (i = 0; i < num_points; i++) {
+ if (point[i].edge < low || point[i].edge > high)
+ continue;
+ p = point[i].p;
+ survivor[num_survivors].p = p;
+ /* x.opt_rd == root_distance(p); */
+ survivor[num_survivors].metric = MAXDIST * p->lastpkt_stratum + point[i].opt_rd;
+ VERB4 bb_error_msg("survivor[%d] metric:%f peer:%s",
+ num_survivors, survivor[num_survivors].metric, p->p_dotted);
+ num_survivors++;
+ }
+ /* There must be at least MIN_SELECTED survivors to satisfy the
+ * correctness assertions. Ordinarily, the Byzantine criteria
+ * require four survivors, but for the demonstration here, one
+ * is acceptable.
+ */
+ if (num_survivors < MIN_SELECTED) {
+ VERB3 bb_error_msg("num_survivors %d < %d, no peer selected",
+ num_survivors, MIN_SELECTED);
+ return NULL;
+ }
+
+//looks like this is ONLY used by the fact that later we pick survivor[0].
+//we can avoid sorting then, just find the minimum once!
+ qsort(survivor, num_survivors, sizeof(survivor[0]), compare_survivor_metric);
+
+ /* For each association p in turn, calculate the selection
+ * jitter p->sjitter as the square root of the sum of squares
+ * (p->offset - q->offset) over all q associations. The idea is
+ * to repeatedly discard the survivor with maximum selection
+ * jitter until a termination condition is met.
+ */
+ while (1) {
+ unsigned max_idx = max_idx;
+ double max_selection_jitter = max_selection_jitter;
+ double min_jitter = min_jitter;
+
+ if (num_survivors <= MIN_CLUSTERED) {
+ VERB3 bb_error_msg("num_survivors %d <= %d, not discarding more",
+ num_survivors, MIN_CLUSTERED);
+ break;
+ }
+
+ /* To make sure a few survivors are left
+ * for the clustering algorithm to chew on,
+ * we stop if the number of survivors
+ * is less than or equal to MIN_CLUSTERED (3).
+ */
+ for (i = 0; i < num_survivors; i++) {
+ double selection_jitter_sq;
+
+ p = survivor[i].p;
+ if (i == 0 || p->filter_jitter < min_jitter)
+ min_jitter = p->filter_jitter;
+
+ selection_jitter_sq = 0;
+ for (j = 0; j < num_survivors; j++) {
+ peer_t *q = survivor[j].p;
+ selection_jitter_sq += SQUARE(p->filter_offset - q->filter_offset);
+ }
+ if (i == 0 || selection_jitter_sq > max_selection_jitter) {
+ max_selection_jitter = selection_jitter_sq;
+ max_idx = i;
+ }
+ VERB5 bb_error_msg("survivor %d selection_jitter^2:%f",
+ i, selection_jitter_sq);
+ }
+ max_selection_jitter = SQRT(max_selection_jitter / num_survivors);
+ VERB4 bb_error_msg("max_selection_jitter (at %d):%f min_jitter:%f",
+ max_idx, max_selection_jitter, min_jitter);
+
+ /* If the maximum selection jitter is less than the
+ * minimum peer jitter, then tossing out more survivors
+ * will not lower the minimum peer jitter, so we might
+ * as well stop.
+ */
+ if (max_selection_jitter < min_jitter) {
+ VERB3 bb_error_msg("max_selection_jitter:%f < min_jitter:%f, num_survivors:%d, not discarding more",
+ max_selection_jitter, min_jitter, num_survivors);
+ break;
+ }
+
+ /* Delete survivor[max_idx] from the list
+ * and go around again.
+ */
+ VERB5 bb_error_msg("dropping survivor %d", max_idx);
+ num_survivors--;
+ while (max_idx < num_survivors) {
+ survivor[max_idx] = survivor[max_idx + 1];
+ max_idx++;
+ }
+ }
+
+ if (0) {
+ /* Combine the offsets of the clustering algorithm survivors
+ * using a weighted average with weight determined by the root
+ * distance. Compute the selection jitter as the weighted RMS
+ * difference between the first survivor and the remaining
+ * survivors. In some cases the inherent clock jitter can be
+ * reduced by not using this algorithm, especially when frequent
+ * clockhopping is involved. bbox: thus we don't do it.
+ */
+ double x, y, z, w;
+ y = z = w = 0;
+ for (i = 0; i < num_survivors; i++) {
+ p = survivor[i].p;
+ x = root_distance(p);
+ y += 1 / x;
+ z += p->filter_offset / x;
+ w += SQUARE(p->filter_offset - survivor[0].p->filter_offset) / x;
+ }
+ //G.cluster_offset = z / y;
+ //G.cluster_jitter = SQRT(w / y);
+ }
+
+ /* Pick the best clock. If the old system peer is on the list
+ * and at the same stratum as the first survivor on the list,
+ * then don't do a clock hop. Otherwise, select the first
+ * survivor on the list as the new system peer.
+ */
+ p = survivor[0].p;
+ if (G.last_update_peer
+ && G.last_update_peer->lastpkt_stratum <= p->lastpkt_stratum
+ ) {
+ /* Starting from 1 is ok here */
+ for (i = 1; i < num_survivors; i++) {
+ if (G.last_update_peer == survivor[i].p) {
+ VERB4 bb_error_msg("keeping old synced peer");
+ p = G.last_update_peer;
+ goto keep_old;
+ }
+ }
+ }
+ G.last_update_peer = p;
+ keep_old:
+ VERB3 bb_error_msg("selected peer %s filter_offset:%+f age:%f",
+ p->p_dotted,
+ p->filter_offset,
+ G.cur_time - p->lastpkt_recv_time
+ );
+ return p;
+}
+
+
+/*
+ * Local clock discipline and its helpers
+ */
+static void
+set_new_values(int disc_state, double offset, double recv_time)
+{
+ /* Enter new state and set state variables. Note we use the time
+ * of the last clock filter sample, which must be earlier than
+ * the current time.
+ */
+ VERB3 bb_error_msg("disc_state=%d last update offset=%f recv_time=%f",
+ disc_state, offset, recv_time);
+ G.discipline_state = disc_state;
+ G.last_update_offset = offset;
+ G.last_update_recv_time = recv_time;
+}
+/* Return: -1: decrease poll interval, 0: leave as is, 1: increase */
+static NOINLINE int
+update_local_clock(peer_t *p)
+{
+ int rc;
+ struct timex tmx;
+ /* Note: can use G.cluster_offset instead: */
+ double offset = p->filter_offset;
+ double recv_time = p->lastpkt_recv_time;
+ double abs_offset;
+#if !USING_KERNEL_PLL_LOOP
+ double freq_drift;
+#endif
+ double since_last_update;
+ double etemp, dtemp;
+
+ abs_offset = fabs(offset);
+
+#if 0
+ /* If needed, -S script can do it by looking at $offset
+ * env var and killing parent */
+ /* If the offset is too large, give up and go home */
+ if (abs_offset > PANIC_THRESHOLD) {
+ bb_error_msg_and_die("offset %f far too big, exiting", offset);
+ }
+#endif
+
+ /* If this is an old update, for instance as the result
+ * of a system peer change, avoid it. We never use
+ * an old sample or the same sample twice.
+ */
+ if (recv_time <= G.last_update_recv_time) {
+ VERB3 bb_error_msg("same or older datapoint: %f >= %f, not using it",
+ G.last_update_recv_time, recv_time);
+ return 0; /* "leave poll interval as is" */
+ }
+
+ /* Clock state machine transition function. This is where the
+ * action is and defines how the system reacts to large time
+ * and frequency errors.
+ */
+ since_last_update = recv_time - G.reftime;
+#if !USING_KERNEL_PLL_LOOP
+ freq_drift = 0;
+#endif
+#if USING_INITIAL_FREQ_ESTIMATION
+ if (G.discipline_state == STATE_FREQ) {
+ /* Ignore updates until the stepout threshold */
+ if (since_last_update < WATCH_THRESHOLD) {
+ VERB3 bb_error_msg("measuring drift, datapoint ignored, %f sec remains",
+ WATCH_THRESHOLD - since_last_update);
+ return 0; /* "leave poll interval as is" */
+ }
+# if !USING_KERNEL_PLL_LOOP
+ freq_drift = (offset - G.last_update_offset) / since_last_update;
+# endif
+ }
+#endif
+
+ /* There are two main regimes: when the
+ * offset exceeds the step threshold and when it does not.
+ */
+ if (abs_offset > STEP_THRESHOLD) {
+ switch (G.discipline_state) {
+ case STATE_SYNC:
+ /* The first outlyer: ignore it, switch to SPIK state */
+ VERB3 bb_error_msg("offset:%+f - spike detected", offset);
+ G.discipline_state = STATE_SPIK;
+ return -1; /* "decrease poll interval" */
+
+ case STATE_SPIK:
+ /* Ignore succeeding outlyers until either an inlyer
+ * is found or the stepout threshold is exceeded.
+ */
+ if (since_last_update < WATCH_THRESHOLD) {
+ VERB3 bb_error_msg("spike detected, datapoint ignored, %f sec remains",
+ WATCH_THRESHOLD - since_last_update);
+ return -1; /* "decrease poll interval" */
+ }
+ /* fall through: we need to step */
+ } /* switch */
+
+ /* Step the time and clamp down the poll interval.
+ *
+ * In NSET state an initial frequency correction is
+ * not available, usually because the frequency file has
+ * not yet been written. Since the time is outside the
+ * capture range, the clock is stepped. The frequency
+ * will be set directly following the stepout interval.
+ *
+ * In FSET state the initial frequency has been set
+ * from the frequency file. Since the time is outside
+ * the capture range, the clock is stepped immediately,
+ * rather than after the stepout interval. Guys get
+ * nervous if it takes 17 minutes to set the clock for
+ * the first time.
+ *
+ * In SPIK state the stepout threshold has expired and
+ * the phase is still above the step threshold. Note
+ * that a single spike greater than the step threshold
+ * is always suppressed, even at the longer poll
+ * intervals.
+ */
+ VERB3 bb_error_msg("stepping time by %+f; poll_exp=MINPOLL", offset);
+ step_time(offset);
+ if (option_mask32 & OPT_q) {
+ /* We were only asked to set time once. Done. */
+ exit(0);
+ }
+
+ G.polladj_count = 0;
+ G.poll_exp = MINPOLL;
+ G.stratum = MAXSTRAT;
+
+ run_script("step", offset);
+
+#if USING_INITIAL_FREQ_ESTIMATION
+ if (G.discipline_state == STATE_NSET) {
+ set_new_values(STATE_FREQ, /*offset:*/ 0, recv_time);
+ return 1; /* "ok to increase poll interval" */
+ }
+#endif
+ abs_offset = offset = 0;
+ set_new_values(STATE_SYNC, offset, recv_time);
+
+ } else { /* abs_offset <= STEP_THRESHOLD */
+
+ if (G.poll_exp < MINPOLL && G.initial_poll_complete) {
+ VERB3 bb_error_msg("small offset:%+f, disabling burst mode", offset);
+ G.polladj_count = 0;
+ G.poll_exp = MINPOLL;
+ }
+
+ /* Compute the clock jitter as the RMS of exponentially
+ * weighted offset differences. Used by the poll adjust code.
+ */
+ etemp = SQUARE(G.discipline_jitter);
+ dtemp = SQUARE(offset - G.last_update_offset);
+ G.discipline_jitter = SQRT(etemp + (dtemp - etemp) / AVG);
+
+ switch (G.discipline_state) {
+ case STATE_NSET:
+ if (option_mask32 & OPT_q) {
+ /* We were only asked to set time once.
+ * The clock is precise enough, no need to step.
+ */
+ exit(0);
+ }
+#if USING_INITIAL_FREQ_ESTIMATION
+ /* This is the first update received and the frequency
+ * has not been initialized. The first thing to do
+ * is directly measure the oscillator frequency.
+ */
+ set_new_values(STATE_FREQ, offset, recv_time);
+#else
+ set_new_values(STATE_SYNC, offset, recv_time);
+#endif
+ VERB3 bb_error_msg("transitioning to FREQ, datapoint ignored");
+ return 0; /* "leave poll interval as is" */
+
+#if 0 /* this is dead code for now */
+ case STATE_FSET:
+ /* This is the first update and the frequency
+ * has been initialized. Adjust the phase, but
+ * don't adjust the frequency until the next update.
+ */
+ set_new_values(STATE_SYNC, offset, recv_time);
+ /* freq_drift remains 0 */
+ break;
+#endif
+
+#if USING_INITIAL_FREQ_ESTIMATION
+ case STATE_FREQ:
+ /* since_last_update >= WATCH_THRESHOLD, we waited enough.
+ * Correct the phase and frequency and switch to SYNC state.
+ * freq_drift was already estimated (see code above)
+ */
+ set_new_values(STATE_SYNC, offset, recv_time);
+ break;
+#endif
+
+ default:
+#if !USING_KERNEL_PLL_LOOP
+ /* Compute freq_drift due to PLL and FLL contributions.
+ *
+ * The FLL and PLL frequency gain constants
+ * depend on the poll interval and Allan
+ * intercept. The FLL is not used below one-half
+ * the Allan intercept. Above that the loop gain
+ * increases in steps to 1 / AVG.
+ */
+ if ((1 << G.poll_exp) > ALLAN / 2) {
+ etemp = FLL - G.poll_exp;
+ if (etemp < AVG)
+ etemp = AVG;
+ freq_drift += (offset - G.last_update_offset) / (MAXD(since_last_update, ALLAN) * etemp);
+ }
+ /* For the PLL the integration interval
+ * (numerator) is the minimum of the update
+ * interval and poll interval. This allows
+ * oversampling, but not undersampling.
+ */
+ etemp = MIND(since_last_update, (1 << G.poll_exp));
+ dtemp = (4 * PLL) << G.poll_exp;
+ freq_drift += offset * etemp / SQUARE(dtemp);
+#endif
+ set_new_values(STATE_SYNC, offset, recv_time);
+ break;
+ }
+ if (G.stratum != p->lastpkt_stratum + 1) {
+ G.stratum = p->lastpkt_stratum + 1;
+ run_script("stratum", offset);
+ }
+ }
+
+ if (G.discipline_jitter < G_precision_sec)
+ G.discipline_jitter = G_precision_sec;
+ G.offset_to_jitter_ratio = abs_offset / G.discipline_jitter;
+
+ G.reftime = G.cur_time;
+ G.ntp_status = p->lastpkt_status;
+ G.refid = p->lastpkt_refid;
+ G.rootdelay = p->lastpkt_rootdelay + p->lastpkt_delay;
+ dtemp = p->filter_jitter; // SQRT(SQUARE(p->filter_jitter) + SQUARE(G.cluster_jitter));
+ dtemp += MAXD(p->filter_dispersion + FREQ_TOLERANCE * (G.cur_time - p->lastpkt_recv_time) + abs_offset, MINDISP);
+ G.rootdisp = p->lastpkt_rootdisp + dtemp;
+ VERB3 bb_error_msg("updating leap/refid/reftime/rootdisp from peer %s", p->p_dotted);
+
+ /* We are in STATE_SYNC now, but did not do adjtimex yet.
+ * (Any other state does not reach this, they all return earlier)
+ * By this time, freq_drift and offset are set
+ * to values suitable for adjtimex.
+ */
+#if !USING_KERNEL_PLL_LOOP
+ /* Calculate the new frequency drift and frequency stability (wander).
+ * Compute the clock wander as the RMS of exponentially weighted
+ * frequency differences. This is not used directly, but can,
+ * along with the jitter, be a highly useful monitoring and
+ * debugging tool.
+ */
+ dtemp = G.discipline_freq_drift + freq_drift;
+ G.discipline_freq_drift = MAXD(MIND(MAXDRIFT, dtemp), -MAXDRIFT);
+ etemp = SQUARE(G.discipline_wander);
+ dtemp = SQUARE(dtemp);
+ G.discipline_wander = SQRT(etemp + (dtemp - etemp) / AVG);
+
+ VERB3 bb_error_msg("discipline freq_drift=%.9f(int:%ld corr:%e) wander=%f",
+ G.discipline_freq_drift,
+ (long)(G.discipline_freq_drift * 65536e6),
+ freq_drift,
+ G.discipline_wander);
+#endif
+ VERB3 {
+ memset(&tmx, 0, sizeof(tmx));
+ if (adjtimex(&tmx) < 0)
+ bb_perror_msg_and_die("adjtimex");
+ bb_error_msg("p adjtimex freq:%ld offset:%+ld status:0x%x tc:%ld",
+ tmx.freq, tmx.offset, tmx.status, tmx.constant);
+ }
+
+ memset(&tmx, 0, sizeof(tmx));
+#if 0
+//doesn't work, offset remains 0 (!) in kernel:
+//ntpd: set adjtimex freq:1786097 tmx.offset:77487
+//ntpd: prev adjtimex freq:1786097 tmx.offset:0
+//ntpd: cur adjtimex freq:1786097 tmx.offset:0
+ tmx.modes = ADJ_FREQUENCY | ADJ_OFFSET;
+ /* 65536 is one ppm */
+ tmx.freq = G.discipline_freq_drift * 65536e6;
+#endif
+ tmx.modes = ADJ_OFFSET | ADJ_STATUS | ADJ_TIMECONST;// | ADJ_MAXERROR | ADJ_ESTERROR;
+ tmx.offset = (offset * 1000000); /* usec */
+ tmx.status = STA_PLL;
+ if (G.ntp_status & LI_PLUSSEC)
+ tmx.status |= STA_INS;
+ if (G.ntp_status & LI_MINUSSEC)
+ tmx.status |= STA_DEL;
+
+ tmx.constant = G.poll_exp - 4;
+ /* EXPERIMENTAL.
+ * The below if statement should be unnecessary, but...
+ * It looks like Linux kernel's PLL is far too gentle in changing
+ * tmx.freq in response to clock offset. Offset keeps growing
+ * and eventually we fall back to smaller poll intervals.
+ * We can make correction more agressive (about x2) by supplying
+ * PLL time constant which is one less than the real one.
+ * To be on a safe side, let's do it only if offset is significantly
+ * larger than jitter.
+ */
+ if (tmx.constant > 0 && G.offset_to_jitter_ratio >= TIMECONST_HACK_GATE)
+ tmx.constant--;
+
+ //tmx.esterror = (uint32_t)(clock_jitter * 1e6);
+ //tmx.maxerror = (uint32_t)((sys_rootdelay / 2 + sys_rootdisp) * 1e6);
+ rc = adjtimex(&tmx);
+ if (rc < 0)
+ bb_perror_msg_and_die("adjtimex");
+ /* NB: here kernel returns constant == G.poll_exp, not == G.poll_exp - 4.
+ * Not sure why. Perhaps it is normal.
+ */
+ VERB3 bb_error_msg("adjtimex:%d freq:%ld offset:%+ld status:0x%x",
+ rc, tmx.freq, tmx.offset, tmx.status);
+ G.kernel_freq_drift = tmx.freq / 65536;
+ VERB2 bb_error_msg("update from:%s offset:%+f jitter:%f clock drift:%+.3fppm tc:%d",
+ p->p_dotted, offset, G.discipline_jitter, (double)tmx.freq / 65536, (int)tmx.constant);
+
+ return 1; /* "ok to increase poll interval" */
+}
+
+
+/*
+ * We've got a new reply packet from a peer, process it
+ * (helpers first)
+ */
+static unsigned
+retry_interval(void)
+{
+ /* Local problem, want to retry soon */
+ unsigned interval, r;
+ interval = RETRY_INTERVAL;
+ r = random();
+ interval += r % (unsigned)(RETRY_INTERVAL / 4);
+ VERB3 bb_error_msg("chose retry interval:%u", interval);
+ return interval;
+}
+static unsigned
+poll_interval(int exponent)
+{
+ unsigned interval, r;
+ exponent = G.poll_exp + exponent;
+ if (exponent < 0)
+ exponent = 0;
+ interval = 1 << exponent;
+ r = random();
+ interval += ((r & (interval-1)) >> 4) + ((r >> 8) & 1); /* + 1/16 of interval, max */
+ VERB3 bb_error_msg("chose poll interval:%u (poll_exp:%d exp:%d)", interval, G.poll_exp, exponent);
+ return interval;
+}
+static NOINLINE void
+recv_and_process_peer_pkt(peer_t *p)
+{
+ int rc;
+ ssize_t size;
+ msg_t msg;
+ double T1, T2, T3, T4;
+ unsigned interval;
+ datapoint_t *datapoint;
+ peer_t *q;
+
+ /* We can recvfrom here and check from.IP, but some multihomed
+ * ntp servers reply from their *other IP*.
+ * TODO: maybe we should check at least what we can: from.port == 123?
+ */
+ size = recv(p->p_fd, &msg, sizeof(msg), MSG_DONTWAIT);
+ if (size == -1) {
+ bb_perror_msg("recv(%s) error", p->p_dotted);
+ if (errno == EHOSTUNREACH || errno == EHOSTDOWN
+ || errno == ENETUNREACH || errno == ENETDOWN
+ || errno == ECONNREFUSED || errno == EADDRNOTAVAIL
+ || errno == EAGAIN
+ ) {
+//TODO: always do this?
+ interval = retry_interval();
+ goto set_next_and_ret;
+ }
+ xfunc_die();
+ }
+
+ if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) {
+ bb_error_msg("malformed packet received from %s", p->p_dotted);
+ return;
+ }
+
+ if (msg.m_orgtime.int_partl != p->p_xmt_msg.m_xmttime.int_partl
+ || msg.m_orgtime.fractionl != p->p_xmt_msg.m_xmttime.fractionl
+ ) {
+ /* Somebody else's packet */
+ return;
+ }
+
+ /* We do not expect any more packets from this peer for now.
+ * Closing the socket informs kernel about it.
+ * We open a new socket when we send a new query.
+ */
+ close(p->p_fd);
+ p->p_fd = -1;
+
+ if ((msg.m_status & LI_ALARM) == LI_ALARM
+ || msg.m_stratum == 0
+ || msg.m_stratum > NTP_MAXSTRATUM
+ ) {
+// TODO: stratum 0 responses may have commands in 32-bit m_refid field:
+// "DENY", "RSTR" - peer does not like us at all
+// "RATE" - peer is overloaded, reduce polling freq
+ interval = poll_interval(0);
+ bb_error_msg("reply from %s: peer is unsynced, next query in %us", p->p_dotted, interval);
+ goto set_next_and_ret;
+ }
+
+// /* Verify valid root distance */
+// if (msg.m_rootdelay / 2 + msg.m_rootdisp >= MAXDISP || p->lastpkt_reftime > msg.m_xmt)
+// return; /* invalid header values */
+
+ p->lastpkt_status = msg.m_status;
+ p->lastpkt_stratum = msg.m_stratum;
+ p->lastpkt_rootdelay = sfp_to_d(msg.m_rootdelay);
+ p->lastpkt_rootdisp = sfp_to_d(msg.m_rootdisp);
+ p->lastpkt_refid = msg.m_refid;
+
+ /*
+ * From RFC 2030 (with a correction to the delay math):
+ *
+ * Timestamp Name ID When Generated
+ * ------------------------------------------------------------
+ * Originate Timestamp T1 time request sent by client
+ * Receive Timestamp T2 time request received by server
+ * Transmit Timestamp T3 time reply sent by server
+ * Destination Timestamp T4 time reply received by client
+ *
+ * The roundtrip delay and local clock offset are defined as
+ *
+ * delay = (T4 - T1) - (T3 - T2); offset = ((T2 - T1) + (T3 - T4)) / 2
+ */
+ T1 = p->p_xmttime;
+ T2 = lfp_to_d(msg.m_rectime);
+ T3 = lfp_to_d(msg.m_xmttime);
+ T4 = G.cur_time;
+
+ p->lastpkt_recv_time = T4;
+
+ VERB5 bb_error_msg("%s->lastpkt_recv_time=%f", p->p_dotted, p->lastpkt_recv_time);
+ p->datapoint_idx = p->reachable_bits ? (p->datapoint_idx + 1) % NUM_DATAPOINTS : 0;
+ datapoint = &p->filter_datapoint[p->datapoint_idx];
+ datapoint->d_recv_time = T4;
+ datapoint->d_offset = ((T2 - T1) + (T3 - T4)) / 2;
+ /* The delay calculation is a special case. In cases where the
+ * server and client clocks are running at different rates and
+ * with very fast networks, the delay can appear negative. In
+ * order to avoid violating the Principle of Least Astonishment,
+ * the delay is clamped not less than the system precision.
+ */
+ p->lastpkt_delay = (T4 - T1) - (T3 - T2);
+ if (p->lastpkt_delay < G_precision_sec)
+ p->lastpkt_delay = G_precision_sec;
+ datapoint->d_dispersion = LOG2D(msg.m_precision_exp) + G_precision_sec;
+ if (!p->reachable_bits) {
+ /* 1st datapoint ever - replicate offset in every element */
+ int i;
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ p->filter_datapoint[i].d_offset = datapoint->d_offset;
+ }
+ }
+
+ p->reachable_bits |= 1;
+ if ((MAX_VERBOSE && G.verbose) || (option_mask32 & OPT_w)) {
+ bb_error_msg("reply from %s: offset:%+f delay:%f status:0x%02x strat:%d refid:0x%08x rootdelay:%f reach:0x%02x",
+ p->p_dotted,
+ datapoint->d_offset,
+ p->lastpkt_delay,
+ p->lastpkt_status,
+ p->lastpkt_stratum,
+ p->lastpkt_refid,
+ p->lastpkt_rootdelay,
+ p->reachable_bits
+ /* not shown: m_ppoll, m_precision_exp, m_rootdisp,
+ * m_reftime, m_orgtime, m_rectime, m_xmttime
+ */
+ );
+ }
+
+ /* Muck with statictics and update the clock */
+ filter_datapoints(p);
+ q = select_and_cluster();
+ rc = -1;
+ if (q) {
+ rc = 0;
+ if (!(option_mask32 & OPT_w)) {
+ rc = update_local_clock(q);
+ /* If drift is dangerously large, immediately
+ * drop poll interval one step down.
+ */
+ if (fabs(q->filter_offset) >= POLLDOWN_OFFSET) {
+ VERB3 bb_error_msg("offset:%+f > POLLDOWN_OFFSET", q->filter_offset);
+ goto poll_down;
+ }
+ }
+ }
+ /* else: no peer selected, rc = -1: we want to poll more often */
+
+ if (rc != 0) {
+ /* Adjust the poll interval by comparing the current offset
+ * with the clock jitter. If the offset is less than
+ * the clock jitter times a constant, then the averaging interval
+ * is increased, otherwise it is decreased. A bit of hysteresis
+ * helps calm the dance. Works best using burst mode.
+ */
+ if (rc > 0 && G.offset_to_jitter_ratio <= POLLADJ_GATE) {
+ /* was += G.poll_exp but it is a bit
+ * too optimistic for my taste at high poll_exp's */
+ G.polladj_count += MINPOLL;
+ if (G.polladj_count > POLLADJ_LIMIT) {
+ G.polladj_count = 0;
+ if (G.poll_exp < MAXPOLL) {
+ G.poll_exp++;
+ VERB3 bb_error_msg("polladj: discipline_jitter:%f ++poll_exp=%d",
+ G.discipline_jitter, G.poll_exp);
+ }
+ } else {
+ VERB3 bb_error_msg("polladj: incr:%d", G.polladj_count);
+ }
+ } else {
+ G.polladj_count -= G.poll_exp * 2;
+ if (G.polladj_count < -POLLADJ_LIMIT || G.poll_exp >= BIGPOLL) {
+ poll_down:
+ G.polladj_count = 0;
+ if (G.poll_exp > MINPOLL) {
+ llist_t *item;
+
+ G.poll_exp--;
+ /* Correct p->next_action_time in each peer
+ * which waits for sending, so that they send earlier.
+ * Old pp->next_action_time are on the order
+ * of t + (1 << old_poll_exp) + small_random,
+ * we simply need to subtract ~half of that.
+ */
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *pp = (peer_t *) item->data;
+ if (pp->p_fd < 0)
+ pp->next_action_time -= (1 << G.poll_exp);
+ }
+ VERB3 bb_error_msg("polladj: discipline_jitter:%f --poll_exp=%d",
+ G.discipline_jitter, G.poll_exp);
+ }
+ } else {
+ VERB3 bb_error_msg("polladj: decr:%d", G.polladj_count);
+ }
+ }
+ }
+
+ /* Decide when to send new query for this peer */
+ interval = poll_interval(0);
+
+ set_next_and_ret:
+ set_next(p, interval);
+}
+
+#if ENABLE_FEATURE_NTPD_SERVER
+static NOINLINE void
+recv_and_process_client_pkt(void /*int fd*/)
+{
+ ssize_t size;
+ //uint8_t version;
+ len_and_sockaddr *to;
+ struct sockaddr *from;
+ msg_t msg;
+ uint8_t query_status;
+ l_fixedpt_t query_xmttime;
+
+ to = get_sock_lsa(G_listen_fd);
+ from = xzalloc(to->len);
+
+ size = recv_from_to(G_listen_fd, &msg, sizeof(msg), MSG_DONTWAIT, from, &to->u.sa, to->len);
+ if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) {
+ char *addr;
+ if (size < 0) {
+ if (errno == EAGAIN)
+ goto bail;
+ bb_perror_msg_and_die("recv");
+ }
+ addr = xmalloc_sockaddr2dotted_noport(from);
+ bb_error_msg("malformed packet received from %s: size %u", addr, (int)size);
+ free(addr);
+ goto bail;
+ }
+
+ /*modify for HUB CVE-2016-6301*/
+ /* Respond only to client and symmetric active packets */
+ if ((msg.m_status & MODE_MASK) != MODE_CLIENT
+ && (msg.m_status & MODE_MASK) != MODE_SYM_ACT) {
+ goto bail;
+ }
+ query_status = msg.m_status;
+ query_xmttime = msg.m_xmttime;
+
+ /* Build a reply packet */
+ memset(&msg, 0, sizeof(msg));
+ msg.m_status = G.stratum < MAXSTRAT ? (G.ntp_status & LI_MASK) : LI_ALARM;
+ msg.m_status |= (query_status & VERSION_MASK);
+ msg.m_status |= ((query_status & MODE_MASK) == MODE_CLIENT) ?
+ MODE_SERVER : MODE_SYM_PAS;
+ msg.m_stratum = G.stratum;
+ msg.m_ppoll = G.poll_exp;
+ msg.m_precision_exp = G_precision_exp;
+ /* this time was obtained between poll() and recv() */
+ msg.m_rectime = d_to_lfp(G.cur_time);
+ msg.m_xmttime = d_to_lfp(gettime1900d()); /* this instant */
+ if (G.peer_cnt == 0) {
+ /* we have no peers: "stratum 1 server" mode. reftime = our own time */
+ G.reftime = G.cur_time;
+ }
+ msg.m_reftime = d_to_lfp(G.reftime);
+ msg.m_orgtime = query_xmttime;
+ msg.m_rootdelay = d_to_sfp(G.rootdelay);
+//simple code does not do this, fix simple code!
+ msg.m_rootdisp = d_to_sfp(G.rootdisp);
+ //version = (query_status & VERSION_MASK); /* ... >> VERSION_SHIFT - done below instead */
+ msg.m_refid = G.refid; // (version > (3 << VERSION_SHIFT)) ? G.refid : G.refid3;
+
+ /* We reply from the local address packet was sent to,
+ * this makes to/from look swapped here: */
+ do_sendto(G_listen_fd,
+ /*from:*/ &to->u.sa, /*to:*/ from, /*addrlen:*/ to->len,
+ &msg, size);
+
+ bail:
+ free(to);
+ free(from);
+}
+#endif
+
+/* Upstream ntpd's options:
+ *
+ * -4 Force DNS resolution of host names to the IPv4 namespace.
+ * -6 Force DNS resolution of host names to the IPv6 namespace.
+ * -a Require cryptographic authentication for broadcast client,
+ * multicast client and symmetric passive associations.
+ * This is the default.
+ * -A Do not require cryptographic authentication for broadcast client,
+ * multicast client and symmetric passive associations.
+ * This is almost never a good idea.
+ * -b Enable the client to synchronize to broadcast servers.
+ * -c conffile
+ * Specify the name and path of the configuration file,
+ * default /etc/ntp.conf
+ * -d Specify debugging mode. This option may occur more than once,
+ * with each occurrence indicating greater detail of display.
+ * -D level
+ * Specify debugging level directly.
+ * -f driftfile
+ * Specify the name and path of the frequency file.
+ * This is the same operation as the "driftfile FILE"
+ * configuration command.
+ * -g Normally, ntpd exits with a message to the system log
+ * if the offset exceeds the panic threshold, which is 1000 s
+ * by default. This option allows the time to be set to any value
+ * without restriction; however, this can happen only once.
+ * If the threshold is exceeded after that, ntpd will exit
+ * with a message to the system log. This option can be used
+ * with the -q and -x options. See the tinker command for other options.
+ * -i jaildir
+ * Chroot the server to the directory jaildir. This option also implies
+ * that the server attempts to drop root privileges at startup
+ * (otherwise, chroot gives very little additional security).
+ * You may need to also specify a -u option.
+ * -k keyfile
+ * Specify the name and path of the symmetric key file,
+ * default /etc/ntp/keys. This is the same operation
+ * as the "keys FILE" configuration command.
+ * -l logfile
+ * Specify the name and path of the log file. The default
+ * is the system log file. This is the same operation as
+ * the "logfile FILE" configuration command.
+ * -L Do not listen to virtual IPs. The default is to listen.
+ * -n Don't fork.
+ * -N To the extent permitted by the operating system,
+ * run the ntpd at the highest priority.
+ * -p pidfile
+ * Specify the name and path of the file used to record the ntpd
+ * process ID. This is the same operation as the "pidfile FILE"
+ * configuration command.
+ * -P priority
+ * To the extent permitted by the operating system,
+ * run the ntpd at the specified priority.
+ * -q Exit the ntpd just after the first time the clock is set.
+ * This behavior mimics that of the ntpdate program, which is
+ * to be retired. The -g and -x options can be used with this option.
+ * Note: The kernel time discipline is disabled with this option.
+ * -r broadcastdelay
+ * Specify the default propagation delay from the broadcast/multicast
+ * server to this client. This is necessary only if the delay
+ * cannot be computed automatically by the protocol.
+ * -s statsdir
+ * Specify the directory path for files created by the statistics
+ * facility. This is the same operation as the "statsdir DIR"
+ * configuration command.
+ * -t key
+ * Add a key number to the trusted key list. This option can occur
+ * more than once.
+ * -u user[:group]
+ * Specify a user, and optionally a group, to switch to.
+ * -v variable
+ * -V variable
+ * Add a system variable listed by default.
+ * -x Normally, the time is slewed if the offset is less than the step
+ * threshold, which is 128 ms by default, and stepped if above
+ * the threshold. This option sets the threshold to 600 s, which is
+ * well within the accuracy window to set the clock manually.
+ * Note: since the slew rate of typical Unix kernels is limited
+ * to 0.5 ms/s, each second of adjustment requires an amortization
+ * interval of 2000 s. Thus, an adjustment as much as 600 s
+ * will take almost 14 days to complete. This option can be used
+ * with the -g and -q options. See the tinker command for other options.
+ * Note: The kernel time discipline is disabled with this option.
+ */
+
+/* By doing init in a separate function we decrease stack usage
+ * in main loop.
+ */
+static NOINLINE void ntp_init(char **argv)
+{
+ unsigned opts;
+ llist_t *peers;
+
+ srandom(getpid());
+
+ if (getuid())
+ bb_error_msg_and_die(bb_msg_you_must_be_root);
+
+ /* Set some globals */
+ G.stratum = MAXSTRAT;
+ if (BURSTPOLL != 0)
+ G.poll_exp = BURSTPOLL; /* speeds up initial sync */
+ G.last_script_run = G.reftime = G.last_update_recv_time = gettime1900d(); /* sets G.cur_time too */
+
+ /* Parse options */
+ peers = NULL;
+ opt_complementary = "dd:p::wn"; /* d: counter; p: list; -w implies -n */
+ opts = getopt32(argv,
+ "nqNx" /* compat */
+ "wp:S:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */
+ "d" /* compat */
+ "46aAbgL", /* compat, ignored */
+ &peers, &G.script_name, &G.verbose);
+ if (!(opts & (OPT_p|OPT_l)))
+ bb_show_usage();
+// if (opts & OPT_x) /* disable stepping, only slew is allowed */
+// G.time_was_stepped = 1;
+ if (peers) {
+ while (peers)
+ add_peers(llist_pop(&peers));
+ } else {
+ /* -l but no peers: "stratum 1 server" mode */
+ G.stratum = 1;
+ }
+ if (!(opts & OPT_n)) {
+ bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO, argv);
+ logmode = LOGMODE_NONE;
+ }
+#if ENABLE_FEATURE_NTPD_SERVER
+ G_listen_fd = -1;
+ if (opts & OPT_l) {
+ G_listen_fd = create_and_bind_dgram_or_die(NULL, 123);
+ socket_want_pktinfo(G_listen_fd);
+ setsockopt(G_listen_fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_IPTOS_LOWDELAY));
+ }
+#endif
+ /* I hesitate to set -20 prio. -15 should be high enough for timekeeping */
+ if (opts & OPT_N)
+ setpriority(PRIO_PROCESS, 0, -15);
+
+ /* If network is up, syncronization occurs in ~10 seconds.
+ * We give "ntpd -q" 10 seconds to get first reply,
+ * then another 50 seconds to finish syncing.
+ *
+ * I tested ntpd 4.2.6p1 and apparently it never exits
+ * (will try forever), but it does not feel right.
+ * The goal of -q is to act like ntpdate: set time
+ * after a reasonably small period of polling, or fail.
+ */
+ if (opts & OPT_q) {
+ option_mask32 |= OPT_qq;
+ alarm(10);
+ }
+
+ bb_signals(0
+ | (1 << SIGTERM)
+ | (1 << SIGINT)
+ | (1 << SIGALRM)
+ , record_signo
+ );
+ bb_signals(0
+ | (1 << SIGPIPE)
+ | (1 << SIGCHLD)
+ , SIG_IGN
+ );
+}
+
+int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ntpd_main(int argc UNUSED_PARAM, char **argv)
+{
+#undef G
+ struct globals G;
+ struct pollfd *pfd;
+ peer_t **idx2peer;
+ unsigned cnt;
+
+ memset(&G, 0, sizeof(G));
+ SET_PTR_TO_GLOBALS(&G);
+
+ ntp_init(argv);
+
+ /* If ENABLE_FEATURE_NTPD_SERVER, + 1 for listen_fd: */
+ cnt = G.peer_cnt + ENABLE_FEATURE_NTPD_SERVER;
+ idx2peer = xzalloc(sizeof(idx2peer[0]) * cnt);
+ pfd = xzalloc(sizeof(pfd[0]) * cnt);
+
+ /* Countdown: we never sync before we sent INITIAL_SAMPLES+1
+ * packets to each peer.
+ * NB: if some peer is not responding, we may end up sending
+ * fewer packets to it and more to other peers.
+ * NB2: sync usually happens using INITIAL_SAMPLES packets,
+ * since last reply does not come back instantaneously.
+ */
+ cnt = G.peer_cnt * (INITIAL_SAMPLES + 1);
+
+ write_pidfile(CONFIG_PID_FILE_PATH "/ntpd.pid");
+
+ while (!bb_got_signal) {
+ llist_t *item;
+ unsigned i, j;
+ int nfds, timeout;
+ double nextaction;
+
+ /* Nothing between here and poll() blocks for any significant time */
+
+ nextaction = G.cur_time + 3600;
+
+ i = 0;
+#if ENABLE_FEATURE_NTPD_SERVER
+ if (G_listen_fd != -1) {
+ pfd[0].fd = G_listen_fd;
+ pfd[0].events = POLLIN;
+ i++;
+ }
+#endif
+ /* Pass over peer list, send requests, time out on receives */
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *p = (peer_t *) item->data;
+
+ if (p->next_action_time <= G.cur_time) {
+ if (p->p_fd == -1) {
+ /* Time to send new req */
+ if (--cnt == 0) {
+ G.initial_poll_complete = 1;
+ }
+ send_query_to_peer(p);
+ } else {
+ /* Timed out waiting for reply */
+ close(p->p_fd);
+ p->p_fd = -1;
+ timeout = poll_interval(-2); /* -2: try a bit sooner */
+ bb_error_msg("timed out waiting for %s, reach 0x%02x, next query in %us",
+ p->p_dotted, p->reachable_bits, timeout);
+ set_next(p, timeout);
+ }
+ }
+
+ if (p->next_action_time < nextaction)
+ nextaction = p->next_action_time;
+
+ if (p->p_fd >= 0) {
+ /* Wait for reply from this peer */
+ pfd[i].fd = p->p_fd;
+ pfd[i].events = POLLIN;
+ idx2peer[i] = p;
+ i++;
+ }
+ }
+
+ timeout = nextaction - G.cur_time;
+ if (timeout < 0)
+ timeout = 0;
+ timeout++; /* (nextaction - G.cur_time) rounds down, compensating */
+
+ /* Here we may block */
+ VERB2 {
+ if (i > (ENABLE_FEATURE_NTPD_SERVER && G_listen_fd != -1)) {
+ /* We wait for at least one reply.
+ * Poll for it, without wasting time for message.
+ * Since replies often come under 1 second, this also
+ * reduces clutter in logs.
+ */
+ nfds = poll(pfd, i, 1000);
+ if (nfds != 0)
+ goto did_poll;
+ if (--timeout <= 0)
+ goto did_poll;
+ }
+ bb_error_msg("poll:%us sockets:%u interval:%us", timeout, i, 1 << G.poll_exp);
+ }
+ nfds = poll(pfd, i, timeout * 1000);
+ did_poll:
+ gettime1900d(); /* sets G.cur_time */
+ if (nfds <= 0) {
+ if (G.script_name && G.cur_time - G.last_script_run > 11*60) {
+ /* Useful for updating battery-backed RTC and such */
+ run_script("periodic", G.last_update_offset);
+ gettime1900d(); /* sets G.cur_time */
+ }
+ continue;
+ }
+
+ /* Process any received packets */
+ j = 0;
+#if ENABLE_FEATURE_NTPD_SERVER
+ if (G.listen_fd != -1) {
+ if (pfd[0].revents /* & (POLLIN|POLLERR)*/) {
+ nfds--;
+ recv_and_process_client_pkt(/*G.listen_fd*/);
+ gettime1900d(); /* sets G.cur_time */
+ }
+ j = 1;
+ }
+#endif
+ for (; nfds != 0 && j < i; j++) {
+ if (pfd[j].revents /* & (POLLIN|POLLERR)*/) {
+ /*
+ * At init, alarm was set to 10 sec.
+ * Now we did get a reply.
+ * Increase timeout to 50 seconds to finish syncing.
+ */
+ if (option_mask32 & OPT_qq) {
+ option_mask32 &= ~OPT_qq;
+ alarm(50);
+ }
+ nfds--;
+ recv_and_process_peer_pkt(idx2peer[j]);
+ gettime1900d(); /* sets G.cur_time */
+ }
+ }
+ } /* while (!bb_got_signal) */
+
+ remove_pidfile(CONFIG_PID_FILE_PATH "/ntpd.pid");
+ kill_myself_with_sig(bb_got_signal);
+}
+
+
+
+
+
+
+/*** openntpd-4.6 uses only adjtime, not adjtimex ***/
+
+/*** ntp-4.2.6/ntpd/ntp_loopfilter.c - adjtimex usage ***/
+
+#if 0
+static double
+direct_freq(double fp_offset)
+{
+#ifdef KERNEL_PLL
+ /*
+ * If the kernel is enabled, we need the residual offset to
+ * calculate the frequency correction.
+ */
+ if (pll_control && kern_enable) {
+ memset(&ntv, 0, sizeof(ntv));
+ ntp_adjtime(&ntv);
+#ifdef STA_NANO
+ clock_offset = ntv.offset / 1e9;
+#else /* STA_NANO */
+ clock_offset = ntv.offset / 1e6;
+#endif /* STA_NANO */
+ drift_comp = FREQTOD(ntv.freq);
+ }
+#endif /* KERNEL_PLL */
+ set_freq((fp_offset - clock_offset) / (current_time - clock_epoch) + drift_comp);
+ wander_resid = 0;
+ return drift_comp;
+}
+
+static void
+set_freq(double freq) /* frequency update */
+{
+ char tbuf[80];
+
+ drift_comp = freq;
+
+#ifdef KERNEL_PLL
+ /*
+ * If the kernel is enabled, update the kernel frequency.
+ */
+ if (pll_control && kern_enable) {
+ memset(&ntv, 0, sizeof(ntv));
+ ntv.modes = MOD_FREQUENCY;
+ ntv.freq = DTOFREQ(drift_comp);
+ ntp_adjtime(&ntv);
+ snprintf(tbuf, sizeof(tbuf), "kernel %.3f PPM", drift_comp * 1e6);
+ report_event(EVNT_FSET, NULL, tbuf);
+ } else {
+ snprintf(tbuf, sizeof(tbuf), "ntpd %.3f PPM", drift_comp * 1e6);
+ report_event(EVNT_FSET, NULL, tbuf);
+ }
+#else /* KERNEL_PLL */
+ snprintf(tbuf, sizeof(tbuf), "ntpd %.3f PPM", drift_comp * 1e6);
+ report_event(EVNT_FSET, NULL, tbuf);
+#endif /* KERNEL_PLL */
+}
+
+...
+...
+...
+
+#ifdef KERNEL_PLL
+ /*
+ * This code segment works when clock adjustments are made using
+ * precision time kernel support and the ntp_adjtime() system
+ * call. This support is available in Solaris 2.6 and later,
+ * Digital Unix 4.0 and later, FreeBSD, Linux and specially
+ * modified kernels for HP-UX 9 and Ultrix 4. In the case of the
+ * DECstation 5000/240 and Alpha AXP, additional kernel
+ * modifications provide a true microsecond clock and nanosecond
+ * clock, respectively.
+ *
+ * Important note: The kernel discipline is used only if the
+ * step threshold is less than 0.5 s, as anything higher can
+ * lead to overflow problems. This might occur if some misguided
+ * lad set the step threshold to something ridiculous.
+ */
+ if (pll_control && kern_enable) {
+
+#define MOD_BITS (MOD_OFFSET | MOD_MAXERROR | MOD_ESTERROR | MOD_STATUS | MOD_TIMECONST)
+
+ /*
+ * We initialize the structure for the ntp_adjtime()
+ * system call. We have to convert everything to
+ * microseconds or nanoseconds first. Do not update the
+ * system variables if the ext_enable flag is set. In
+ * this case, the external clock driver will update the
+ * variables, which will be read later by the local
+ * clock driver. Afterwards, remember the time and
+ * frequency offsets for jitter and stability values and
+ * to update the frequency file.
+ */
+ memset(&ntv, 0, sizeof(ntv));
+ if (ext_enable) {
+ ntv.modes = MOD_STATUS;
+ } else {
+#ifdef STA_NANO
+ ntv.modes = MOD_BITS | MOD_NANO;
+#else /* STA_NANO */
+ ntv.modes = MOD_BITS;
+#endif /* STA_NANO */
+ if (clock_offset < 0)
+ dtemp = -.5;
+ else
+ dtemp = .5;
+#ifdef STA_NANO
+ ntv.offset = (int32)(clock_offset * 1e9 + dtemp);
+ ntv.constant = sys_poll;
+#else /* STA_NANO */
+ ntv.offset = (int32)(clock_offset * 1e6 + dtemp);
+ ntv.constant = sys_poll - 4;
+#endif /* STA_NANO */
+ ntv.esterror = (u_int32)(clock_jitter * 1e6);
+ ntv.maxerror = (u_int32)((sys_rootdelay / 2 + sys_rootdisp) * 1e6);
+ ntv.status = STA_PLL;
+
+ /*
+ * Enable/disable the PPS if requested.
+ */
+ if (pps_enable) {
+ if (!(pll_status & STA_PPSTIME))
+ report_event(EVNT_KERN,
+ NULL, "PPS enabled");
+ ntv.status |= STA_PPSTIME | STA_PPSFREQ;
+ } else {
+ if (pll_status & STA_PPSTIME)
+ report_event(EVNT_KERN,
+ NULL, "PPS disabled");
+ ntv.status &= ~(STA_PPSTIME | STA_PPSFREQ);
+ }
+ if (sys_leap == LEAP_ADDSECOND)
+ ntv.status |= STA_INS;
+ else if (sys_leap == LEAP_DELSECOND)
+ ntv.status |= STA_DEL;
+ }
+
+ /*
+ * Pass the stuff to the kernel. If it squeals, turn off
+ * the pps. In any case, fetch the kernel offset,
+ * frequency and jitter.
+ */
+ if (ntp_adjtime(&ntv) == TIME_ERROR) {
+ if (!(ntv.status & STA_PPSSIGNAL))
+ report_event(EVNT_KERN, NULL,
+ "PPS no signal");
+ }
+ pll_status = ntv.status;
+#ifdef STA_NANO
+ clock_offset = ntv.offset / 1e9;
+#else /* STA_NANO */
+ clock_offset = ntv.offset / 1e6;
+#endif /* STA_NANO */
+ clock_frequency = FREQTOD(ntv.freq);
+
+ /*
+ * If the kernel PPS is lit, monitor its performance.
+ */
+ if (ntv.status & STA_PPSTIME) {
+#ifdef STA_NANO
+ clock_jitter = ntv.jitter / 1e9;
+#else /* STA_NANO */
+ clock_jitter = ntv.jitter / 1e6;
+#endif /* STA_NANO */
+ }
+
+#if defined(STA_NANO) && NTP_API == 4
+ /*
+ * If the TAI changes, update the kernel TAI.
+ */
+ if (loop_tai != sys_tai) {
+ loop_tai = sys_tai;
+ ntv.modes = MOD_TAI;
+ ntv.constant = sys_tai;
+ ntp_adjtime(&ntv);
+ }
+#endif /* STA_NANO */
+ }
+#endif /* KERNEL_PLL */
+#endif
diff --git a/ap/app/busybox/src/networking/ntpd_simple.c b/ap/app/busybox/src/networking/ntpd_simple.c
new file mode 100644
index 0000000..55bded8
--- /dev/null
+++ b/ap/app/busybox/src/networking/ntpd_simple.c
@@ -0,0 +1,1008 @@
+/*
+ * NTP client/server, based on OpenNTPD 3.9p1
+ *
+ * Author: Adam Tkac <vonsch@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "libbb.h"
+#include <netinet/ip.h> /* For IPTOS_LOWDELAY definition */
+#include <sys/resource.h> /* setpriority */
+#ifndef IPTOS_LOWDELAY
+# define IPTOS_LOWDELAY 0x10
+#endif
+#ifndef IP_PKTINFO
+# error "Sorry, your kernel has to support IP_PKTINFO"
+#endif
+
+
+/* Sync to peers every N secs */
+#define INTERVAL_QUERY_NORMAL 30
+#define INTERVAL_QUERY_PATHETIC 60
+#define INTERVAL_QUERY_AGRESSIVE 5
+
+/* Bad if *less than* TRUSTLEVEL_BADPEER */
+#define TRUSTLEVEL_BADPEER 6
+#define TRUSTLEVEL_PATHETIC 2
+#define TRUSTLEVEL_AGRESSIVE 8
+#define TRUSTLEVEL_MAX 10
+
+#define QSCALE_OFF_MIN 0.05
+#define QSCALE_OFF_MAX 0.50
+
+/* Single query might take N secs max */
+#define QUERYTIME_MAX 15
+/* Min offset for settime at start. "man ntpd" says it's 128 ms */
+#define STEPTIME_MIN_OFFSET 0.128
+
+typedef struct {
+ uint32_t int_partl;
+ uint32_t fractionl;
+} l_fixedpt_t;
+
+typedef struct {
+ uint16_t int_parts;
+ uint16_t fractions;
+} s_fixedpt_t;
+
+enum {
+ NTP_DIGESTSIZE = 16,
+ NTP_MSGSIZE_NOAUTH = 48,
+ NTP_MSGSIZE = (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE),
+};
+
+typedef struct {
+ uint8_t m_status; /* status of local clock and leap info */
+ uint8_t m_stratum; /* stratum level */
+ uint8_t m_ppoll; /* poll value */
+ int8_t m_precision_exp;
+ s_fixedpt_t m_rootdelay;
+ s_fixedpt_t m_dispersion;
+ uint32_t m_refid;
+ l_fixedpt_t m_reftime;
+ l_fixedpt_t m_orgtime;
+ l_fixedpt_t m_rectime;
+ l_fixedpt_t m_xmttime;
+ uint32_t m_keyid;
+ uint8_t m_digest[NTP_DIGESTSIZE];
+} msg_t;
+
+enum {
+ NTP_VERSION = 4,
+ NTP_MAXSTRATUM = 15,
+
+ /* Status Masks */
+ MODE_MASK = (7 << 0),
+ VERSION_MASK = (7 << 3),
+ VERSION_SHIFT = 3,
+ LI_MASK = (3 << 6),
+
+ /* Leap Second Codes (high order two bits of m_status) */
+ LI_NOWARNING = (0 << 6), /* no warning */
+ LI_PLUSSEC = (1 << 6), /* add a second (61 seconds) */
+ LI_MINUSSEC = (2 << 6), /* minus a second (59 seconds) */
+ LI_ALARM = (3 << 6), /* alarm condition */
+
+ /* Mode values */
+ MODE_RES0 = 0, /* reserved */
+ MODE_SYM_ACT = 1, /* symmetric active */
+ MODE_SYM_PAS = 2, /* symmetric passive */
+ MODE_CLIENT = 3, /* client */
+ MODE_SERVER = 4, /* server */
+ MODE_BROADCAST = 5, /* broadcast */
+ MODE_RES1 = 6, /* reserved for NTP control message */
+ MODE_RES2 = 7, /* reserved for private use */
+};
+
+#define OFFSET_1900_1970 2208988800UL /* 1970 - 1900 in seconds */
+
+typedef struct {
+ double d_offset;
+ double d_delay;
+ //UNUSED: double d_error;
+ time_t d_rcv_time;
+ uint32_t d_refid4;
+ uint8_t d_leap;
+ uint8_t d_stratum;
+ uint8_t d_good;
+} datapoint_t;
+
+#define NUM_DATAPOINTS 8
+typedef struct {
+ len_and_sockaddr *p_lsa;
+ char *p_dotted;
+ /* When to send new query (if p_fd == -1)
+ * or when receive times out (if p_fd >= 0): */
+ time_t next_action_time;
+ int p_fd;
+ uint8_t p_datapoint_idx;
+ uint8_t p_trustlevel;
+ double p_xmttime;
+ datapoint_t update;
+ datapoint_t p_datapoint[NUM_DATAPOINTS];
+ msg_t p_xmt_msg;
+} peer_t;
+
+enum {
+ OPT_n = (1 << 0),
+ OPT_q = (1 << 1),
+ OPT_N = (1 << 2),
+ OPT_x = (1 << 3),
+ /* Insert new options above this line. */
+ /* Non-compat options: */
+ OPT_p = (1 << 4),
+ OPT_l = (1 << 5) * ENABLE_FEATURE_NTPD_SERVER,
+};
+
+
+struct globals {
+ /* total round trip delay to currently selected reference clock */
+ double rootdelay;
+ /* reference timestamp: time when the system clock was last set or corrected */
+ double reftime;
+ llist_t *ntp_peers;
+#if ENABLE_FEATURE_NTPD_SERVER
+ int listen_fd;
+#endif
+ unsigned verbose;
+ unsigned peer_cnt;
+ unsigned scale;
+ uint32_t refid;
+ uint32_t refid4;
+ uint8_t synced;
+ uint8_t leap;
+#define G_precision_exp -6
+// int8_t precision_exp;
+ uint8_t stratum;
+ uint8_t time_was_stepped;
+ uint8_t first_adj_done;
+};
+#define G (*ptr_to_globals)
+
+static const int const_IPTOS_LOWDELAY = IPTOS_LOWDELAY;
+
+
+static void
+set_next(peer_t *p, unsigned t)
+{
+ p->next_action_time = time(NULL) + t;
+}
+
+static void
+add_peers(char *s)
+{
+ peer_t *p;
+
+ p = xzalloc(sizeof(*p));
+ p->p_lsa = xhost2sockaddr(s, 123);
+ p->p_dotted = xmalloc_sockaddr2dotted_noport(&p->p_lsa->u.sa);
+ p->p_fd = -1;
+ p->p_xmt_msg.m_status = MODE_CLIENT | (NTP_VERSION << 3);
+ p->p_trustlevel = TRUSTLEVEL_PATHETIC;
+ p->next_action_time = time(NULL); /* = set_next(p, 0); */
+
+ llist_add_to(&G.ntp_peers, p);
+ G.peer_cnt++;
+}
+
+static double
+gettime1900d(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL); /* never fails */
+ return (tv.tv_sec + 1.0e-6 * tv.tv_usec + OFFSET_1900_1970);
+}
+
+static void
+d_to_tv(double d, struct timeval *tv)
+{
+ tv->tv_sec = (long)d;
+ tv->tv_usec = (d - tv->tv_sec) * 1000000;
+}
+
+static double
+lfp_to_d(l_fixedpt_t lfp)
+{
+ double ret;
+ lfp.int_partl = ntohl(lfp.int_partl);
+ lfp.fractionl = ntohl(lfp.fractionl);
+ ret = (double)lfp.int_partl + ((double)lfp.fractionl / UINT_MAX);
+ return ret;
+}
+
+#if 0 //UNUSED
+static double
+sfp_to_d(s_fixedpt_t sfp)
+{
+ double ret;
+ sfp.int_parts = ntohs(sfp.int_parts);
+ sfp.fractions = ntohs(sfp.fractions);
+ ret = (double)sfp.int_parts + ((double)sfp.fractions / USHRT_MAX);
+ return ret;
+}
+#endif
+
+#if ENABLE_FEATURE_NTPD_SERVER
+static l_fixedpt_t
+d_to_lfp(double d)
+{
+ l_fixedpt_t lfp;
+ lfp.int_partl = (uint32_t)d;
+ lfp.fractionl = (uint32_t)((d - lfp.int_partl) * UINT_MAX);
+ lfp.int_partl = htonl(lfp.int_partl);
+ lfp.fractionl = htonl(lfp.fractionl);
+ return lfp;
+}
+
+static s_fixedpt_t
+d_to_sfp(double d)
+{
+ s_fixedpt_t sfp;
+ sfp.int_parts = (uint16_t)d;
+ sfp.fractions = (uint16_t)((d - sfp.int_parts) * USHRT_MAX);
+ sfp.int_parts = htons(sfp.int_parts);
+ sfp.fractions = htons(sfp.fractions);
+ return sfp;
+}
+#endif
+
+static unsigned
+error_interval(void)
+{
+ unsigned interval, r;
+ interval = INTERVAL_QUERY_PATHETIC * QSCALE_OFF_MAX / QSCALE_OFF_MIN;
+ r = (unsigned)random() % (unsigned)(interval / 10);
+ return (interval + r);
+}
+
+static int
+do_sendto(int fd,
+ const struct sockaddr *from, const struct sockaddr *to, socklen_t addrlen,
+ msg_t *msg, ssize_t len)
+{
+ ssize_t ret;
+
+ errno = 0;
+ if (!from) {
+ ret = sendto(fd, msg, len, MSG_DONTWAIT, to, addrlen);
+ } else {
+ ret = send_to_from(fd, msg, len, MSG_DONTWAIT, to, from, addrlen);
+ }
+ if (ret != len) {
+ bb_perror_msg("send failed");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+send_query_to_peer(peer_t *p)
+{
+ // Why do we need to bind()?
+ // See what happens when we don't bind:
+ //
+ // socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
+ // setsockopt(3, SOL_IP, IP_TOS, [16], 4) = 0
+ // gettimeofday({1259071266, 327885}, NULL) = 0
+ // sendto(3, "xxx", 48, MSG_DONTWAIT, {sa_family=AF_INET, sin_port=htons(123), sin_addr=inet_addr("10.34.32.125")}, 16) = 48
+ // ^^^ we sent it from some source port picked by kernel.
+ // time(NULL) = 1259071266
+ // write(2, "ntpd: entering poll 15 secs\n", 28) = 28
+ // poll([{fd=3, events=POLLIN}], 1, 15000) = 1 ([{fd=3, revents=POLLIN}])
+ // recv(3, "yyy", 68, MSG_DONTWAIT) = 48
+ // ^^^ this recv will receive packets to any local port!
+ //
+ // Uncomment this and use strace to see it in action:
+#define PROBE_LOCAL_ADDR // { len_and_sockaddr lsa; lsa.len = LSA_SIZEOF_SA; getsockname(p->query.fd, &lsa.u.sa, &lsa.len); }
+
+ if (p->p_fd == -1) {
+ int fd, family;
+ len_and_sockaddr *local_lsa;
+
+ family = p->p_lsa->u.sa.sa_family;
+ p->p_fd = fd = xsocket_type(&local_lsa, family, SOCK_DGRAM);
+ /* local_lsa has "null" address and port 0 now.
+ * bind() ensures we have a *particular port* selected by kernel
+ * and remembered in p->p_fd, thus later recv(p->p_fd)
+ * receives only packets sent to this port.
+ */
+ PROBE_LOCAL_ADDR
+ xbind(fd, &local_lsa->u.sa, local_lsa->len);
+ PROBE_LOCAL_ADDR
+#if ENABLE_FEATURE_IPV6
+ if (family == AF_INET)
+#endif
+ setsockopt(fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_IPTOS_LOWDELAY));
+ free(local_lsa);
+ }
+
+ /*
+ * Send out a random 64-bit number as our transmit time. The NTP
+ * server will copy said number into the originate field on the
+ * response that it sends us. This is totally legal per the SNTP spec.
+ *
+ * The impact of this is two fold: we no longer send out the current
+ * system time for the world to see (which may aid an attacker), and
+ * it gives us a (not very secure) way of knowing that we're not
+ * getting spoofed by an attacker that can't capture our traffic
+ * but can spoof packets from the NTP server we're communicating with.
+ *
+ * Save the real transmit timestamp locally.
+ */
+ p->p_xmt_msg.m_xmttime.int_partl = random();
+ p->p_xmt_msg.m_xmttime.fractionl = random();
+ p->p_xmttime = gettime1900d();
+
+ if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len,
+ &p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1
+ ) {
+ close(p->p_fd);
+ p->p_fd = -1;
+ set_next(p, INTERVAL_QUERY_PATHETIC);
+ return -1;
+ }
+
+ if (G.verbose)
+ bb_error_msg("sent query to %s", p->p_dotted);
+ set_next(p, QUERYTIME_MAX);
+
+ return 0;
+}
+
+
+/* Time is stepped only once, when the first packet from a peer is received.
+ */
+static void
+step_time_once(double offset)
+{
+ double dtime;
+ llist_t *item;
+ struct timeval tv;
+ char buf[80];
+ time_t tval;
+
+ if (G.time_was_stepped)
+ goto bail;
+ G.time_was_stepped = 1;
+
+ /* if the offset is small, don't step, slew (later) */
+ if (offset < STEPTIME_MIN_OFFSET && offset > -STEPTIME_MIN_OFFSET)
+ goto bail;
+
+ gettimeofday(&tv, NULL); /* never fails */
+ dtime = offset + tv.tv_sec;
+ dtime += 1.0e-6 * tv.tv_usec;
+ d_to_tv(dtime, &tv);
+
+ if (settimeofday(&tv, NULL) == -1)
+ bb_perror_msg_and_die("settimeofday");
+
+ tval = tv.tv_sec;
+ strftime(buf, sizeof(buf), "%a %b %e %H:%M:%S %Z %Y", localtime(&tval));
+
+ bb_error_msg("setting clock to %s (offset %fs)", buf, offset);
+
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *p = (peer_t *) item->data;
+ p->next_action_time -= (time_t)offset;
+ }
+
+ bail:
+ if (option_mask32 & OPT_q)
+ exit(0);
+}
+
+
+/* Time is periodically slewed when we collect enough
+ * good data points.
+ */
+static int
+compare_offsets(const void *aa, const void *bb)
+{
+ const peer_t *const *a = aa;
+ const peer_t *const *b = bb;
+ if ((*a)->update.d_offset < (*b)->update.d_offset)
+ return -1;
+ return ((*a)->update.d_offset > (*b)->update.d_offset);
+}
+static unsigned
+updated_scale(double offset)
+{
+ if (offset < 0)
+ offset = -offset;
+ if (offset > QSCALE_OFF_MAX)
+ return 1;
+ if (offset < QSCALE_OFF_MIN)
+ return QSCALE_OFF_MAX / QSCALE_OFF_MIN;
+ return QSCALE_OFF_MAX / offset;
+}
+static void
+slew_time(void)
+{
+ llist_t *item;
+ double offset_median;
+ struct timeval tv;
+
+ {
+ peer_t **peers = xzalloc(sizeof(peers[0]) * G.peer_cnt);
+ unsigned goodpeer_cnt = 0;
+ unsigned middle;
+
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *p = (peer_t *) item->data;
+ if (p->p_trustlevel < TRUSTLEVEL_BADPEER)
+ continue;
+ if (!p->update.d_good) {
+ free(peers);
+ return;
+ }
+ peers[goodpeer_cnt++] = p;
+ }
+
+ if (goodpeer_cnt == 0) {
+ free(peers);
+ goto clear_good;
+ }
+
+ qsort(peers, goodpeer_cnt, sizeof(peers[0]), compare_offsets);
+
+ middle = goodpeer_cnt / 2;
+ if (middle != 0 && (goodpeer_cnt & 1) == 0) {
+ offset_median = (peers[middle-1]->update.d_offset + peers[middle]->update.d_offset) / 2;
+ G.rootdelay = (peers[middle-1]->update.d_delay + peers[middle]->update.d_delay) / 2;
+ G.stratum = 1 + MAX(peers[middle-1]->update.d_stratum, peers[middle]->update.d_stratum);
+ } else {
+ offset_median = peers[middle]->update.d_offset;
+ G.rootdelay = peers[middle]->update.d_delay;
+ G.stratum = 1 + peers[middle]->update.d_stratum;
+ }
+ G.leap = peers[middle]->update.d_leap;
+ G.refid4 = peers[middle]->update.d_refid4;
+ G.refid =
+#if ENABLE_FEATURE_IPV6
+ peers[middle]->p_lsa->u.sa.sa_family != AF_INET ?
+ G.refid4 :
+#endif
+ peers[middle]->p_lsa->u.sin.sin_addr.s_addr;
+ free(peers);
+ }
+//TODO: if (offset_median > BIG) step_time(offset_median)?
+
+ G.scale = updated_scale(offset_median);
+
+ bb_error_msg("adjusting clock by %fs, our stratum is %u, time scale %u",
+ offset_median, G.stratum, G.scale);
+
+ errno = 0;
+ d_to_tv(offset_median, &tv);
+ if (adjtime(&tv, &tv) == -1)
+ bb_perror_msg_and_die("adjtime failed");
+ if (G.verbose >= 2)
+ bb_error_msg("old adjust: %d.%06u", (int)tv.tv_sec, (unsigned)tv.tv_usec);
+
+ if (G.first_adj_done) {
+ uint8_t synced = (tv.tv_sec == 0 && tv.tv_usec == 0);
+ if (synced != G.synced) {
+ G.synced = synced;
+ bb_error_msg("clock is %ssynced", synced ? "" : "un");
+ }
+ }
+ G.first_adj_done = 1;
+
+ G.reftime = gettime1900d();
+
+ clear_good:
+ for (item = G.ntp_peers; item != NULL; item = item->link) {
+ peer_t *p = (peer_t *) item->data;
+ p->update.d_good = 0;
+ }
+}
+
+static void
+update_peer_data(peer_t *p)
+{
+ /* Clock filter.
+ * Find the datapoint with the lowest delay.
+ * Use that as the peer update.
+ * Invalidate it and all older ones.
+ */
+ int i;
+ int best = -1;
+ int good = 0;
+
+ for (i = 0; i < NUM_DATAPOINTS; i++) {
+ if (p->p_datapoint[i].d_good) {
+ good++;
+ if (best < 0 || p->p_datapoint[i].d_delay < p->p_datapoint[best].d_delay)
+ best = i;
+ }
+ }
+
+ if (good < 8) //FIXME: was it meant to be NUM_DATAPOINTS, not 8?
+ return;
+
+ p->update = p->p_datapoint[best]; /* struct copy */
+ slew_time();
+
+ for (i = 0; i < NUM_DATAPOINTS; i++)
+ if (p->p_datapoint[i].d_rcv_time <= p->p_datapoint[best].d_rcv_time)
+ p->p_datapoint[i].d_good = 0;
+}
+
+static unsigned
+scale_interval(unsigned requested)
+{
+ unsigned interval, r;
+ interval = requested * G.scale;
+ r = (unsigned)random() % (unsigned)(MAX(5, interval / 10));
+ return (interval + r);
+}
+static void
+recv_and_process_peer_pkt(peer_t *p)
+{
+ ssize_t size;
+ msg_t msg;
+ double T1, T2, T3, T4;
+ unsigned interval;
+ datapoint_t *datapoint;
+
+ /* We can recvfrom here and check from.IP, but some multihomed
+ * ntp servers reply from their *other IP*.
+ * TODO: maybe we should check at least what we can: from.port == 123?
+ */
+ size = recv(p->p_fd, &msg, sizeof(msg), MSG_DONTWAIT);
+ if (size == -1) {
+ bb_perror_msg("recv(%s) error", p->p_dotted);
+ if (errno == EHOSTUNREACH || errno == EHOSTDOWN
+ || errno == ENETUNREACH || errno == ENETDOWN
+ || errno == ECONNREFUSED || errno == EADDRNOTAVAIL
+ || errno == EAGAIN
+ ) {
+//TODO: always do this?
+ set_next(p, error_interval());
+ goto close_sock;
+ }
+ xfunc_die();
+ }
+
+ if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) {
+ bb_error_msg("malformed packet received from %s", p->p_dotted);
+ goto bail;
+ }
+
+ if (msg.m_orgtime.int_partl != p->p_xmt_msg.m_xmttime.int_partl
+ || msg.m_orgtime.fractionl != p->p_xmt_msg.m_xmttime.fractionl
+ ) {
+ goto bail;
+ }
+
+ if ((msg.m_status & LI_ALARM) == LI_ALARM
+ || msg.m_stratum == 0
+ || msg.m_stratum > NTP_MAXSTRATUM
+ ) {
+// TODO: stratum 0 responses may have commands in 32-bit m_refid field:
+// "DENY", "RSTR" - peer does not like us at all
+// "RATE" - peer is overloaded, reduce polling freq
+ interval = error_interval();
+ bb_error_msg("reply from %s: not synced, next query in %us", p->p_dotted, interval);
+ goto close_sock;
+ }
+
+ /*
+ * From RFC 2030 (with a correction to the delay math):
+ *
+ * Timestamp Name ID When Generated
+ * ------------------------------------------------------------
+ * Originate Timestamp T1 time request sent by client
+ * Receive Timestamp T2 time request received by server
+ * Transmit Timestamp T3 time reply sent by server
+ * Destination Timestamp T4 time reply received by client
+ *
+ * The roundtrip delay and local clock offset are defined as
+ *
+ * delay = (T4 - T1) - (T3 - T2); offset = ((T2 - T1) + (T3 - T4)) / 2
+ */
+ T1 = p->p_xmttime;
+ T2 = lfp_to_d(msg.m_rectime);
+ T3 = lfp_to_d(msg.m_xmttime);
+ T4 = gettime1900d();
+
+ datapoint = &p->p_datapoint[p->p_datapoint_idx];
+
+ datapoint->d_offset = ((T2 - T1) + (T3 - T4)) / 2;
+ datapoint->d_delay = (T4 - T1) - (T3 - T2);
+ if (datapoint->d_delay < 0) {
+ bb_error_msg("reply from %s: negative delay %f", p->p_dotted, datapoint->d_delay);
+ interval = error_interval();
+ set_next(p, interval);
+ goto close_sock;
+ }
+ //UNUSED: datapoint->d_error = (T2 - T1) - (T3 - T4);
+ datapoint->d_rcv_time = (time_t)(T4 - OFFSET_1900_1970); /* = time(NULL); */
+ datapoint->d_good = 1;
+
+ datapoint->d_leap = (msg.m_status & LI_MASK);
+ //UNUSED: datapoint->o_precision = msg.m_precision_exp;
+ //UNUSED: datapoint->o_rootdelay = sfp_to_d(msg.m_rootdelay);
+ //UNUSED: datapoint->o_rootdispersion = sfp_to_d(msg.m_dispersion);
+ //UNUSED: datapoint->d_refid = ntohl(msg.m_refid);
+ datapoint->d_refid4 = msg.m_xmttime.fractionl;
+ //UNUSED: datapoint->o_reftime = lfp_to_d(msg.m_reftime);
+ //UNUSED: datapoint->o_poll = msg.m_ppoll;
+ datapoint->d_stratum = msg.m_stratum;
+
+ if (p->p_trustlevel < TRUSTLEVEL_PATHETIC)
+ interval = scale_interval(INTERVAL_QUERY_PATHETIC);
+ else if (p->p_trustlevel < TRUSTLEVEL_AGRESSIVE)
+ interval = scale_interval(INTERVAL_QUERY_AGRESSIVE);
+ else
+ interval = scale_interval(INTERVAL_QUERY_NORMAL);
+
+ set_next(p, interval);
+
+ /* Every received reply which we do not discard increases trust */
+ if (p->p_trustlevel < TRUSTLEVEL_MAX) {
+ p->p_trustlevel++;
+ if (p->p_trustlevel == TRUSTLEVEL_BADPEER)
+ bb_error_msg("peer %s now valid", p->p_dotted);
+ }
+
+ if (G.verbose)
+ bb_error_msg("reply from %s: offset %f delay %f, next query in %us", p->p_dotted,
+ datapoint->d_offset, datapoint->d_delay, interval);
+
+ update_peer_data(p);
+//TODO: do it after all peers had a chance to return at least one reply?
+ step_time_once(datapoint->d_offset);
+
+ p->p_datapoint_idx++;
+ if (p->p_datapoint_idx >= NUM_DATAPOINTS)
+ p->p_datapoint_idx = 0;
+
+ close_sock:
+ /* We do not expect any more packets from this peer for now.
+ * Closing the socket informs kernel about it.
+ * We open a new socket when we send a new query.
+ */
+ close(p->p_fd);
+ p->p_fd = -1;
+ bail:
+ return;
+}
+
+#if ENABLE_FEATURE_NTPD_SERVER
+static void
+recv_and_process_client_pkt(void /*int fd*/)
+{
+ ssize_t size;
+ uint8_t version;
+ double rectime;
+ len_and_sockaddr *to;
+ struct sockaddr *from;
+ msg_t msg;
+ uint8_t query_status;
+ uint8_t query_ppoll;
+ l_fixedpt_t query_xmttime;
+
+ to = get_sock_lsa(G.listen_fd);
+ from = xzalloc(to->len);
+
+ size = recv_from_to(G.listen_fd, &msg, sizeof(msg), MSG_DONTWAIT, from, &to->u.sa, to->len);
+ if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) {
+ char *addr;
+ if (size < 0) {
+ if (errno == EAGAIN)
+ goto bail;
+ bb_perror_msg_and_die("recv");
+ }
+ addr = xmalloc_sockaddr2dotted_noport(from);
+ bb_error_msg("malformed packet received from %s: size %u", addr, (int)size);
+ free(addr);
+ goto bail;
+ }
+
+ query_status = msg.m_status;
+ query_ppoll = msg.m_ppoll;
+ query_xmttime = msg.m_xmttime;
+
+ /* Build a reply packet */
+ memset(&msg, 0, sizeof(msg));
+ msg.m_status = G.synced ? G.leap : LI_ALARM;
+ msg.m_status |= (query_status & VERSION_MASK);
+ msg.m_status |= ((query_status & MODE_MASK) == MODE_CLIENT) ?
+ MODE_SERVER : MODE_SYM_PAS;
+ msg.m_stratum = G.stratum;
+ msg.m_ppoll = query_ppoll;
+ msg.m_precision_exp = G_precision_exp;
+ rectime = gettime1900d();
+ msg.m_xmttime = msg.m_rectime = d_to_lfp(rectime);
+ msg.m_reftime = d_to_lfp(G.reftime);
+ //msg.m_xmttime = d_to_lfp(gettime1900d()); // = msg.m_rectime
+ msg.m_orgtime = query_xmttime;
+ msg.m_rootdelay = d_to_sfp(G.rootdelay);
+ version = (query_status & VERSION_MASK); /* ... >> VERSION_SHIFT - done below instead */
+ msg.m_refid = (version > (3 << VERSION_SHIFT)) ? G.refid4 : G.refid;
+
+ /* We reply from the local address packet was sent to,
+ * this makes to/from look swapped here: */
+ do_sendto(G.listen_fd,
+ /*from:*/ &to->u.sa, /*to:*/ from, /*addrlen:*/ to->len,
+ &msg, size);
+
+ bail:
+ free(to);
+ free(from);
+}
+#endif
+
+/* Upstream ntpd's options:
+ *
+ * -4 Force DNS resolution of host names to the IPv4 namespace.
+ * -6 Force DNS resolution of host names to the IPv6 namespace.
+ * -a Require cryptographic authentication for broadcast client,
+ * multicast client and symmetric passive associations.
+ * This is the default.
+ * -A Do not require cryptographic authentication for broadcast client,
+ * multicast client and symmetric passive associations.
+ * This is almost never a good idea.
+ * -b Enable the client to synchronize to broadcast servers.
+ * -c conffile
+ * Specify the name and path of the configuration file,
+ * default /etc/ntp.conf
+ * -d Specify debugging mode. This option may occur more than once,
+ * with each occurrence indicating greater detail of display.
+ * -D level
+ * Specify debugging level directly.
+ * -f driftfile
+ * Specify the name and path of the frequency file.
+ * This is the same operation as the "driftfile FILE"
+ * configuration command.
+ * -g Normally, ntpd exits with a message to the system log
+ * if the offset exceeds the panic threshold, which is 1000 s
+ * by default. This option allows the time to be set to any value
+ * without restriction; however, this can happen only once.
+ * If the threshold is exceeded after that, ntpd will exit
+ * with a message to the system log. This option can be used
+ * with the -q and -x options. See the tinker command for other options.
+ * -i jaildir
+ * Chroot the server to the directory jaildir. This option also implies
+ * that the server attempts to drop root privileges at startup
+ * (otherwise, chroot gives very little additional security).
+ * You may need to also specify a -u option.
+ * -k keyfile
+ * Specify the name and path of the symmetric key file,
+ * default /etc/ntp/keys. This is the same operation
+ * as the "keys FILE" configuration command.
+ * -l logfile
+ * Specify the name and path of the log file. The default
+ * is the system log file. This is the same operation as
+ * the "logfile FILE" configuration command.
+ * -L Do not listen to virtual IPs. The default is to listen.
+ * -n Don't fork.
+ * -N To the extent permitted by the operating system,
+ * run the ntpd at the highest priority.
+ * -p pidfile
+ * Specify the name and path of the file used to record the ntpd
+ * process ID. This is the same operation as the "pidfile FILE"
+ * configuration command.
+ * -P priority
+ * To the extent permitted by the operating system,
+ * run the ntpd at the specified priority.
+ * -q Exit the ntpd just after the first time the clock is set.
+ * This behavior mimics that of the ntpdate program, which is
+ * to be retired. The -g and -x options can be used with this option.
+ * Note: The kernel time discipline is disabled with this option.
+ * -r broadcastdelay
+ * Specify the default propagation delay from the broadcast/multicast
+ * server to this client. This is necessary only if the delay
+ * cannot be computed automatically by the protocol.
+ * -s statsdir
+ * Specify the directory path for files created by the statistics
+ * facility. This is the same operation as the "statsdir DIR"
+ * configuration command.
+ * -t key
+ * Add a key number to the trusted key list. This option can occur
+ * more than once.
+ * -u user[:group]
+ * Specify a user, and optionally a group, to switch to.
+ * -v variable
+ * -V variable
+ * Add a system variable listed by default.
+ * -x Normally, the time is slewed if the offset is less than the step
+ * threshold, which is 128 ms by default, and stepped if above
+ * the threshold. This option sets the threshold to 600 s, which is
+ * well within the accuracy window to set the clock manually.
+ * Note: since the slew rate of typical Unix kernels is limited
+ * to 0.5 ms/s, each second of adjustment requires an amortization
+ * interval of 2000 s. Thus, an adjustment as much as 600 s
+ * will take almost 14 days to complete. This option can be used
+ * with the -g and -q options. See the tinker command for other options.
+ * Note: The kernel time discipline is disabled with this option.
+ */
+
+/* By doing init in a separate function we decrease stack usage
+ * in main loop.
+ */
+static NOINLINE void ntp_init(char **argv)
+{
+ unsigned opts;
+ llist_t *peers;
+
+ srandom(getpid());
+
+ if (getuid())
+ bb_error_msg_and_die(bb_msg_you_must_be_root);
+
+ peers = NULL;
+ opt_complementary = "dd:p::"; /* d: counter, p: list */
+ opts = getopt32(argv,
+ "nqNx" /* compat */
+ "p:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */
+ "d" /* compat */
+ "46aAbgL", /* compat, ignored */
+ &peers, &G.verbose);
+ if (!(opts & (OPT_p|OPT_l)))
+ bb_show_usage();
+ if (opts & OPT_x) /* disable stepping, only slew is allowed */
+ G.time_was_stepped = 1;
+ while (peers)
+ add_peers(llist_pop(&peers));
+ if (!(opts & OPT_n)) {
+ bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO, argv);
+ logmode = LOGMODE_NONE;
+ }
+#if ENABLE_FEATURE_NTPD_SERVER
+ G.listen_fd = -1;
+ if (opts & OPT_l) {
+ G.listen_fd = create_and_bind_dgram_or_die(NULL, 123);
+ socket_want_pktinfo(G.listen_fd);
+ setsockopt(G.listen_fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_IPTOS_LOWDELAY));
+ }
+#endif
+ /* I hesitate to set -20 prio. -15 should be high enough for timekeeping */
+ if (opts & OPT_N)
+ setpriority(PRIO_PROCESS, 0, -15);
+
+ /* Set some globals */
+#if 0
+ /* With constant b = 100, G.precision_exp is also constant -6.
+ * Uncomment this and you'll see */
+ {
+ int prec = 0;
+ int b;
+# if 0
+ struct timespec tp;
+ /* We can use sys_clock_getres but assuming 10ms tick should be fine */
+ clock_getres(CLOCK_REALTIME, &tp);
+ tp.tv_sec = 0;
+ tp.tv_nsec = 10000000;
+ b = 1000000000 / tp.tv_nsec; /* convert to Hz */
+# else
+ b = 100; /* b = 1000000000/10000000 = 100 */
+# endif
+ while (b > 1)
+ prec--, b >>= 1;
+ //G.precision_exp = prec;
+ bb_error_msg("G.precision_exp:%d", prec); /* -6 */
+ }
+#endif
+ G.scale = 1;
+
+ bb_signals((1 << SIGTERM) | (1 << SIGINT), record_signo);
+ bb_signals((1 << SIGPIPE) | (1 << SIGHUP), SIG_IGN);
+}
+
+int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ntpd_main(int argc UNUSED_PARAM, char **argv)
+{
+ struct globals g;
+ struct pollfd *pfd;
+ peer_t **idx2peer;
+
+ memset(&g, 0, sizeof(g));
+ SET_PTR_TO_GLOBALS(&g);
+
+ ntp_init(argv);
+
+ {
+ /* if ENABLE_FEATURE_NTPD_SERVER, + 1 for listen_fd: */
+ unsigned cnt = g.peer_cnt + ENABLE_FEATURE_NTPD_SERVER;
+ idx2peer = xzalloc(sizeof(idx2peer[0]) * cnt);
+ pfd = xzalloc(sizeof(pfd[0]) * cnt);
+ }
+
+ while (!bb_got_signal) {
+ llist_t *item;
+ unsigned i, j;
+ unsigned sent_cnt, trial_cnt;
+ int nfds, timeout;
+ time_t cur_time, nextaction;
+
+ /* Nothing between here and poll() blocks for any significant time */
+
+ cur_time = time(NULL);
+ nextaction = cur_time + 3600;
+
+ i = 0;
+#if ENABLE_FEATURE_NTPD_SERVER
+ if (g.listen_fd != -1) {
+ pfd[0].fd = g.listen_fd;
+ pfd[0].events = POLLIN;
+ i++;
+ }
+#endif
+ /* Pass over peer list, send requests, time out on receives */
+ sent_cnt = trial_cnt = 0;
+ for (item = g.ntp_peers; item != NULL; item = item->link) {
+ peer_t *p = (peer_t *) item->data;
+
+ /* Overflow-safe "if (p->next_action_time <= cur_time) ..." */
+ if ((int)(cur_time - p->next_action_time) >= 0) {
+ if (p->p_fd == -1) {
+ /* Time to send new req */
+ trial_cnt++;
+ if (send_query_to_peer(p) == 0)
+ sent_cnt++;
+ } else {
+ /* Timed out waiting for reply */
+ close(p->p_fd);
+ p->p_fd = -1;
+ timeout = error_interval();
+ bb_error_msg("timed out waiting for %s, "
+ "next query in %us", p->p_dotted, timeout);
+ if (p->p_trustlevel >= TRUSTLEVEL_BADPEER) {
+ p->p_trustlevel /= 2;
+ if (p->p_trustlevel < TRUSTLEVEL_BADPEER)
+ bb_error_msg("peer %s now invalid", p->p_dotted);
+ }
+ set_next(p, timeout);
+ }
+ }
+
+ if (p->next_action_time < nextaction)
+ nextaction = p->next_action_time;
+
+ if (p->p_fd >= 0) {
+ /* Wait for reply from this peer */
+ pfd[i].fd = p->p_fd;
+ pfd[i].events = POLLIN;
+ idx2peer[i] = p;
+ i++;
+ }
+ }
+
+ if ((trial_cnt > 0 && sent_cnt == 0) || g.peer_cnt == 0)
+ step_time_once(0); /* no good peers, don't wait */
+
+ timeout = nextaction - cur_time;
+ if (timeout < 1)
+ timeout = 1;
+
+ /* Here we may block */
+ if (g.verbose >= 2)
+ bb_error_msg("poll %us, sockets:%u", timeout, i);
+ nfds = poll(pfd, i, timeout * 1000);
+ if (nfds <= 0)
+ continue;
+
+ /* Process any received packets */
+ j = 0;
+#if ENABLE_FEATURE_NTPD_SERVER
+ if (g.listen_fd != -1) {
+ if (pfd[0].revents /* & (POLLIN|POLLERR)*/) {
+ nfds--;
+ recv_and_process_client_pkt(/*g.listen_fd*/);
+ }
+ j = 1;
+ }
+#endif
+ for (; nfds != 0 && j < i; j++) {
+ if (pfd[j].revents /* & (POLLIN|POLLERR)*/) {
+ nfds--;
+ recv_and_process_peer_pkt(idx2peer[j]);
+ }
+ }
+ } /* while (!bb_got_signal) */
+
+ kill_myself_with_sig(bb_got_signal);
+}
diff --git a/ap/app/busybox/src/networking/ping.c b/ap/app/busybox/src/networking/ping.c
new file mode 100644
index 0000000..3df67f5
--- /dev/null
+++ b/ap/app/busybox/src/networking/ping.c
@@ -0,0 +1,921 @@
+/* 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 source tree.
+ */
+/* 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"
+
+#ifdef __BIONIC__
+/* should be in netinet/ip_icmp.h */
+# define ICMP_DEST_UNREACH 3 /* Destination Unreachable */
+# define ICMP_SOURCE_QUENCH 4 /* Source Quench */
+# define ICMP_REDIRECT 5 /* Redirect (change route) */
+# define ICMP_ECHO 8 /* Echo Request */
+# define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */
+# define ICMP_PARAMETERPROB 12 /* Parameter Problem */
+# define ICMP_TIMESTAMP 13 /* Timestamp Request */
+# define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */
+# define ICMP_INFO_REQUEST 15 /* Information Request */
+# define ICMP_INFO_REPLY 16 /* Information Reply */
+# define ICMP_ADDRESS 17 /* Address Mask Request */
+# define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */
+#endif
+
+//config:config PING
+//config: bool "ping"
+//config: default y
+//config: select PLATFORM_LINUX
+//config: help
+//config: ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to
+//config: elicit an ICMP ECHO_RESPONSE from a host or gateway.
+//config:
+//config:config PING6
+//config: bool "ping6"
+//config: default y
+//config: depends on FEATURE_IPV6 && PING
+//config: help
+//config: This will give you a ping that can talk IPv6.
+//config:
+//config:config FEATURE_FANCY_PING
+//config: bool "Enable fancy ping output"
+//config: default y
+//config: depends on PING
+//config: help
+//config: Make the output from the ping applet include statistics, and at the
+//config: same time provide full support for ICMP packets.
+
+/* Needs socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), therefore BB_SUID_MAYBE: */
+//applet:IF_PING(APPLET(ping, BB_DIR_BIN, BB_SUID_MAYBE))
+//applet:IF_PING6(APPLET(ping6, BB_DIR_BIN, BB_SUID_MAYBE))
+
+//kbuild:lib-$(CONFIG_PING) += ping.o
+//kbuild:lib-$(CONFIG_PING6) += ping.o
+
+//usage:#if !ENABLE_FEATURE_FANCY_PING
+//usage:# define ping_trivial_usage
+//usage: "HOST"
+//usage:# define ping_full_usage "\n\n"
+//usage: "Send ICMP ECHO_REQUEST packets to network hosts"
+//usage:# define ping6_trivial_usage
+//usage: "HOST"
+//usage:# define ping6_full_usage "\n\n"
+//usage: "Send ICMP ECHO_REQUEST packets to network hosts"
+//usage:#else
+//usage:# define ping_trivial_usage
+//usage: "[OPTIONS] HOST"
+//usage:# define ping_full_usage "\n\n"
+//usage: "Send ICMP ECHO_REQUEST packets to network hosts\n"
+//usage: "\n -4,-6 Force IP or IPv6 name resolution"
+//usage: "\n -c CNT Send only CNT pings"
+//usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)"
+//usage: "\n -t TTL Set TTL"
+//usage: "\n -I IFACE/IP Use interface or IP address as source"
+//usage: "\n -W SEC Seconds to wait for the first response (default:10)"
+//usage: "\n (after all -c CNT packets are sent)"
+//usage: "\n -w SEC Seconds until ping exits (default:infinite)"
+//usage: "\n (can exit earlier with -c CNT)"
+//usage: "\n -q Quiet, only displays output at start"
+//usage: "\n and when finished"
+//usage:
+//usage:# define ping6_trivial_usage
+//usage: "[OPTIONS] HOST"
+//usage:# define ping6_full_usage "\n\n"
+//usage: "Send ICMP ECHO_REQUEST packets to network hosts\n"
+//usage: "\n -c CNT Send only CNT pings"
+//usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)"
+//usage: "\n -I IFACE/IP Use interface or IP address as source"
+//usage: "\n -q Quiet, only displays output at start"
+//usage: "\n and when finished"
+//usage:
+//usage:#endif
+//usage:
+//usage:#define ping_example_usage
+//usage: "$ ping localhost\n"
+//usage: "PING slag (127.0.0.1): 56 data bytes\n"
+//usage: "64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=20.1 ms\n"
+//usage: "\n"
+//usage: "--- debian ping statistics ---\n"
+//usage: "1 packets transmitted, 1 packets received, 0% packet loss\n"
+//usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n"
+//usage:#define ping6_example_usage
+//usage: "$ ping6 ip6-localhost\n"
+//usage: "PING ip6-localhost (::1): 56 data bytes\n"
+//usage: "64 bytes from ::1: icmp6_seq=0 ttl=64 time=20.1 ms\n"
+//usage: "\n"
+//usage: "--- ip6-localhost ping statistics ---\n"
+//usage: "1 packets transmitted, 1 packets received, 0% packet loss\n"
+//usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n"
+
+#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,
+ MAX_DUP_CHK = (8 * 128),
+ MAXWAIT = 10,
+ PINGINTERVAL = 1, /* 1 second */
+};
+
+#if !ENABLE_FEATURE_FANCY_PING
+
+/* Simple version */
+
+struct globals {
+ char *hostname;
+ char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { } while (0)
+
+static void noresp(int ign UNUSED_PARAM)
+{
+ printf("No response from %s\n", G.hostname);
+ exit(EXIT_FAILURE);
+}
+
+static void ping4(len_and_sockaddr *lsa)
+{
+ struct icmp *pkt;
+ int pingsock, c;
+
+ pingsock = create_icmp_socket();
+
+ pkt = (struct icmp *) G.packet;
+ memset(pkt, 0, sizeof(G.packet));
+ pkt->icmp_type = ICMP_ECHO;
+ pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, sizeof(G.packet));
+
+ xsendto(pingsock, G.packet, DEFDATALEN + ICMP_MINLEN, &lsa->u.sa, lsa->len);
+
+ /* listen for replies */
+ while (1) {
+ struct sockaddr_in from;
+ socklen_t fromlen = sizeof(from);
+
+ c = recvfrom(pingsock, G.packet, sizeof(G.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 *) G.packet;
+
+ pkt = (struct icmp *) (G.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 icmp6_hdr *pkt;
+ int pingsock, c;
+ int sockopt;
+
+ pingsock = create_icmp6_socket();
+
+ pkt = (struct icmp6_hdr *) G.packet;
+ memset(pkt, 0, sizeof(G.packet));
+ pkt->icmp6_type = ICMP6_ECHO_REQUEST;
+
+ sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
+ setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
+
+ xsendto(pingsock, G.packet, DEFDATALEN + sizeof(struct icmp6_hdr), &lsa->u.sa, lsa->len);
+
+ /* listen for replies */
+ while (1) {
+ struct sockaddr_in6 from;
+ socklen_t fromlen = sizeof(from);
+
+ c = recvfrom(pingsock, G.packet, sizeof(G.packet), 0,
+ (struct sockaddr *) &from, &fromlen);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ if (c >= ICMP_MINLEN) { /* icmp6_hdr */
+ pkt = (struct icmp6_hdr *) G.packet;
+ if (pkt->icmp6_type == ICMP6_ECHO_REPLY)
+ break;
+ }
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(pingsock);
+}
+#endif
+
+#if !ENABLE_PING6
+# define common_ping_main(af, argv) common_ping_main(argv)
+#endif
+static int common_ping_main(sa_family_t af, char **argv)
+{
+ len_and_sockaddr *lsa;
+
+ INIT_G();
+
+#if ENABLE_PING6
+ 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
+
+ G.hostname = *argv;
+ if (!G.hostname)
+ bb_show_usage();
+
+#if ENABLE_PING6
+ lsa = xhost_and_af2sockaddr(G.hostname, 0, af);
+#else
+ lsa = xhost_and_af2sockaddr(G.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", G.hostname);
+ return EXIT_SUCCESS;
+}
+
+
+#else /* FEATURE_FANCY_PING */
+
+
+/* Full(er) version */
+
+#define OPT_STRING ("qvc:s:t:w:W:I:4" IF_PING6("6"))
+enum {
+ OPT_QUIET = 1 << 0,
+ OPT_VERBOSE = 1 << 1,
+ OPT_c = 1 << 2,
+ OPT_s = 1 << 3,
+ OPT_t = 1 << 4,
+ OPT_w = 1 << 5,
+ OPT_W = 1 << 6,
+ OPT_I = 1 << 7,
+ OPT_IPV4 = 1 << 8,
+ OPT_IPV6 = (1 << 9) * 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 opt_ttl;
+ 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;
+ unsigned sizeof_rcv_packet;
+ char *rcv_packet; /* [datalen + MAXIPLEN + MAXICMPLEN] */
+ void *snd_packet; /* [datalen + ipv4/ipv6_const] */
+ 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];
+} FIX_ALIASING;
+#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 opt_ttl (G.opt_ttl )
+#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), int size_pkt)
+{
+ int sz;
+
+ CLR((uint16_t)ntransmitted % MAX_DUP_CHK);
+ ntransmitted++;
+
+ size_pkt += datalen;
+
+ /* sizeof(pingaddr) can be larger than real sa size, but I think
+ * it doesn't matter */
+ sz = xsendto(pingsock, G.snd_packet, 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)
+{
+ struct icmp *pkt = G.snd_packet;
+
+ //memset(pkt, 0, datalen + ICMP_MINLEN + 4); - G.snd_packet was xzalloced
+ pkt->icmp_type = ICMP_ECHO;
+ /*pkt->icmp_code = 0;*/
+ pkt->icmp_cksum = 0; /* cksum is calculated with this field set to 0 */
+ pkt->icmp_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */
+ pkt->icmp_id = myid;
+
+ /* If datalen < 4, we store timestamp _past_ the packet,
+ * but it's ok - we allocated 4 extra bytes in xzalloc() just in case.
+ */
+ /*if (datalen >= 4)*/
+ /* No hton: we'll read it back on the same machine */
+ *(uint32_t*)&pkt->icmp_dun = monotonic_us();
+
+ pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, datalen + ICMP_MINLEN);
+
+ sendping_tail(sendping4, ICMP_MINLEN);
+}
+#if ENABLE_PING6
+static void sendping6(int junk UNUSED_PARAM)
+{
+ struct icmp6_hdr *pkt = G.snd_packet;
+
+ //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();
+
+ //TODO? pkt->icmp_cksum = inet_cksum(...);
+
+ sendping_tail(sendping6, 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_all();
+}
+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, &from->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)
+{
+ 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));
+
+ if (opt_ttl != 0) {
+ setsockopt(pingsock, IPPROTO_IP, IP_TTL, &opt_ttl, sizeof(opt_ttl));
+ /* above doesnt affect packets sent to bcast IP, so... */
+ setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_TTL, &opt_ttl, sizeof(opt_ttl));
+ }
+
+ 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, G.rcv_packet, G.sizeof_rcv_packet, 0,
+ (struct sockaddr *) &from, &fromlen);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ unpack4(G.rcv_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)
+{
+ 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 = G.rcv_packet;
+ iov.iov_len = G.sizeof_rcv_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); - unaligned access */
+ move_from_unaligned_int(hoplimit, CMSG_DATA(mp));
+ }
+ }
+ unpack6(G.rcv_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);
+
+ G.sizeof_rcv_packet = datalen + MAXIPLEN + MAXICMPLEN;
+ G.rcv_packet = xzalloc(G.sizeof_rcv_packet);
+#if ENABLE_PING6
+ if (lsa->u.sa.sa_family == AF_INET6) {
+ /* +4 reserves a place for timestamp, which may end up sitting
+ * _after_ packet. Saves one if() - see sendping4/6() */
+ G.snd_packet = xzalloc(datalen + sizeof(struct icmp6_hdr) + 4);
+ ping6(lsa);
+ } else
+#endif
+ {
+ G.snd_packet = xzalloc(datalen + ICMP_MINLEN + 4);
+ ping4(lsa);
+ }
+}
+
+static int common_ping_main(int opt, char **argv)
+{
+ len_and_sockaddr *lsa;
+ char *str_s;
+
+ INIT_G();
+
+ /* exactly one argument needed; -v and -q don't mix; -c NUM, -t NUM, -w NUM, -W NUM */
+ opt_complementary = "=1:q--v:v--q:c+:t+:w+:W+";
+ opt |= getopt32(argv, OPT_STRING, &pingcount, &str_s, &opt_ttl, &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 */
+
+
+int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping_main(int argc UNUSED_PARAM, char **argv)
+{
+#if !ENABLE_FEATURE_FANCY_PING
+ return common_ping_main(AF_UNSPEC, argv);
+#else
+ return common_ping_main(0, argv);
+#endif
+}
+
+#if ENABLE_PING6
+int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping6_main(int argc UNUSED_PARAM, char **argv)
+{
+# if !ENABLE_FEATURE_FANCY_PING
+ return common_ping_main(AF_INET6, argv);
+# else
+ return common_ping_main(OPT_IPV6, argv);
+# endif
+}
+#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/ap/app/busybox/src/networking/pscan.c b/ap/app/busybox/src/networking/pscan.c
new file mode 100644
index 0000000..28005ad
--- /dev/null
+++ b/ap/app/busybox/src/networking/pscan.c
@@ -0,0 +1,165 @@
+/*
+ * Pscan is a mini port scanner implementation for busybox
+ *
+ * Copyright 2007 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define pscan_trivial_usage
+//usage: "[-cb] [-p MIN_PORT] [-P MAX_PORT] [-t TIMEOUT] [-T MIN_RTT] HOST"
+//usage:#define pscan_full_usage "\n\n"
+//usage: "Scan a host, print all open ports\n"
+//usage: "\n -c Show closed ports too"
+//usage: "\n -b Show blocked ports too"
+//usage: "\n -p Scan from this port (default 1)"
+//usage: "\n -P Scan up to this port (default 1024)"
+//usage: "\n -t Timeout (default 5000 ms)"
+//usage: "\n -T Minimum rtt (default 5 ms, increase for congested hosts)"
+
+#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->u.sa, 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/ap/app/busybox/src/networking/route.c b/ap/app/busybox/src/networking/route.c
new file mode 100644
index 0000000..4235ea7
--- /dev/null
+++ b/ap/app/busybox/src/networking/route.c
@@ -0,0 +1,712 @@
+/* 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 source tree.
+ *
+ *
+ * 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.
+ */
+
+//usage:#define route_trivial_usage
+//usage: "[{add|del|delete}]"
+//usage:#define route_full_usage "\n\n"
+//usage: "Edit kernel routing tables\n"
+//usage: "\n -n Don't resolve names"
+//usage: "\n -e Display other/more information"
+//usage: "\n -A inet" IF_FEATURE_IPV6("{6}") " Select address family"
+
+#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 NOINLINE void INET_setroute(int action, char **args)
+{
+ /* char buffer instead of bona-fide struct avoids aliasing warning */
+ char rt_buf[sizeof(struct rtentry)];
+ struct rtentry *const rt = (void *)rt_buf;
+
+ 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 = "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 NOINLINE 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, "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_IFNAMSIZ(ifr.ifr_name, devname);
+ xioctl(skfd, SIOCGIFINDEX, &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);
+ }
+ }
+ fclose(fp);
+}
+
+#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;
+ while (1) {
+ 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;
+ }
+ }
+ }
+ fclose(fp);
+}
+
+#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/ap/app/busybox/src/networking/slattach.c b/ap/app/busybox/src/networking/slattach.c
new file mode 100644
index 0000000..a500da6
--- /dev/null
+++ b/ap/app/busybox/src/networking/slattach.c
@@ -0,0 +1,258 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Stripped down version of net-tools for busybox.
+ *
+ * Author: Ignacio Garcia Perez (iggarpe at gmail dot com)
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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.
+ */
+
+//usage:#define slattach_trivial_usage
+//usage: "[-cehmLF] [-s SPEED] [-p PROTOCOL] DEVICE"
+//usage:#define slattach_full_usage "\n\n"
+//usage: "Attach network interface(s) to serial line(s)\n"
+//usage: "\n -p PROT Set protocol (slip, cslip, slip6, clisp6 or adaptive)"
+//usage: "\n -s SPD Set line speed"
+//usage: "\n -e Exit after initializing device"
+//usage: "\n -h Exit when the carrier is lost"
+//usage: "\n -c PROG Run PROG when the line is hung up"
+//usage: "\n -m Do NOT initialize the line in raw 8 bits mode"
+//usage: "\n -L Enable 3-wire operation"
+//usage: "\n -F Disable RTS/CTS flow control"
+
+#include "libbb.h"
+#include "libiproute/utils.h" /* invarg() */
+
+struct globals {
+ int handle;
+ int saved_disc;
+ struct termios saved_state;
+} FIX_ALIASING;
+#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/ap/app/busybox/src/networking/tc.c b/ap/app/busybox/src/networking/tc.c
new file mode 100644
index 0000000..f968707
--- /dev/null
+++ b/ap/app/busybox/src/networking/tc.c
@@ -0,0 +1,565 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Bernhard Reutner-Fischer adjusted for busybox
+ */
+
+//usage:#define tc_trivial_usage
+/* //usage: "[OPTIONS] OBJECT CMD [dev STRING]" */
+//usage: "OBJECT CMD [dev STRING]"
+//usage:#define tc_full_usage "\n\n"
+//usage: "OBJECT: {qdisc|class|filter}\n"
+//usage: "CMD: {add|del|change|replace|show}\n"
+//usage: "\n"
+//usage: "qdisc [ handle QHANDLE ] [ root |"IF_FEATURE_TC_INGRESS(" ingress |")" parent CLASSID ]\n"
+/* //usage: "[ estimator INTERVAL TIME_CONSTANT ]\n" */
+//usage: " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n"
+//usage: " QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n"
+//usage: "qdisc show [ dev STRING ]"IF_FEATURE_TC_INGRESS(" [ingress]")"\n"
+//usage: "class [ classid CLASSID ] [ root | parent CLASSID ]\n"
+//usage: " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n"
+//usage: "class show [ dev STRING ] [ root | parent CLASSID ]\n"
+//usage: "filter [ pref PRIO ] [ protocol PROTO ]\n"
+/* //usage: "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */
+//usage: " [ root | classid CLASSID ] [ handle FILTERID ]\n"
+//usage: " [ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n"
+//usage: "filter show [ dev STRING ] [ root | parent CLASSID ]"
+
+#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;
+ uint32_t filter_qdisc;
+ uint32_t filter_parent;
+ uint32_t filter_prio;
+ uint32_t filter_proto;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#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)
+#define INIT_G() do { } 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(uint32_t *h, const char *str) {
+ uint32_t 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(uint32_t *h, const char *str) {
+ uint32_t 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);
+//FIXME: check for "" too?
+ 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";
+ char 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:
+ 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 queuing */
+ 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 queuing */
+ /* 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)
+ )
+ ) {
+ /* nothing */
+ } 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) {
+ uint32_t 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
+ slash = strchr(handle, '/');
+ if (slash != NULL)
+ *slash = '\0';
+ */
+ msg.tcm_handle = get_u32(*argv, "handle");
+ /* if (slash) {if (get_u32(uint32_t &mask, slash+1, NULL)) 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");
+ filter_prio = get_u32(*argv, "priority");
+ } else if (arg == ARG_proto) { /* filter::list */
+ uint16_t 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("can't 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/ap/app/busybox/src/networking/tcpudp.c b/ap/app/busybox/src/networking/tcpudp.c
new file mode 100644
index 0000000..3df6a98
--- /dev/null
+++ b/ap/app/busybox/src/networking/tcpudp.c
@@ -0,0 +1,646 @@
+/* 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 source tree.
+ */
+
+/* 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.
+ */
+
+//usage:#define tcpsvd_trivial_usage
+//usage: "[-hEv] [-c N] [-C N[:MSG]] [-b N] [-u USER] [-l NAME] IP PORT PROG"
+/* with not-implemented options: */
+/* //usage: "[-hpEvv] [-c N] [-C N[:MSG]] [-b N] [-u USER] [-l NAME] [-i DIR|-x CDB] [-t SEC] IP PORT PROG" */
+//usage:#define tcpsvd_full_usage "\n\n"
+//usage: "Create TCP socket, bind to IP:PORT and listen\n"
+//usage: "for incoming connection. Run PROG for each connection.\n"
+//usage: "\n IP IP to listen on, 0 = all"
+//usage: "\n PORT Port to listen on"
+//usage: "\n PROG ARGS Program to run"
+//usage: "\n -l NAME Local hostname (else looks up local hostname in DNS)"
+//usage: "\n -u USER[:GRP] Change to user/group after bind"
+//usage: "\n -c N Handle up to N connections simultaneously"
+//usage: "\n -b N Allow a backlog of approximately N TCP SYNs"
+//usage: "\n -C N[:MSG] Allow only up to N connections from the same IP"
+//usage: "\n New connections from this IP address are closed"
+//usage: "\n immediately. MSG is written to the peer before close"
+//usage: "\n -h Look up peer's hostname"
+//usage: "\n -E Don't set up environment variables"
+//usage: "\n -v Verbose"
+//usage:
+//usage:#define udpsvd_trivial_usage
+//usage: "[-hEv] [-c N] [-u USER] [-l NAME] IP PORT PROG"
+//usage:#define udpsvd_full_usage "\n\n"
+//usage: "Create UDP socket, bind to IP:PORT and wait\n"
+//usage: "for incoming packets. Run PROG for each packet,\n"
+//usage: "redirecting all further packets with same peer ip:port to it.\n"
+//usage: "\n IP IP to listen on, 0 = all"
+//usage: "\n PORT Port to listen on"
+//usage: "\n PROG ARGS Program to run"
+//usage: "\n -l NAME Local hostname (else looks up local hostname in DNS)"
+//usage: "\n -u USER[:GRP] Change to user/group after bind"
+//usage: "\n -c N Handle up to N connections simultaneously"
+//usage: "\n -h Look up peer's hostname"
+//usage: "\n -E Don't set up environment variables"
+//usage: "\n -v Verbose"
+
+#include "libbb.h"
+
+/* Wants <limits.h> etc, thus included after libbb.h: */
+#ifdef __linux__
+#include <linux/types.h> /* for __be32 etc */
+#include <linux/netfilter_ipv4.h>
+#endif
+
+// 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 */
+} FIX_ALIASING;
+#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;
+ bb_unsetenv_and_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;
+ unsigned opts;
+
+ 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
+ opts = 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 */
+ opts = 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 (opts & 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 (opts & OPT_u) {
+ xget_uidgid(&ugid, user);
+ }
+#ifdef SSLSVD
+ if (opts & OPT_U) ssluser = optarg;
+ if (opts & OPT_slash) root = optarg;
+ if (opts & OPT_Z) cert = optarg;
+ if (opts & 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) && !(opts & OPT_u)) {
+ xfunc_exitcode = 100;
+ bb_error_msg_and_die(bb_msg_you_must_be_root);
+ }
+ if (opts & 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("can't initialize ssl");
+ if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) {
+ if (client)
+ fatal("can't read cert, key, or ca file");
+ fatal("can't read cert or key file");
+ }
+ if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0)
+ fatal("can't 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);
+ close_on_exec_on(sock);
+ } 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 (opts & OPT_u) {
+ /* drop permissions */
+ xsetgid(ugid.gid);
+ xsetuid(ugid.uid);
+ }
+#endif
+
+ if (verbose) {
+ char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa);
+ if (opts & OPT_u)
+ bb_error_msg("listening on %s, starting, uid %u, gid %u", addr,
+ (unsigned)ugid.uid, (unsigned)ugid.gid);
+ else
+ bb_error_msg("listening on %s, starting", addr);
+ free(addr);
+ }
+
+ /* 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->u.sa, 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 */
+
+ { /* 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 || !(opts & OPT_E)) {
+ if (!max_per_host) /* remote_addr is not yet known */
+ free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa);
+ if (opts & OPT_h) {
+ free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa);
+ if (!remote_hostname) {
+ bb_error_msg("can't 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 (opts & 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("can't 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((opts & OPT_h)
+ ? "start %u %s-%s (%s-%s)"
+ : "start %u %s-%s",
+ pid,
+ local_addr, remote_addr,
+ local_hostname, remote_hostname);
+ }
+
+ if (!(opts & OPT_E)) {
+ /* setup ucspi env */
+ const char *proto = tcp ? "TCP" : "UDP";
+
+#ifdef SO_ORIGINAL_DST
+ /* 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);
+ }
+#endif
+ xsetenv_plain("PROTO", proto);
+ xsetenv_proto(proto, "LOCALADDR", local_addr);
+ xsetenv_proto(proto, "REMOTEADDR", remote_addr);
+ if (opts & 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);
+ bb_perror_msg_and_die("can't execute '%s'", argv[0]);
+#else
+ BB_EXECVP_or_die(argv);
+#endif
+}
+
+/*
+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/ap/app/busybox/src/networking/tcpudp_perhost.c b/ap/app/busybox/src/networking/tcpudp_perhost.c
new file mode 100644
index 0000000..1054108
--- /dev/null
+++ b/ap/app/busybox/src/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 source tree.
+ */
+
+#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/ap/app/busybox/src/networking/tcpudp_perhost.h b/ap/app/busybox/src/networking/tcpudp_perhost.h
new file mode 100644
index 0000000..3e57576
--- /dev/null
+++ b/ap/app/busybox/src/networking/tcpudp_perhost.h
@@ -0,0 +1,33 @@
+/* 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 source tree.
+ */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+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);
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/ap/app/busybox/src/networking/telnet.c b/ap/app/busybox/src/networking/telnet.c
new file mode 100644
index 0000000..58a6919
--- /dev/null
+++ b/ap/app/busybox/src/networking/telnet.c
@@ -0,0 +1,666 @@
+/* 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 GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * 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>
+ *
+ */
+
+//usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+//usage:#define telnet_trivial_usage
+//usage: "[-a] [-l USER] HOST [PORT]"
+//usage:#define telnet_full_usage "\n\n"
+//usage: "Connect to telnet server\n"
+//usage: "\n -a Automatic login with $USER variable"
+//usage: "\n -l USER Automatic login as USER"
+//usage:
+//usage:#else
+//usage:#define telnet_trivial_usage
+//usage: "HOST [PORT]"
+//usage:#define telnet_full_usage "\n\n"
+//usage: "Connect to telnet server"
+//usage:#endif
+
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+#include "libbb.h"
+
+#ifdef __BIONIC__
+/* should be in arpa/telnet.h */
+# define IAC 255 /* interpret as command: */
+# define DONT 254 /* you are not to use option */
+# define DO 253 /* please, you use option */
+# define WONT 252 /* I won't use option */
+# define WILL 251 /* I will use option */
+# define SB 250 /* interpret as subnegotiation */
+# define SE 240 /* end sub negotiation */
+# define TELOPT_ECHO 1 /* echo */
+# define TELOPT_SGA 3 /* suppress go ahead */
+# define TELOPT_TTYPE 24 /* terminal type */
+# define TELOPT_NAWS 31 /* window size */
+#endif
+
+#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_NORMAL = 0,
+ TS_COPY = 1,
+ TS_IAC = 2,
+ TS_OPT = 3,
+ TS_SUB1 = 4,
+ TS_SUB2 = 5,
+ TS_CR = 6,
+};
+
+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;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+ struct G_sizecheck { \
+ char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
+ }; \
+} while (0)
+
+
+static void rawmode(void);
+static void cookmode(void);
+static void do_linemode(void);
+static void will_charmode(void);
+static void telopt(byte c);
+static void subneg(byte c);
+
+static void iac_flush(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 con_escape(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 ret;
+ }
+ break;
+ case 'c':
+ if (bb_got_signal) {
+ will_charmode();
+ goto ret;
+ }
+ 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();
+ ret:
+ bb_got_signal = 0;
+}
+
+static void handle_net_output(int len)
+{
+ byte outbuf[2 * DATABUFSIZE];
+ byte *dst = outbuf;
+ byte *src = (byte*)G.buf;
+ byte *end = src + len;
+
+ while (src < end) {
+ byte c = *src++;
+ if (c == 0x1d) {
+ con_escape();
+ return;
+ }
+ *dst = c;
+ if (c == IAC)
+ *++dst = c; /* IAC -> IAC IAC */
+ else
+ if (c == '\r' || c == '\n') {
+ /* Enter key sends '\r' in raw mode and '\n' in cooked one.
+ *
+ * See RFC 1123 3.3.1 Telnet End-of-Line Convention.
+ * Using CR LF instead of other allowed possibilities
+ * like CR NUL - easier to talk to HTTP/SMTP servers.
+ */
+ *dst = '\r'; /* Enter -> CR LF */
+ *++dst = '\n';
+ }
+ dst++;
+ }
+ if (dst - outbuf != 0)
+ full_write(netfd, outbuf, dst - outbuf);
+}
+
+static void handle_net_input(int len)
+{
+ int i;
+ int cstart = 0;
+
+ for (i = 0; i < len; i++) {
+ byte c = G.buf[i];
+
+ if (G.telstate == TS_NORMAL) { /* most typical state */
+ if (c == IAC) {
+ cstart = i;
+ G.telstate = TS_IAC;
+ }
+ else if (c == '\r') {
+ cstart = i + 1;
+ G.telstate = TS_CR;
+ }
+ /* No IACs were seen so far, no need to copy
+ * bytes within G.buf: */
+ continue;
+ }
+
+ switch (G.telstate) {
+ case TS_CR:
+ /* Prev char was CR. If cur one is NUL, ignore it.
+ * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling.
+ */
+ G.telstate = TS_COPY;
+ if (c == '\0')
+ break;
+ /* else: fall through - need to handle CR IAC ... properly */
+
+ case TS_COPY: /* Prev char was ordinary */
+ /* Similar to NORMAL, but in TS_COPY we need to copy bytes */
+ if (c == IAC)
+ G.telstate = TS_IAC;
+ else
+ G.buf[cstart++] = c;
+ if (c == '\r')
+ G.telstate = TS_CR;
+ break;
+
+ case TS_IAC: /* Prev char was IAC */
+ if (c == IAC) { /* IAC IAC -> one IAC */
+ G.buf[cstart++] = c;
+ G.telstate = TS_COPY;
+ 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;
+ /* DATA MARK must be added later */
+ default:
+ G.telstate = TS_COPY;
+ }
+ break;
+
+ case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */
+ telopt(c);
+ G.telstate = TS_COPY;
+ break;
+
+ case TS_SUB1: /* Subnegotiation */
+ case TS_SUB2: /* Subnegotiation */
+ subneg(c); /* can change G.telstate */
+ break;
+ }
+ }
+
+ if (G.telstate != TS_NORMAL) {
+ /* We had some IACs, or CR */
+ if (G.iaclen)
+ iac_flush();
+ if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */
+ G.telstate = TS_NORMAL;
+ len = cstart;
+ }
+
+ if (len)
+ full_write(STDOUT_FILENO, G.buf, len);
+}
+
+static void put_iac(int c)
+{
+ G.iacbuf[G.iaclen++] = c;
+}
+
+static void put_iac2(byte wwdd, byte c)
+{
+ if (G.iaclen + 3 > IACBUFSIZE)
+ iac_flush();
+
+ put_iac(IAC);
+ put_iac(wwdd);
+ put_iac(c);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void put_iac_subopt(byte c, char *str)
+{
+ int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 )
+
+ if (G.iaclen + len > IACBUFSIZE)
+ iac_flush();
+
+ put_iac(IAC);
+ put_iac(SB);
+ put_iac(c);
+ put_iac(0);
+
+ while (*str)
+ put_iac(*str++);
+
+ put_iac(IAC);
+ put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void put_iac_subopt_autologin(void)
+{
+ int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2)
+ const char *p = "USER";
+
+ if (G.iaclen + len > IACBUFSIZE)
+ iac_flush();
+
+ put_iac(IAC);
+ put_iac(SB);
+ put_iac(TELOPT_NEW_ENVIRON);
+ put_iac(TELQUAL_IS);
+ put_iac(NEW_ENV_VAR);
+
+ while (*p)
+ put_iac(*p++);
+
+ put_iac(NEW_ENV_VALUE);
+
+ p = G.autologin;
+ while (*p)
+ put_iac(*p++);
+
+ put_iac(IAC);
+ put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void put_iac_naws(byte c, int x, int y)
+{
+ if (G.iaclen + 9 > IACBUFSIZE)
+ iac_flush();
+
+ put_iac(IAC);
+ put_iac(SB);
+ put_iac(c);
+
+ put_iac((x >> 8) & 0xff);
+ put_iac(x & 0xff);
+ put_iac((y >> 8) & 0xff);
+ put_iac(y & 0xff);
+
+ put_iac(IAC);
+ put_iac(SE);
+}
+#endif
+
+static void setConMode(void)
+{
+ if (G.telflags & UF_ECHO) {
+ if (G.charmode == CHM_TRY) {
+ G.charmode = CHM_ON;
+ printf("\r\nEntering %s mode"
+ "\r\nEscape character is '^%c'.\r\n", "character", ']');
+ rawmode();
+ }
+ } else {
+ if (G.charmode != CHM_OFF) {
+ G.charmode = CHM_OFF;
+ printf("\r\nEntering %s mode"
+ "\r\nEscape character is '^%c'.\r\n", "line", 'C');
+ cookmode();
+ }
+ }
+}
+
+static void will_charmode(void)
+{
+ G.charmode = CHM_TRY;
+ G.telflags |= (UF_ECHO | UF_SGA);
+ setConMode();
+
+ put_iac2(DO, TELOPT_ECHO);
+ put_iac2(DO, TELOPT_SGA);
+ iac_flush();
+}
+
+static void do_linemode(void)
+{
+ G.charmode = CHM_TRY;
+ G.telflags &= ~(UF_ECHO | UF_SGA);
+ setConMode();
+
+ put_iac2(DONT, TELOPT_ECHO);
+ put_iac2(DONT, TELOPT_SGA);
+ iac_flush();
+}
+
+static void to_notsup(char c)
+{
+ if (G.telwish == WILL)
+ put_iac2(DONT, c);
+ else if (G.telwish == DO)
+ put_iac2(WONT, c);
+}
+
+static void to_echo(void)
+{
+ /* if server requests ECHO, don't agree */
+ if (G.telwish == DO) {
+ put_iac2(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)
+ put_iac2(DO, TELOPT_ECHO);
+ else
+ put_iac2(DONT, TELOPT_ECHO);
+
+ setConMode();
+ full_write1_str("\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)
+ put_iac2(DO, TELOPT_SGA);
+ else
+ put_iac2(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)
+ put_iac2(WILL, TELOPT_TTYPE);
+ else
+ put_iac2(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)
+ put_iac2(WILL, TELOPT_NEW_ENVIRON);
+ else
+ put_iac2(WONT, TELOPT_NEW_ENVIRON);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void to_naws(void)
+{
+ /* Tell server we will do NAWS */
+ put_iac2(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();
+ put_iac_naws(c, G.win_width, G.win_height);
+ break;
+#endif
+ default:
+ to_notsup(c);
+ break;
+ }
+}
+
+/* subnegotiation -- ignore all (except TTYPE,NAWS) */
+static void 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 && G.ttype)
+ put_iac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+ else
+ if (c == TELOPT_NEW_ENVIRON && G.autologin)
+ put_iac_subopt_autologin();
+#endif
+ break;
+ case TS_SUB2:
+ if (c == SE) {
+ G.telstate = TS_COPY;
+ return;
+ }
+ G.telstate = TS_SUB1;
+ break;
+ }
+}
+
+static void rawmode(void)
+{
+ if (G.do_termios)
+ tcsetattr(0, TCSADRAIN, &G.termios_raw);
+}
+
+static void cookmode(void)
+{
+ if (G.do_termios)
+ tcsetattr(0, TCSADRAIN, &G.termios_def);
+}
+
+int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnet_main(int argc UNUSED_PARAM, char **argv)
+{
+ char *host;
+ int port;
+ int len;
+ struct pollfd ufds[2];
+
+ 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);
+
+ ufds[0].fd = STDIN_FILENO;
+ ufds[0].events = POLLIN;
+ ufds[1].fd = netfd;
+ ufds[1].events = POLLIN;
+
+ while (1) {
+ if (poll(ufds, 2, -1) < 0) {
+ /* error, ignore and/or log something, bay go to loop */
+ if (bb_got_signal)
+ con_escape();
+ else
+ sleep(1);
+ continue;
+ }
+
+// FIXME: reads can block. Need full bidirectional buffering.
+
+ if (ufds[0].revents) {
+ len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE);
+ if (len <= 0)
+ doexit(EXIT_SUCCESS);
+ TRACE(0, ("Read con: %d\n", len));
+ handle_net_output(len);
+ }
+
+ if (ufds[1].revents) {
+ len = safe_read(netfd, G.buf, DATABUFSIZE);
+ if (len <= 0) {
+ full_write1_str("Connection closed by foreign host\r\n");
+ doexit(EXIT_FAILURE);
+ }
+ TRACE(0, ("Read netfd (%d): %d\n", netfd, len));
+ handle_net_input(len);
+ }
+ } /* while (1) */
+}
diff --git a/ap/app/busybox/src/networking/telnetd.c b/ap/app/busybox/src/networking/telnetd.c
new file mode 100644
index 0000000..9e7a84c
--- /dev/null
+++ b/ap/app/busybox/src/networking/telnetd.c
@@ -0,0 +1,748 @@
+/* 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 source tree.
+ *
+ * ---------------------------------------------------------------------------
+ * (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
+ */
+
+//usage:#define telnetd_trivial_usage
+//usage: "[OPTIONS]"
+//usage:#define telnetd_full_usage "\n\n"
+//usage: "Handle incoming telnet connections"
+//usage: IF_NOT_FEATURE_TELNETD_STANDALONE(" via inetd") "\n"
+//usage: "\n -l LOGIN Exec LOGIN on connect"
+//usage: "\n -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue"
+//usage: "\n -K Close connection as soon as login exits"
+//usage: "\n (normally wait until all programs close slave pty)"
+//usage: IF_FEATURE_TELNETD_STANDALONE(
+//usage: "\n -p PORT Port to listen on"
+//usage: "\n -b ADDR[:PORT] Address to bind to"
+//usage: "\n -F Run in foreground"
+//usage: "\n -i Inetd mode"
+//usage: IF_FEATURE_TELNETD_INETD_WAIT(
+//usage: "\n -w SEC Inetd 'wait' mode, linger time SEC"
+//usage: "\n -S Log to syslog (implied by -i or without -F and -w)"
+//usage: )
+//usage: )
+
+#define DEBUG 0
+
+#include "libbb.h"
+#include <syslog.h>
+
+#if DEBUG
+# define TELCMDS
+# define TELOPTS
+#endif
+#include <arpa/telnet.h>
+
+
+struct tsession {
+ struct tsession *next;
+ pid_t shell_pid;
+ int sockfd_read;
+ int sockfd_write;
+ int ptyfd;
+
+ /* two circular buffers */
+ /*char *buf1, *buf2;*/
+/*#define TS_BUF1(ts) ts->buf1*/
+/*#define TS_BUF2(ts) TS_BUF2(ts)*/
+#define TS_BUF1(ts) ((unsigned char*)(ts + 1))
+#define TS_BUF2(ts) (((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 */
+struct globals {
+ struct tsession *sessions;
+ const char *loginpath;
+ const char *issuefile;
+ int maxfd;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+ G.loginpath = "/bin/login"; \
+ G.issuefile = "/etc/issue.net"; \
+} while (0)
+
+
+/*
+ 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.
+
+ 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) + 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.
+ * See RFC 1123 3.3.1 Telnet End-of-Line Convention.
+ */
+ 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);
+}
+
+/*
+ * Converting single IAC into double on output
+ */
+static size_t iac_safe_write(int fd, const char *buf, size_t count)
+{
+ const char *IACptr;
+ size_t wr, rc, total;
+
+ total = 0;
+ while (1) {
+ if (count == 0)
+ return total;
+ if (*buf == (char)IAC) {
+ static const char IACIAC[] ALIGN1 = { IAC, IAC };
+ rc = safe_write(fd, IACIAC, 2);
+ if (rc != 2)
+ break;
+ buf++;
+ total++;
+ count--;
+ continue;
+ }
+ /* count != 0, *buf != IAC */
+ IACptr = memchr(buf, IAC, count);
+ wr = count;
+ if (IACptr)
+ wr = IACptr - buf;
+ rc = safe_write(fd, buf, wr);
+ if (rc != wr)
+ break;
+ buf += rc;
+ total += rc;
+ count -= rc;
+ }
+ /* here: rc - result of last short write */
+ if ((ssize_t)rc < 0) { /* error? */
+ if (total == 0)
+ return rc;
+ rc = 0;
+ }
+ return total + rc;
+}
+
+/* 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 PORT */
+ OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
+ OPT_SYSLOG = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */
+ OPT_WAIT = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */
+};
+
+static struct tsession *
+make_new_session(
+ IF_FEATURE_TELNETD_STANDALONE(int sock)
+ IF_NOT_FEATURE_TELNETD_STANDALONE(void)
+) {
+#if !ENABLE_FEATURE_TELNETD_STANDALONE
+ enum { sock = 0 };
+#endif
+ 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 > G.maxfd)
+ G.maxfd = fd;
+ ts->ptyfd = fd;
+ ndelay_on(fd);
+ close_on_exec_on(fd);
+
+ /* SO_KEEPALIVE by popular demand */
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ ts->sockfd_read = sock;
+ ndelay_on(sock);
+ if (sock == 0) { /* 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 > G.maxfd)
+ G.maxfd = sock;
+#else
+ /* 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,
+ /* This requires telnetd.ctrlSQ.patch (incomplete) */
+ /*IAC, DO, TELOPT_LFLOW,*/
+ IAC, WILL, TELOPT_ECHO,
+ IAC, WILL, TELOPT_SGA
+ };
+ /* This confuses iac_safe_write(), it will try to duplicate
+ * each IAC... */
+ //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send));
+ //ts->rdidx2 = sizeof(iacs_to_send);
+ //ts->size2 = sizeof(iacs_to_send);
+ /* So just stuff it into TCP stream! (no error check...) */
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
+#else
+ safe_write(1, iacs_to_send, sizeof(iacs_to_send));
+#endif
+ /*ts->rdidx2 = 0; - xzalloc did it */
+ /*ts->size2 = 0;*/
+ }
+
+ fflush_all();
+ 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! */
+
+ /* Restore default signal handling ASAP */
+ bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
+
+ pid = getpid();
+
+ if (ENABLE_FEATURE_UTMP) {
+ len_and_sockaddr *lsa = get_peer_lsa(sock);
+ char *hostname = NULL;
+ if (lsa) {
+ hostname = xmalloc_sockaddr2dotted(&lsa->u.sa);
+ free(lsa);
+ }
+ write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname);
+ free(hostname);
+ }
+
+ /* Make new session and process group */
+ setsid();
+
+ /* 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, pid); /* 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_all(),
+ * 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(G.issuefile, tty_name);
+
+ /* Exec shell / login / whatever */
+ login_argv[0] = G.loginpath;
+ login_argv[1] = NULL;
+ /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
+ * exec external program.
+ * NB: sock is either 0 or has CLOEXEC set on it.
+ * fd has CLOEXEC set on it too. These two fds will be closed here.
+ */
+ BB_EXECVP(G.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", G.loginpath);*/
+}
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+
+static void
+free_session(struct tsession *ts)
+{
+ struct tsession *t;
+
+ if (option_mask32 & OPT_INETD)
+ exit(EXIT_SUCCESS);
+
+ /* Unlink this telnet session from the session list */
+ t = G.sessions;
+ if (t == ts)
+ G.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);
+ waitpid(ts->shell_pid, NULL, 0);
+#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 */
+ G.maxfd = 0;
+ ts = G.sessions;
+ while (ts) {
+ if (G.maxfd < ts->ptyfd)
+ G.maxfd = ts->ptyfd;
+ if (G.maxfd < ts->sockfd_read)
+ G.maxfd = ts->sockfd_read;
+#if 0
+ /* Again, sockfd_write == sockfd_read here */
+ if (G.maxfd < ts->sockfd_write)
+ G.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;
+ int save_errno = errno;
+
+ /* Looping: more than one child may have exited */
+ while (1) {
+ pid = wait_any_nohang(NULL);
+ if (pid <= 0)
+ break;
+ ts = G.sessions;
+ while (ts) {
+ if (ts->shell_pid == pid) {
+ ts->shell_pid = -1;
+// man utmp:
+// When init(8) finds that a process has exited, it locates its utmp entry
+// by ut_pid, sets ut_type to DEAD_PROCESS, and clears ut_user, ut_host
+// and ut_time with null bytes.
+// [same applies to other processes which maintain utmp entries, like telnetd]
+//
+// We do not bother actually clearing fields:
+// it might be interesting to know who was logged in and from where
+ update_utmp(pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
+ break;
+ }
+ ts = ts->next;
+ }
+ }
+
+ errno = save_errno;
+}
+
+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; /* for compiler */
+ int sec_linger = sec_linger;
+ char *opt_bindaddr = NULL;
+ char *opt_portnbr;
+#else
+ enum {
+ IS_INETD = 1,
+ master_fd = -1,
+ };
+#endif
+ INIT_G();
+
+ /* -w NUM, and implies -F. -w and -i don't mix */
+ IF_FEATURE_TELNETD_INETD_WAIT(opt_complementary = "wF:w+:i--w:w--i";)
+ /* 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"
+ IF_FEATURE_TELNETD_STANDALONE("p:b:F")
+ IF_FEATURE_TELNETD_INETD_WAIT("Sw:"),
+ &G.issuefile, &G.loginpath
+ IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)
+ IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger)
+ );
+ 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_SYSLOG) || !(opt & OPT_FOREGROUND)) {
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ if (IS_INETD) {
+ G.sessions = make_new_session(0);
+ if (!G.sessions) /* pty opening or vfork problem, exit */
+ return 1; /* make_new_session printed error message */
+ } else {
+ master_fd = 0;
+ if (!(opt & OPT_WAIT)) {
+ unsigned portnbr = 23;
+ if (opt & OPT_PORT)
+ portnbr = xatou16(opt_portnbr);
+ master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
+ xlisten(master_fd, 1);
+ }
+ close_on_exec_on(master_fd);
+ }
+#else
+ G.sessions = make_new_session();
+ if (!G.sessions) /* pty opening or vfork problem, exit */
+ return 1; /* make_new_session printed 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 data flow.
+
+ +-------+ 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 = G.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 > G.maxfd)
+ G.maxfd = master_fd;
+ }
+
+ {
+ struct timeval *tv_ptr = NULL;
+#if ENABLE_FEATURE_TELNETD_INETD_WAIT
+ struct timeval tv;
+ if ((opt & OPT_WAIT) && !G.sessions) {
+ tv.tv_sec = sec_linger;
+ tv.tv_usec = 0;
+ tv_ptr = &tv;
+ }
+#endif
+ count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr);
+ }
+ if (count == 0) /* "telnetd -w SEC" timed out */
+ return 0;
+ if (count < 0)
+ goto again; /* EINTR or ENOMEM */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ /* 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;
+ close_on_exec_on(fd);
+
+ /* Create a new session and link it into active list */
+ new_ts = make_new_session(fd);
+ if (new_ts) {
+ new_ts->next = G.sessions;
+ G.sessions = new_ts;
+ } else {
+ close(fd);
+ }
+ }
+#endif
+
+ /* Then check for data tunneling */
+ ts = G.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 = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + 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) + 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)[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) + 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:
+ if (ts->shell_pid > 0)
+ update_utmp(ts->shell_pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
+ free_session(ts);
+ ts = next;
+ }
+
+ goto again;
+}
diff --git a/ap/app/busybox/src/networking/telnetd.ctrlSQ.patch b/ap/app/busybox/src/networking/telnetd.ctrlSQ.patch
new file mode 100644
index 0000000..7060e1c
--- /dev/null
+++ b/ap/app/busybox/src/networking/telnetd.ctrlSQ.patch
@@ -0,0 +1,175 @@
+From: "Doug Graham" <dgraham@nortel.com>
+Date: 2009-01-22 07:20
+
+Hello,
+
+Busybox's telnetd does not disable local (client-side) flow control
+properly. It does not put the pty into packet mode and then notify the
+client whenever flow control is disabled by an application running under
+its control. The result is that ^S/^Q are not passed through to the
+application, which is painful when the application is an emacs variant.
+
+I suppose that support for this might be considered bloat, but the
+included patch only adds about 200 bytes of text to x86 busybox and 300
+bytes to mipsel busybox. Please consider applying.
+
+=============================
+
+NB: the patch doesn't work as-is because we now have iac_safe_write()
+which quotes IACs on output.
+
+=============================
+Docs:
+
+The following ioctl(2) calls apply only to pseudo terminals:
+
+TIOCSTOP Stops output to a terminal (e.g. like typing ^S). Takes no parameter.
+
+TIOCSTART Restarts output (stopped by TIOCSTOP or by typing ^S). Takes no parameter.
+
+TIOCPKT Enable/disable packet mode. When applied to the master side of a pseudo terminal, each
+subsequent read(2) from the terminal will return data written on the slave part of the pseudo terminal preceded by a
+zero byte (symbolically defined as TIOCPKT_DATA), or a single byte reflecting control status information.
+In the latter case, the byte is an inclusive-or of zero or more of the bits:
+
+TIOCPKT_FLUSHREAD whenever the read queue for the terminal is flushed.
+TIOCPKT_FLUSHWRITE whenever the write queue for the terminal is flushed.
+TIOCPKT_STOP whenever output to the terminal is stopped a la ^S.
+TIOCPKT_START whenever output to the terminal is restarted.
+TIOCPKT_DOSTOP whenever t_stopc is ^S and t_startc is ^Q.
+TIOCPKT_NOSTOP whenever the start and stop characters are not ^S/^Q.
+
+While this mode is in use, the presence of control status information to be read from the master side may be detected
+by a select(2) for exceptional conditions.
+
+This mode is used by rlogin(1) and rlogind(8) to implement a remote-echoed, locally ^S/^Q flow-controlled remote login
+with proper back-flushing of output; it can be used by other similar programs.
+
+TIOCUCNTL Enable/disable a mode that allows a small number of simple user ioctl(2) commands to be passed through
+the pseudo-terminal, using a protocol similar to that of TIOCPKT. The TIOCUCNTL and TIOCPKT modes are mutually
+exclusive. This mode is enabled from the master side of a pseudo terminal. Each subsequent read(2) from the master side
+will return data written on the slave part of the pseudo terminal preceded by a zero byte, or a single byte reflecting a
+user control operation on the slave side. A user control command consists of a special ioctl(2) operation with no data;
+the command is given as UIOCCMD (n), where n is a number in the range 1-255. The operation value n will be received as
+a single byte on the next read(2) from the master side. The ioctl(2) UIOCCMD (0) is a no-op that may be used to probe
+for the existence of this facility. As with TIOCPKT mode, command operations may be detected with a select(2) for
+exceptional conditions.
+
+--- busybox-1.13.2/networking/telnetd.c 2009/01/21 20:02:39 1.1
++++ busybox-1.13.2/networking/telnetd.c 2009/01/22 00:35:28
+@@ -38,6 +38,9 @@
+ int sockfd_read, sockfd_write, ptyfd;
+ int shell_pid;
+
++#ifdef TIOCPKT
++ int flowstate;
++#endif
+ /* two circular buffers */
+ /*char *buf1, *buf2;*/
+ /*#define TS_BUF1 ts->buf1*/
+@@ -170,6 +173,9 @@
+ int fd, pid;
+ char tty_name[GETPTY_BUFSIZE];
+ struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
++#ifdef TIOCPKT
++ int on = 1;
++#endif
+
+ /*ts->buf1 = (char *)(ts + 1);*/
+ /*ts->buf2 = ts->buf1 + BUFSIZE;*/
+@@ -180,6 +186,10 @@
+ maxfd = fd;
+ ts->ptyfd = fd;
+ ndelay_on(fd);
++#ifdef TIOCPKT
++ ioctl(fd, TIOCPKT, &on);
++ ts->flowstate = TIOCPKT_DOSTOP;
++#endif
+ #if ENABLE_FEATURE_TELNETD_STANDALONE
+ ts->sockfd_read = sock;
+ /* SO_KEEPALIVE by popular demand */
+@@ -385,6 +395,16 @@
+ portnbr = 23,
+ };
+ #endif
++#ifdef TIOCPKT
++ int control;
++ static const char lflow_on[] =
++ {IAC, SB, TELOPT_LFLOW, LFLOW_ON, IAC, SE};
++ static const char lflow_off[] =
++ {IAC, SB, TELOPT_LFLOW, LFLOW_OFF, IAC, SE};
++# define RESERVED sizeof(lflow_on)
++#else
++# define RESERVED 0
++#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" IF_FEATURE_TELNETD_STANDALONE("p:b:F"),
+@@ -475,7 +495,7 @@
+ 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 */
++ if (ts->size2 < (BUFSIZE - RESERVED)) /* can read from pty */
+ FD_SET(ts->ptyfd, &rdfdset);
+ }
+ ts = next;
+@@ -593,6 +613,52 @@
+ goto skip4;
+ goto kill_session;
+ }
++#ifdef TIOCPKT
++ control = TS_BUF2[ts->rdidx2];
++ if (--count > 0 && control == TIOCPKT_DATA) {
++ /*
++ * If we are in packet mode, and we have
++ * just read a chunk of actual data from
++ * the pty, then there is the TIOCPKT_DATA
++ * byte (zero) that we have got to remove
++ * somehow. If there were no chars in
++ * TS_BUF2 before we did this read, then
++ * we can optimize by just advancing wridx2.
++ * Otherwise we have to copy the new data down
++ * to close the gap (Could use readv() instead).
++ */
++ if (ts->size2 == 0)
++ ts->wridx2++;
++ else {
++ memmove(TS_BUF2 + ts->rdidx2,
++ TS_BUF2 + ts->rdidx2 + 1, count);
++ }
++ }
++
++ /*
++ * If the flow control state changed, notify
++ * the client. If "control" is not TIOCPKT_DATA,
++ * then there are no data bytes to worry about.
++ */
++ if ((control & (TIOCPKT_DOSTOP|TIOCPKT_NOSTOP)) != 0
++ && ts->flowstate != (control & TIOCPKT_DOSTOP)) {
++ const char *p = ts->flowstate ? lflow_off : lflow_on;
++
++ /*
++ * We know we have enough free slots available
++ * (see RESERVED) but they are not necessarily
++ * contiguous; we may have to wrap.
++ */
++ for (count = sizeof(lflow_on); count > 0; count--) {
++ TS_BUF2[ts->rdidx2++] = *p++;
++ if (ts->rdidx2 >= BUFSIZE)
++ ts->rdidx2 = 0;
++ ts->size2++;
++ }
++
++ ts->flowstate = control & TIOCPKT_DOSTOP;
++ }
++#endif /* TIOCPKT */
+ ts->size2 += count;
+ ts->rdidx2 += count;
+ if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
+
+--Doug
+_______________________________________________
+busybox mailing list
+busybox@busybox.net
+http://lists.busybox.net/mailman/listinfo/busybox
diff --git a/ap/app/busybox/src/networking/tftp.c b/ap/app/busybox/src/networking/tftp.c
new file mode 100644
index 0000000..630fdaf
--- /dev/null
+++ b/ap/app/busybox/src/networking/tftp.c
@@ -0,0 +1,879 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 source tree.
+ */
+
+//usage:#define tftp_trivial_usage
+//usage: "[OPTIONS] HOST [PORT]"
+//usage:#define tftp_full_usage "\n\n"
+//usage: "Transfer a file from/to tftp server\n"
+//usage: "\n -l FILE Local FILE"
+//usage: "\n -r FILE Remote FILE"
+//usage: IF_FEATURE_TFTP_GET(
+//usage: "\n -g Get file"
+//usage: )
+//usage: IF_FEATURE_TFTP_PUT(
+//usage: "\n -p Put file"
+//usage: )
+//usage: IF_FEATURE_TFTP_BLOCKSIZE(
+//usage: "\n -b SIZE Transfer blocks of SIZE octets"
+//usage: )
+//usage:
+//usage:#define tftpd_trivial_usage
+//usage: "[-cr] [-u USER] [DIR]"
+//usage:#define tftpd_full_usage "\n\n"
+//usage: "Transfer a file on tftp client's request\n"
+//usage: "\n"
+//usage: "tftpd should be used as an inetd service.\n"
+//usage: "tftpd's line for inetd.conf:\n"
+//usage: " 69 dgram udp nowait root tftpd tftpd -l /files/to/serve\n"
+//usage: "It also can be ran from udpsvd:\n"
+//usage: " udpsvd -vE 0.0.0.0 69 tftpd /files/to/serve\n"
+//usage: "\n -r Prohibit upload"
+//usage: "\n -c Allow file creation via upload"
+//usage: "\n -u Access files as USER"
+//usage: "\n -l Log to syslog (inetd mode requires this)"
+
+#include "libbb.h"
+#include <syslog.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"
+/* Was 50 ms but users asked to bump it up a bit */
+#define TFTP_TIMEOUT_MS 100
+#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,
+ TFTPD_OPT_l = (1 << 11) * ENABLE_TFTPD,
+};
+
+#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
+#define IF_GETPUT(...)
+#define CMD_GET(cmd) 1
+#define CMD_PUT(cmd) 0
+#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
+#define IF_GETPUT(...)
+#define CMD_GET(cmd) 0
+#define CMD_PUT(cmd) 1
+#else
+#define IF_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];
+ struct passwd *pw;
+ /* used in tftpd_main(), a bit big for stack: */
+ char block_buf[TFTP_BLKSIZE_DEFAULT];
+#if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+ off_t pos;
+ off_t size;
+ const char *file;
+ bb_progress_t pmt;
+#endif
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define INIT_G() do { } while (0)
+
+#define G_error_pkt_reason (G.error_pkt[3])
+#define G_error_pkt_str ((char*)(G.error_pkt + 4))
+
+#if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+static void tftp_progress_update(void)
+{
+ bb_progress_update(&G.pmt, 0, G.pos, G.size);
+}
+static void tftp_progress_init(void)
+{
+ bb_progress_init(&G.pmt, G.file);
+ tftp_progress_update();
+}
+static void tftp_progress_done(void)
+{
+ if (is_bb_progress_inited(&G.pmt)) {
+ tftp_progress_update();
+ bb_putchar_stderr('\n');
+ bb_progress_free(&G.pmt);
+ }
+}
+#else
+# define tftp_progress_init() ((void)0)
+# define tftp_progress_done() ((void)0)
+#endif
+
+#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(
+ /* NULL if tftp, !NULL if tftpd: */
+ len_and_sockaddr *our_lsa,
+ len_and_sockaddr *peer_lsa,
+ const char *local_file
+ IF_TFTP(, const char *remote_file)
+#if !ENABLE_TFTP
+# define remote_file NULL
+#endif
+ /* 1 for tftp; 1/0 for tftpd depending whether client asked about it: */
+ IF_FEATURE_TFTP_BLOCKSIZE(, int want_transfer_size)
+ IF_FEATURE_TFTP_BLOCKSIZE(, int blksize))
+{
+#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;
+ IF_FEATURE_TFTP_BLOCKSIZE(smallint expect_OACK = 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 separate
+ * in case we rcv a garbage pkt - 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);
+
+ 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 (G_error_pkt_reason || G_error_pkt_str[0])
+ goto send_err_pkt;
+
+ if (G.pw) {
+ change_identity(G.pw); /* initgroups, setgid, setuid */
+ }
+ }
+
+ /* Prepare open mode */
+ 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
+ }
+
+ /* Examples of network traffic.
+ * Note two cases when ACKs with block# of 0 are sent.
+ *
+ * Download without options:
+ * tftp -> "\0\1FILENAME\0octet\0"
+ * "\0\3\0\1FILEDATA..." <- tftpd
+ * tftp -> "\0\4\0\1"
+ * ...
+ * Download with option of blksize 16384:
+ * tftp -> "\0\1FILENAME\0octet\0blksize\00016384\0"
+ * "\0\6blksize\00016384\0" <- tftpd
+ * tftp -> "\0\4\0\0"
+ * "\0\3\0\1FILEDATA..." <- tftpd
+ * tftp -> "\0\4\0\1"
+ * ...
+ * Upload without options:
+ * tftp -> "\0\2FILENAME\0octet\0"
+ * "\0\4\0\0" <- tftpd
+ * tftp -> "\0\3\0\1FILEDATA..."
+ * "\0\4\0\1" <- tftpd
+ * ...
+ * Upload with option of blksize 16384:
+ * tftp -> "\0\2FILENAME\0octet\0blksize\00016384\0"
+ * "\0\6blksize\00016384\0" <- tftpd
+ * tftp -> "\0\3\0\1FILEDATA..."
+ * "\0\4\0\1" <- tftpd
+ * ...
+ */
+ block_nr = 1;
+ cp = xbuf + 2;
+
+ if (!ENABLE_TFTP || our_lsa) { /* tftpd */
+ /* Open file (must be after changing user) */
+ local_fd = open(local_file, open_mode, 0666);
+ if (local_fd < 0) {
+ G_error_pkt_reason = ERR_NOFILE;
+ strcpy(G_error_pkt_str, "can't open file");
+ goto send_err_pkt;
+ }
+/* gcc 4.3.1 would NOT optimize it out as it should! */
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ if (blksize != TFTP_BLKSIZE_DEFAULT || want_transfer_size) {
+ /* 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
+ if (CMD_GET(option_mask32)) {
+ /* It's upload and we don't send OACK.
+ * We must ACK 1st packet (with filename)
+ * as if it is "block 0" */
+ block_nr = 0;
+ }
+
+ } else { /* tftp */
+ /* Open file (must be after changing user) */
+ local_fd = CMD_GET(option_mask32) ? STDOUT_FILENO : STDIN_FILENO;
+ if (NOT_LONE_DASH(local_file))
+ local_fd = xopen(local_file, open_mode);
+/* Removing #if, 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 packet */
+ strcpy(cp, "octet");
+ cp += sizeof("octet");
+
+# if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ if (blksize == TFTP_BLKSIZE_DEFAULT && !want_transfer_size)
+ goto send_pkt;
+
+ /* Need to add option to pkt */
+ if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN tsize ") + sizeof(off_t)*3) {
+ bb_error_msg("remote filename is too long");
+ goto ret;
+ }
+ expect_OACK = 1;
+# endif
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ add_blksize_opt:
+ if (blksize != TFTP_BLKSIZE_DEFAULT) {
+ /* add "blksize", <nul>, blksize, <nul> */
+ strcpy(cp, "blksize");
+ cp += sizeof("blksize");
+ cp += snprintf(cp, 6, "%d", blksize) + 1;
+ }
+ if (want_transfer_size) {
+ /* add "tsize", <nul>, size, <nul> (see RFC2349) */
+ /* if tftp and downloading, we send "0" (since we opened local_fd with O_TRUNC)
+ * and this makes server to send "tsize" option with the size */
+ /* if tftp and uploading, we send file size (maybe dont, to not confuse old servers???) */
+ /* if tftpd and downloading, we are answering to client's request */
+ /* if tftpd and uploading: !want_transfer_size, this code is not executed */
+ struct stat st;
+ strcpy(cp, "tsize");
+ cp += sizeof("tsize");
+ st.st_size = 0;
+ fstat(local_fd, &st);
+ cp += sprintf(cp, "%"OFF_FMT"u", (off_t)st.st_size) + 1;
+# if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+ /* Save for progress bar. If 0 (tftp downloading),
+ * we look at server's reply later */
+ G.size = st.st_size;
+ if (remote_file && st.st_size)
+ tftp_progress_init();
+# endif
+ }
+#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;
+ IF_FEATURE_TFTP_PROGRESS_BAR(G.pos += 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);
+
+#if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+ if (is_bb_progress_inited(&G.pmt))
+ tftp_progress_update();
+#endif
+ /* 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) {
+ tftp_progress_done();
+ 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 (expect_OACK) {
+ expect_OACK = 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) {
+ G_error_pkt_reason = ERR_BAD_OPT;
+ goto send_err_pkt;
+ }
+ io_bufsize = blksize + 4;
+ }
+# if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+ if (remote_file && G.size == 0) { /* if we don't know it yet */
+ res = tftp_get_option("tsize", &rbuf[2], len - 2);
+ if (res) {
+ G.size = bb_strtoull(res, NULL, 10);
+ if (G.size)
+ tftp_progress_init();
+ }
+ }
+# endif
+ if (CMD_GET(option_mask32)) {
+ /* We'll send ACK for OACK,
+ * such ACK has "block no" of 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." */
+ if (blksize != TFTP_BLKSIZE_DEFAULT)
+ bb_error_msg("falling back to blocksize "TFTP_BLKSIZE_DEFAULT_STR);
+ 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(G_error_pkt_str, bb_msg_write_error);
+ G_error_pkt_reason = ERR_WRITE;
+ goto send_err_pkt;
+ }
+ if (sz != blksize) {
+ finished = 1;
+ }
+ IF_FEATURE_TFTP_PROGRESS_BAR(G.pos += sz;)
+ continue; /* send ACK */
+ }
+/* Disabled to cope with servers with Sorcerer's Apprentice Syndrome */
+#if 0
+ if (recv_blk == (block_nr - 1)) {
+ /* Server lost our TFTP_ACK. Resend it */
+ block_nr = recv_blk;
+ continue;
+ }
+#endif
+ }
+
+ 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(G_error_pkt_str, bb_msg_read_error);
+ send_err_pkt:
+ if (G_error_pkt_str[0])
+ bb_error_msg("%s", G_error_pkt_str);
+ G.error_pkt[1] = TFTP_ERROR;
+ xsendto(socket_fd, G.error_pkt, 4 + 1 + strlen(G_error_pkt_str),
+ &peer_lsa->u.sa, peer_lsa->len);
+ return EXIT_FAILURE;
+#undef remote_file
+}
+
+#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;
+ IF_GETPUT(int opt;)
+
+ INIT_G();
+
+ /* -p or -g is mandatory, and they are mutually exclusive */
+ opt_complementary = "" IF_FEATURE_TFTP_GET("g:") IF_FEATURE_TFTP_PUT("p:")
+ IF_GETPUT("g--p:p--g:");
+
+ IF_GETPUT(opt =) getopt32(argv,
+ IF_FEATURE_TFTP_GET("g") IF_FEATURE_TFTP_PUT("p")
+ "l:r:" IF_FEATURE_TFTP_BLOCKSIZE("b:"),
+ &local_file, &remote_file
+ IF_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 (remote_file) {
+ if (!local_file) {
+ const char *slash = strrchr(remote_file, '/');
+ local_file = slash ? slash + 1 : remote_file;
+ }
+ } else {
+ 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
+
+# if ENABLE_FEATURE_TFTP_PROGRESS_BAR
+ G.file = remote_file;
+# endif
+ result = tftp_protocol(
+ NULL /*our_lsa*/, peer_lsa,
+ local_file, remote_file
+ IF_FEATURE_TFTP_BLOCKSIZE(, 1 /* want_transfer_size */)
+ IF_FEATURE_TFTP_BLOCKSIZE(, blksize)
+ );
+ tftp_progress_done();
+
+ if (result != EXIT_SUCCESS && NOT_LONE_DASH(local_file) && CMD_GET(opt)) {
+ unlink(local_file);
+ }
+ return result;
+}
+
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_TFTPD
+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, *user_opt;
+ const char *error_msg;
+ int opt, result, opcode;
+ IF_FEATURE_TFTP_BLOCKSIZE(int blksize = TFTP_BLKSIZE_DEFAULT;)
+ IF_FEATURE_TFTP_BLOCKSIZE(int want_transfer_size = 0;)
+
+ 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:l", &user_opt) << 8);
+ argv += optind;
+ if (opt & TFTPD_OPT_l) {
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+ if (opt & TFTPD_OPT_u) {
+ /* Must be before xchroot */
+ G.pw = xgetpwnam(user_opt);
+ }
+ if (argv[0]) {
+ xchroot(argv[0]);
+ }
+
+ result = recv_from_to(STDIN_FILENO, G.block_buf, sizeof(G.block_buf),
+ 0 /* flags */,
+ &peer_lsa->u.sa, &our_lsa->u.sa, our_lsa->len);
+
+ error_msg = "malformed packet";
+ opcode = ntohs(*(uint16_t*)G.block_buf);
+ if (result < 4 || result >= sizeof(G.block_buf)
+ || G.block_buf[result-1] != '\0'
+ || (IF_FEATURE_TFTP_PUT(opcode != TFTP_RRQ) /* not download */
+ IF_GETPUT(&&)
+ IF_FEATURE_TFTP_GET(opcode != TFTP_WRQ) /* not upload */
+ )
+ ) {
+ goto err;
+ }
+ local_file = G.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;
+ /* RFC 1350 says mode string is case independent */
+ if (mode >= G.block_buf + result || strcasecmp(mode, "octet") != 0) {
+ goto err;
+ }
+# if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ {
+ char *res;
+ char *opt_str = mode + sizeof("octet");
+ int opt_len = G.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) {
+ G_error_pkt_reason = ERR_BAD_OPT;
+ /* will just send error pkt */
+ goto do_proto;
+ }
+ }
+ if (opcode != TFTP_WRQ /* download? */
+ /* did client ask us about file size? */
+ && tftp_get_option("tsize", opt_str, opt_len)
+ ) {
+ want_transfer_size = 1;
+ }
+ }
+ }
+# endif
+
+ if (!ENABLE_FEATURE_TFTP_PUT || opcode == TFTP_WRQ) {
+ if (opt & TFTPD_OPT_r) {
+ /* This would mean "disk full" - not true */
+ /*G_error_pkt_reason = ERR_WRITE;*/
+ error_msg = bb_msg_write_error;
+ goto err;
+ }
+ IF_GETPUT(option_mask32 |= TFTP_OPT_GET;) /* will receive file's data */
+ } else {
+ IF_GETPUT(option_mask32 |= TFTP_OPT_PUT;) /* will send file's data */
+ }
+
+ /* NB: if G_error_pkt_str or G_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 IF_TFTP(, NULL /*remote_file*/)
+ IF_FEATURE_TFTP_BLOCKSIZE(, want_transfer_size)
+ IF_FEATURE_TFTP_BLOCKSIZE(, blksize)
+ );
+
+ return result;
+ err:
+ strcpy(G_error_pkt_str, error_msg);
+ goto do_proto;
+}
+
+#endif /* ENABLE_TFTPD */
+
+#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */
diff --git a/ap/app/busybox/src/networking/traceroute.c b/ap/app/busybox/src/networking/traceroute.c
new file mode 100644
index 0000000..6b7b2eb
--- /dev/null
+++ b/ap/app/busybox/src/networking/traceroute.c
@@ -0,0 +1,1234 @@
+/* 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.
+ */
+
+/*
+ * traceroute6
+ *
+ * Modified for NRL 4.4BSD IPv6 release.
+ * 07/31/96 bgp
+ *
+ * Modified for Linux IPv6 by Pedro Roque <roque@di.fc.ul.pt>
+ * 31/07/1996
+ *
+ * As ICMP error messages for IPv6 now include more than 8 bytes
+ * UDP datagrams are now sent via an UDP socket instead of magic
+ * RAW socket tricks.
+ *
+ * Converted to busybox applet by Leonid Lisovskiy <lly@sf.net>
+ * 2009-11-16
+ */
+
+/*
+ * 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
+ */
+
+//usage:#define traceroute_trivial_usage
+//usage: "[-"IF_TRACEROUTE6("46")"FIldnrv] [-f 1ST_TTL] [-m MAXTTL] [-p PORT] [-q PROBES]\n"
+//usage: " [-s SRC_IP] [-t TOS] [-w WAIT_SEC] [-g GATEWAY] [-i IFACE]\n"
+//usage: " [-z PAUSE_MSEC] HOST [BYTES]"
+//usage:#define traceroute_full_usage "\n\n"
+//usage: "Trace the route to HOST\n"
+//usage: IF_TRACEROUTE6(
+//usage: "\n -4,-6 Force IP or IPv6 name resolution"
+//usage: )
+//usage: "\n -F Set the don't fragment bit"
+//usage: "\n -I Use ICMP ECHO instead of UDP datagrams"
+//usage: "\n -l Display the TTL value of the returned packet"
+//usage: "\n -d Set SO_DEBUG options to socket"
+//usage: "\n -n Print numeric addresses"
+//usage: "\n -r Bypass routing tables, send directly to HOST"
+//usage: "\n -v Verbose"
+//usage: "\n -m Max time-to-live (max number of hops)"
+//usage: "\n -p Base UDP port number used in probes"
+//usage: "\n (default 33434)"
+//usage: "\n -q Number of probes per TTL (default 3)"
+//usage: "\n -s IP address to use as the source address"
+//usage: "\n -t Type-of-service in probe packets (default 0)"
+//usage: "\n -w Time in seconds to wait for a response (default 3)"
+//usage: "\n -g Loose source route gateway (8 max)"
+//usage:
+//usage:#define traceroute6_trivial_usage
+//usage: "[-dnrv] [-m MAXTTL] [-p PORT] [-q PROBES]\n"
+//usage: " [-s SRC_IP] [-t TOS] [-w WAIT_SEC] [-i IFACE]\n"
+//usage: " HOST [BYTES]"
+//usage:#define traceroute6_full_usage "\n\n"
+//usage: "Trace the route to HOST\n"
+//usage: "\n -d Set SO_DEBUG options to socket"
+//usage: "\n -n Print numeric addresses"
+//usage: "\n -r Bypass routing tables, send directly to HOST"
+//usage: "\n -v Verbose"
+//usage: "\n -m Max time-to-live (max number of hops)"
+//usage: "\n -p Base UDP port number used in probes"
+//usage: "\n (default is 33434)"
+//usage: "\n -q Number of probes per TTL (default 3)"
+//usage: "\n -s IP address to use as the source address"
+//usage: "\n -t Type-of-service in probe packets (default 0)"
+//usage: "\n -w Time in seconds to wait for a response (default 3)"
+
+#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>
+#if ENABLE_FEATURE_IPV6
+# include <netinet/ip6.h>
+# include <netinet/icmp6.h>
+# ifndef SOL_IPV6
+# define SOL_IPV6 IPPROTO_IPV6
+# endif
+#endif
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#ifndef IPPROTO_ICMP
+# define IPPROTO_ICMP 1
+#endif
+#ifndef IPPROTO_IP
+# define IPPROTO_IP 0
+#endif
+
+
+#define OPT_STRING \
+ "FIlnrdvxt:i:m:p:q:s:w:z:f:" \
+ IF_FEATURE_TRACEROUTE_SOURCE_ROUTE("g:") \
+ "4" IF_TRACEROUTE6("6")
+enum {
+ OPT_DONT_FRAGMNT = (1 << 0), /* F */
+ OPT_USE_ICMP = (1 << 1) * ENABLE_FEATURE_TRACEROUTE_USE_ICMP, /* I */
+ OPT_TTL_FLAG = (1 << 2), /* l */
+ OPT_ADDR_NUM = (1 << 3), /* n */
+ OPT_BYPASS_ROUTE = (1 << 4), /* r */
+ OPT_DEBUG = (1 << 5), /* d */
+ OPT_VERBOSE = (1 << 6) * ENABLE_FEATURE_TRACEROUTE_VERBOSE, /* v */
+ OPT_IP_CHKSUM = (1 << 7), /* x */
+ OPT_TOS = (1 << 8), /* t */
+ OPT_DEVICE = (1 << 9), /* i */
+ OPT_MAX_TTL = (1 << 10), /* m */
+ OPT_PORT = (1 << 11), /* p */
+ OPT_NPROBES = (1 << 12), /* q */
+ OPT_SOURCE = (1 << 13), /* s */
+ OPT_WAITTIME = (1 << 14), /* w */
+ OPT_PAUSE_MS = (1 << 15), /* z */
+ OPT_FIRST_TTL = (1 << 16), /* f */
+ OPT_SOURCE_ROUTE = (1 << 17) * ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE, /* g */
+ OPT_IPV4 = (1 << (17+ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE)), /* 4 */
+ OPT_IPV6 = (1 << (18+ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE)) * ENABLE_TRACEROUTE6, /* 6 */
+};
+#define verbose (option_mask32 & OPT_VERBOSE)
+
+enum {
+ SIZEOF_ICMP_HDR = 8,
+ rcvsock = 3, /* receive (icmp) socket file descriptor */
+ sndsock = 4, /* send (udp/icmp) socket file descriptor */
+};
+
+/* Data section of the probe packet */
+struct outdata_t {
+ 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 */
+};
+
+#if ENABLE_TRACEROUTE6
+struct outdata6_t {
+ uint32_t ident6;
+ uint32_t seq6;
+ struct timeval tv_UNUSED PACKED; /* time packet left */
+};
+#endif
+
+struct globals {
+ struct ip *outip;
+ struct outdata_t *outdata;
+ len_and_sockaddr *dest_lsa;
+ int packlen; /* total length of packet */
+ int pmtu; /* Path MTU Discovery (RFC1191) */
+ uint32_t ident;
+ uint16_t port; // 32768 + 666; /* start udp dest port # for probe packets */
+ int waittime; // 5; /* time to wait for response (in seconds) */
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ int optlen; /* length of ip options */
+#else
+#define optlen 0
+#endif
+ unsigned char recv_pkt[512]; /* last inbound (icmp) packet */
+#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 outdata (G.outdata )
+#define dest_lsa (G.dest_lsa )
+#define packlen (G.packlen )
+#define pmtu (G.pmtu )
+#define ident (G.ident )
+#define port (G.port )
+#define waittime (G.waittime )
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+# define optlen (G.optlen )
+#endif
+#define recv_pkt (G.recv_pkt )
+#define gwlist (G.gwlist )
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ port = 32768 + 666; \
+ waittime = 5; \
+} while (0)
+
+#define outicmp ((struct icmp *)(outip + 1))
+#define outudp ((struct udphdr *)(outip + 1))
+
+
+/* libbb candidate? tftp uses this idiom too */
+static len_and_sockaddr* dup_sockaddr(const len_and_sockaddr *lsa)
+{
+ len_and_sockaddr *new_lsa = xzalloc(LSA_LEN_SIZE + lsa->len);
+ memcpy(new_lsa, lsa, LSA_LEN_SIZE + lsa->len);
+ return new_lsa;
+}
+
+
+static int
+wait_for_reply(len_and_sockaddr *from_lsa, struct sockaddr *to, unsigned *timestamp_us, int *left_ms)
+{
+ struct pollfd pfd[1];
+ int read_len = 0;
+
+ pfd[0].fd = rcvsock;
+ pfd[0].events = POLLIN;
+ if (*left_ms >= 0 && safe_poll(pfd, 1, *left_ms) > 0) {
+ unsigned t;
+
+ read_len = recv_from_to(rcvsock,
+ recv_pkt, sizeof(recv_pkt),
+ /*flags:*/ MSG_DONTWAIT,
+ &from_lsa->u.sa, to, from_lsa->len);
+ t = monotonic_us();
+ *left_ms -= (t - *timestamp_us) / 1000;
+ *timestamp_us = t;
+ }
+
+ return read_len;
+}
+
+static void
+send_probe(int seq, int ttl)
+{
+ int len, res;
+ void *out;
+
+ /* Payload */
+#if ENABLE_TRACEROUTE6
+ if (dest_lsa->u.sa.sa_family == AF_INET6) {
+ struct outdata6_t *pkt = (struct outdata6_t *) outip;
+ pkt->ident6 = htonl(ident);
+ pkt->seq6 = htonl(seq);
+ /*gettimeofday(&pkt->tv, &tz);*/
+ } else
+#endif
+ {
+ 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 (option_mask32 & OPT_USE_ICMP) {
+ outicmp->icmp_seq = htons(seq);
+
+ /* Always calculate checksum for icmp packets */
+ outicmp->icmp_cksum = 0;
+ outicmp->icmp_cksum = inet_cksum((uint16_t *)outicmp,
+ packlen - (sizeof(*outip) + optlen));
+ if (outicmp->icmp_cksum == 0)
+ outicmp->icmp_cksum = 0xffff;
+ }
+ }
+
+//BUG! verbose is (x & OPT_VERBOSE), not a counter!
+#if 0 //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 ENABLE_TRACEROUTE6
+ if (dest_lsa->u.sa.sa_family == AF_INET6) {
+ res = setsockopt(sndsock, SOL_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
+ if (res < 0)
+ bb_perror_msg_and_die("setsockopt UNICAST_HOPS %d", ttl);
+ out = outip;
+ len = packlen;
+ } else
+#endif
+ {
+#if defined IP_TTL
+ res = setsockopt(sndsock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
+ if (res < 0)
+ bb_perror_msg_and_die("setsockopt ttl %d", ttl);
+#endif
+ out = outicmp;
+ len = packlen - sizeof(*outip);
+ if (!(option_mask32 & OPT_USE_ICMP)) {
+ out = outdata;
+ len -= sizeof(*outudp);
+ set_nport(&dest_lsa->u.sa, htons(port + seq));
+ }
+ }
+
+ res = xsendto(sndsock, out, len, &dest_lsa->u.sa, dest_lsa->len);
+ if (res != len)
+ bb_info_msg("sent %d octets, ret=%d", len, res);
+}
+
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+/*
+ * Convert an ICMP "type" field to a printable string.
+ */
+static 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 ENABLE_TRACEROUTE6
+ static const char *const ttab6[] = {
+[0] "Error", "Dest Unreachable", "Packet Too Big", "Time Exceeded",
+[4] "Param Problem",
+[8] "Echo Request", "Echo Reply", "Membership Query", "Membership Report",
+[12] "Membership Reduction", "Router Solicit", "Router Advert", "Neighbor Solicit",
+[16] "Neighbor Advert", "Redirect",
+ };
+
+ if (dest_lsa->u.sa.sa_family == AF_INET6) {
+ if (t < 5)
+ return ttab6[t];
+ if (t < 128 || t > ND_REDIRECT)
+ return "OUT-OF-RANGE";
+ return ttab6[(t & 63) + 8];
+ }
+# endif
+ if (t >= ARRAY_SIZE(ttab))
+ return "OUT-OF-RANGE";
+
+ return ttab[t];
+}
+#endif
+
+#if !ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define packet4_ok(read_len, from, seq) \
+ packet4_ok(read_len, seq)
+#endif
+static int
+packet4_ok(int read_len, const struct sockaddr_in *from, int seq)
+{
+ const struct icmp *icp;
+ unsigned char type, code;
+ int hlen;
+ const struct ip *ip;
+
+ ip = (struct ip *) recv_pkt;
+ hlen = ip->ip_hl << 2;
+ if (read_len < hlen + ICMP_MINLEN) {
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ if (verbose)
+ printf("packet too short (%d bytes) from %s\n", read_len,
+ inet_ntoa(from->sin_addr));
+#endif
+ return 0;
+ }
+ read_len -= hlen;
+ icp = (struct icmp *)(recv_pkt + hlen);
+ type = icp->icmp_type;
+ code = icp->icmp_code;
+ /* Path MTU Discovery (RFC1191) */
+ pmtu = 0;
+ if (code == ICMP_UNREACH_NEEDFRAG)
+ pmtu = ntohs(icp->icmp_nextmtu);
+
+ if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS)
+ || type == ICMP_UNREACH
+ || type == ICMP_ECHOREPLY
+ ) {
+ const struct ip *hip;
+ const struct udphdr *up;
+
+ hip = &icp->icmp_ip;
+ hlen = hip->ip_hl << 2;
+ if (option_mask32 & OPT_USE_ICMP) {
+ struct icmp *hicmp;
+
+ /* XXX */
+ if (type == ICMP_ECHOREPLY
+ && icp->icmp_id == htons(ident)
+ && icp->icmp_seq == htons(seq)
+ ) {
+ return ICMP_UNREACH_PORT+1;
+ }
+
+ hicmp = (struct icmp *)((unsigned char *)hip + hlen);
+ if (hlen + SIZEOF_ICMP_HDR <= read_len
+ && hip->ip_p == IPPROTO_ICMP
+ && hicmp->icmp_id == htons(ident)
+ && hicmp->icmp_seq == htons(seq)
+ ) {
+ return (type == ICMP_TIMXCEED ? -1 : code + 1);
+ }
+ } else {
+ up = (struct udphdr *)((char *)hip + hlen);
+ if (hlen + 12 <= read_len
+ && hip->ip_p == IPPROTO_UDP
+// Off: since we do not form the entire IP packet,
+// but defer it to kernel, we can't set source port,
+// and thus can't check it here in the reply
+ /* && 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",
+ read_len, inet_ntoa(from->sin_addr),
+ inet_ntoa(ip->ip_dst),
+ type, pr_type(type), icp->icmp_code);
+ for (i = 4; i < read_len; i += sizeof(*lp))
+ printf("%2d: x%8.8x\n", i, *lp++);
+ }
+#endif
+ return 0;
+}
+
+#if ENABLE_TRACEROUTE6
+# if !ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define packet_ok(read_len, from_lsa, to, seq) \
+ packet_ok(read_len, from_lsa, seq)
+# endif
+static int
+packet_ok(int read_len, len_and_sockaddr *from_lsa,
+ struct sockaddr *to,
+ int seq)
+{
+ const struct icmp6_hdr *icp;
+ unsigned char type, code;
+
+ if (from_lsa->u.sa.sa_family == AF_INET)
+ return packet4_ok(read_len, &from_lsa->u.sin, seq);
+
+ icp = (struct icmp6_hdr *) recv_pkt;
+
+ type = icp->icmp6_type;
+ code = icp->icmp6_code;
+
+ if ((type == ICMP6_TIME_EXCEEDED && code == ICMP6_TIME_EXCEED_TRANSIT)
+ || type == ICMP6_DST_UNREACH
+ ) {
+ struct ip6_hdr *hip;
+ struct udphdr *up;
+ int nexthdr;
+
+ hip = (struct ip6_hdr *)(icp + 1);
+ up = (struct udphdr *) (hip + 1);
+ nexthdr = hip->ip6_nxt;
+
+ if (nexthdr == IPPROTO_FRAGMENT) {
+ nexthdr = *(unsigned char*)up;
+ up++;
+ }
+ if (nexthdr == IPPROTO_UDP) {
+ struct outdata6_t *pkt;
+
+ pkt = (struct outdata6_t *) (up + 1);
+
+ if (ntohl(pkt->ident6) == ident
+ && ntohl(pkt->seq6) == seq
+ ) {
+ return (type == ICMP6_TIME_EXCEEDED ? -1 : (code<<8)+1);
+ }
+ }
+ }
+
+# if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ if (verbose) {
+ unsigned char *p;
+ char pa1[MAXHOSTNAMELEN];
+ char pa2[MAXHOSTNAMELEN];
+ int i;
+
+ p = (unsigned char *) (icp + 1);
+
+ printf("\n%d bytes from %s to "
+ "%s: icmp type %d (%s) code %d\n",
+ read_len,
+ inet_ntop(AF_INET6, &from_lsa->u.sin6.sin6_addr, pa1, sizeof(pa1)),
+ inet_ntop(AF_INET6, &((struct sockaddr_in6*)to)->sin6_addr, pa2, sizeof(pa2)),
+ type, pr_type(type), icp->icmp6_code);
+
+ read_len -= sizeof(struct icmp6_hdr);
+ for (i = 0; i < read_len; i++) {
+ if (i % 16 == 0)
+ printf("%04x:", i);
+ if (i % 4 == 0)
+ bb_putchar(' ');
+ printf("%02x", p[i]);
+ if ((i % 16 == 15) && (i + 1 < read_len))
+ bb_putchar('\n');
+ }
+ bb_putchar('\n');
+ }
+# endif
+
+ return 0;
+}
+#else /* !ENABLE_TRACEROUTE6 */
+static ALWAYS_INLINE int
+packet_ok(int read_len,
+ len_and_sockaddr *from_lsa IF_NOT_FEATURE_TRACEROUTE_VERBOSE(UNUSED_PARAM),
+ struct sockaddr *to UNUSED_PARAM,
+ int seq)
+{
+ return packet4_ok(read_len, &from_lsa->u.sin, seq);
+}
+#endif
+
+/*
+ * Construct an Internet address representation.
+ * If the -n flag has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+static void
+print_inetname(const struct sockaddr *from)
+{
+ char *ina = xmalloc_sockaddr2dotted_noport(from);
+
+ if (option_mask32 & OPT_ADDR_NUM) {
+ printf(" %s", ina);
+ } else {
+ char *n = NULL;
+
+ if (from->sa_family != AF_INET
+ || ((struct sockaddr_in*)from)->sin_addr.s_addr != INADDR_ANY
+ ) {
+ /* Try to reverse resolve if it is not 0.0.0.0 */
+ n = xmalloc_sockaddr2host_noport((struct sockaddr*)from);
+ }
+ printf(" %s (%s)", (n ? n : ina), ina);
+ free(n);
+ }
+ free(ina);
+}
+
+static void
+print(int read_len, const struct sockaddr *from, const struct sockaddr *to)
+{
+ print_inetname(from);
+
+ if (verbose) {
+ char *ina = xmalloc_sockaddr2dotted_noport(to);
+#if ENABLE_TRACEROUTE6
+ if (to->sa_family == AF_INET6) {
+ read_len -= sizeof(struct ip6_hdr);
+ } else
+#endif
+ {
+ struct ip *ip4packet = (struct ip*)recv_pkt;
+ read_len -= ip4packet->ip_hl << 2;
+ }
+ printf(" %d bytes to %s", read_len, ina);
+ free(ina);
+ }
+}
+
+static void
+print_delta_ms(unsigned t1p, unsigned t2p)
+{
+ unsigned tt = t2p - t1p;
+ printf(" %u.%03u ms", tt / 1000, tt % 1000);
+}
+
+/*
+ * Usage: [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]
+ * [-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]
+ * [-w waittime] [-z pausemsecs] host [packetlen]"
+ */
+static int
+common_traceroute_main(int op, char **argv)
+{
+ int minpacket;
+ int tos = 0;
+ int max_ttl = 30;
+ int nprobes = 3;
+ int first_ttl = 1;
+ unsigned pausemsecs = 0;
+ char *source;
+ char *device;
+ char *tos_str;
+ char *max_ttl_str;
+ char *port_str;
+ char *nprobes_str;
+ char *waittime_str;
+ char *pausemsecs_str;
+ char *first_ttl_str;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ llist_t *source_route_list = NULL;
+ int lsrr = 0;
+#endif
+#if ENABLE_TRACEROUTE6
+ sa_family_t af;
+#else
+ enum { af = AF_INET };
+#endif
+ int ttl;
+ int seq;
+ len_and_sockaddr *from_lsa;
+ struct sockaddr *lastaddr;
+ struct sockaddr *to;
+
+ INIT_G();
+
+ /* minimum 1 arg */
+ opt_complementary = "-1:x-x" IF_FEATURE_TRACEROUTE_SOURCE_ROUTE(":g::");
+ op |= getopt32(argv, OPT_STRING
+ , &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
+ );
+ argv += optind;
+
+#if 0 /* IGNORED */
+ if (op & OPT_IP_CHKSUM)
+ bb_error_msg("warning: ip checksums disabled");
+#endif
+ 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() != 0)
+ bb_error_msg_and_die(bb_msg_you_must_be_root);
+ }
+ if (op & OPT_WAITTIME)
+ waittime = xatou_range(waittime_str, 1, 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, max_ttl);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ if (source_route_list) {
+ while (source_route_list) {
+ len_and_sockaddr *lsa;
+
+ if (lsrr >= NGATEWAYS)
+ bb_error_msg_and_die("no more than %d gateways", NGATEWAYS);
+ lsa = xhost_and_af2sockaddr(llist_pop(&source_route_list), 0, AF_INET);
+ gwlist[lsrr] = lsa->u.sin.sin_addr.s_addr;
+ free(lsa);
+ ++lsrr;
+ }
+ optlen = (lsrr + 1) * sizeof(gwlist[0]);
+ }
+#endif
+
+ /* Process destination and optional packet size */
+ minpacket = sizeof(*outip) + SIZEOF_ICMP_HDR + sizeof(*outdata) + optlen;
+ if (!(op & OPT_USE_ICMP))
+ minpacket += sizeof(*outudp) - SIZEOF_ICMP_HDR;
+#if ENABLE_TRACEROUTE6
+ af = AF_UNSPEC;
+ if (op & OPT_IPV4)
+ af = AF_INET;
+ if (op & OPT_IPV6)
+ af = AF_INET6;
+ dest_lsa = xhost_and_af2sockaddr(argv[0], port, af);
+ af = dest_lsa->u.sa.sa_family;
+ if (af == AF_INET6)
+ minpacket = sizeof(struct outdata6_t);
+#else
+ dest_lsa = xhost2sockaddr(argv[0], port);
+#endif
+ packlen = minpacket;
+ if (argv[1])
+ packlen = xatoul_range(argv[1], minpacket, 32 * 1024);
+
+ /* Ensure the socket fds won't be 0, 1 or 2 */
+ bb_sanitize_stdio();
+
+#if ENABLE_TRACEROUTE6
+ if (af == AF_INET6) {
+ xmove_fd(xsocket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6), rcvsock);
+# ifdef IPV6_RECVPKTINFO
+ setsockopt(rcvsock, SOL_IPV6, IPV6_RECVPKTINFO,
+ &const_int_1, sizeof(const_int_1));
+ setsockopt(rcvsock, SOL_IPV6, IPV6_2292PKTINFO,
+ &const_int_1, sizeof(const_int_1));
+# else
+ setsockopt(rcvsock, SOL_IPV6, IPV6_PKTINFO,
+ &const_int_1, sizeof(const_int_1));
+# endif
+ } else
+#endif
+ {
+ xmove_fd(xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP), rcvsock);
+ }
+
+#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));
+
+#if ENABLE_TRACEROUTE6
+ if (af == AF_INET6) {
+ static const int two = 2;
+ if (setsockopt(rcvsock, SOL_RAW, IPV6_CHECKSUM, &two, sizeof(two)) < 0)
+ bb_perror_msg_and_die("setsockopt RAW_CHECKSUM");
+ xmove_fd(xsocket(af, SOCK_DGRAM, 0), sndsock);
+ } else
+#endif
+ {
+ if (op & OPT_USE_ICMP)
+ xmove_fd(xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP), sndsock);
+ else
+ xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), sndsock);
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE && defined IP_OPTIONS
+ if (lsrr > 0) {
+ unsigned char optlist[MAX_IPOPTLEN];
+ unsigned size;
+
+ /* final hop */
+ gwlist[lsrr] = dest_lsa->u.sin.sin_addr.s_addr;
+ ++lsrr;
+
+ /* force 4 byte alignment */
+ optlist[0] = IPOPT_NOP;
+ /* loose source route option */
+ optlist[1] = IPOPT_LSRR;
+ size = lsrr * sizeof(gwlist[0]);
+ optlist[2] = size + 3;
+ /* pointer to LSRR addresses */
+ optlist[3] = IPOPT_MINOFF;
+ memcpy(optlist + 4, gwlist, size);
+
+ if (setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS,
+ (char *)optlist, size + sizeof(gwlist[0])) < 0) {
+ bb_perror_msg_and_die("IP_OPTIONS");
+ }
+ }
+#endif
+ }
+
+#ifdef SO_SNDBUF
+ if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, &packlen, sizeof(packlen)) < 0) {
+ bb_perror_msg_and_die("SO_SNDBUF");
+ }
+#endif
+#ifdef IP_TOS
+ if ((op & OPT_TOS) && setsockopt(sndsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
+ bb_perror_msg_and_die("setsockopt tos %d", tos);
+ }
+#endif
+#ifdef IP_DONTFRAG
+ if (op & OPT_DONT_FRAGMNT)
+ setsockopt(sndsock, IPPROTO_IP, IP_DONTFRAG,
+ &const_int_1, sizeof(const_int_1));
+#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));
+
+ outip = xzalloc(packlen);
+
+ ident = getpid();
+
+ if (af == AF_INET) {
+ if (op & OPT_USE_ICMP) {
+ ident |= 0x8000;
+ outicmp->icmp_type = ICMP_ECHO;
+ outicmp->icmp_id = htons(ident);
+ outdata = (struct outdata_t *)((char *)outicmp + SIZEOF_ICMP_HDR);
+ } else {
+ outdata = (struct outdata_t *)(outudp + 1);
+ }
+ }
+
+ if (op & OPT_DEVICE) /* hmm, do we need error check? */
+ setsockopt_bindtodevice(sndsock, device);
+
+ if (op & OPT_SOURCE) {
+#if ENABLE_TRACEROUTE6
+// TODO: need xdotted_and_af2sockaddr?
+ len_and_sockaddr *source_lsa = xhost_and_af2sockaddr(source, 0, af);
+#else
+ len_and_sockaddr *source_lsa = xdotted2sockaddr(source, 0);
+#endif
+ /* Ping4 does this (why?) */
+ if (af == AF_INET)
+ if (setsockopt(sndsock, IPPROTO_IP, IP_MULTICAST_IF,
+ &source_lsa->u.sa, source_lsa->len))
+ bb_error_msg_and_die("can't set multicast source interface");
+//TODO: we can query source port we bound to,
+// and check it in replies... if we care enough
+ xbind(sndsock, &source_lsa->u.sa, source_lsa->len);
+ free(source_lsa);
+ }
+#if ENABLE_TRACEROUTE6
+ else if (af == AF_INET6) {
+//TODO: why we don't do it for IPv4?
+ len_and_sockaddr *source_lsa;
+
+ int probe_fd = xsocket(af, SOCK_DGRAM, 0);
+ if (op & OPT_DEVICE)
+ setsockopt_bindtodevice(probe_fd, device);
+ set_nport(&dest_lsa->u.sa, htons(1025));
+ /* dummy connect. makes kernel pick source IP (and port) */
+ xconnect(probe_fd, &dest_lsa->u.sa, dest_lsa->len);
+ set_nport(&dest_lsa->u.sa, htons(port));
+
+ /* read IP and port */
+ source_lsa = get_sock_lsa(probe_fd);
+ if (source_lsa == NULL)
+ bb_error_msg_and_die("can't get probe addr");
+
+ close(probe_fd);
+
+ /* bind our sockets to this IP (but not port) */
+ set_nport(&source_lsa->u.sa, 0);
+ xbind(sndsock, &source_lsa->u.sa, source_lsa->len);
+ xbind(rcvsock, &source_lsa->u.sa, source_lsa->len);
+
+ free(source_lsa);
+ }
+#endif
+
+ /* Revert to non-privileged user after opening sockets */
+ xsetgid(getgid());
+ xsetuid(getuid());
+
+ printf("traceroute to %s (%s)", argv[0],
+ xmalloc_sockaddr2dotted_noport(&dest_lsa->u.sa));
+ if (op & OPT_SOURCE)
+ printf(" from %s", source);
+ printf(", %d hops max, %d byte packets\n", max_ttl, packlen);
+
+ from_lsa = dup_sockaddr(dest_lsa);
+ lastaddr = xzalloc(dest_lsa->len);
+ to = xzalloc(dest_lsa->len);
+ seq = 0;
+ for (ttl = first_ttl; ttl <= max_ttl; ++ttl) {
+ int probe;
+ int unreachable = 0; /* counter */
+ int gotlastaddr = 0; /* flags */
+ int got_there = 0;
+
+ printf("%2d", ttl);
+ for (probe = 0; probe < nprobes; ++probe) {
+ int read_len;
+ unsigned t1;
+ unsigned t2;
+ int left_ms;
+ struct ip *ip;
+
+ fflush_all();
+ if (probe != 0 && pausemsecs > 0)
+ usleep(pausemsecs * 1000);
+
+ send_probe(++seq, ttl);
+ t2 = t1 = monotonic_us();
+
+ left_ms = waittime * 1000;
+ while ((read_len = wait_for_reply(from_lsa, to, &t2, &left_ms)) != 0) {
+ int icmp_code;
+
+ /* Recv'ed a packet, or read error */
+ /* t2 = monotonic_us() - set by wait_for_reply */
+
+ if (read_len < 0)
+ continue;
+ icmp_code = packet_ok(read_len, from_lsa, to, seq);
+ /* Skip short packet */
+ if (icmp_code == 0)
+ continue;
+
+ if (!gotlastaddr
+ || (memcmp(lastaddr, &from_lsa->u.sa, from_lsa->len) != 0)
+ ) {
+ print(read_len, &from_lsa->u.sa, to);
+ memcpy(lastaddr, &from_lsa->u.sa, from_lsa->len);
+ gotlastaddr = 1;
+ }
+
+ print_delta_ms(t1, t2);
+ ip = (struct ip *)recv_pkt;
+
+ if (from_lsa->u.sa.sa_family == AF_INET)
+ if (op & OPT_TTL_FLAG)
+ printf(" (%d)", ip->ip_ttl);
+
+ /* time exceeded in transit */
+ if (icmp_code == -1)
+ break;
+ icmp_code--;
+ switch (icmp_code) {
+#if ENABLE_TRACEROUTE6
+ case ICMP6_DST_UNREACH_NOPORT << 8:
+ got_there = 1;
+ break;
+#endif
+ case ICMP_UNREACH_PORT:
+ if (ip->ip_ttl <= 1)
+ printf(" !");
+ got_there = 1;
+ break;
+
+ case ICMP_UNREACH_NET:
+#if ENABLE_TRACEROUTE6 && (ICMP6_DST_UNREACH_NOROUTE != ICMP_UNREACH_NET)
+ case ICMP6_DST_UNREACH_NOROUTE << 8:
+#endif
+ printf(" !N");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_HOST:
+#if ENABLE_TRACEROUTE6
+ case ICMP6_DST_UNREACH_ADDR << 8:
+#endif
+ printf(" !H");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_PROTOCOL:
+ printf(" !P");
+ got_there = 1;
+ break;
+ case ICMP_UNREACH_NEEDFRAG:
+ printf(" !F-%d", pmtu);
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_SRCFAIL:
+#if ENABLE_TRACEROUTE6
+ case ICMP6_DST_UNREACH_ADMIN << 8:
+#endif
+ printf(" !S");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_FILTER_PROHIB:
+ case ICMP_UNREACH_NET_PROHIB: /* misuse */
+ printf(" !A");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_HOST_PROHIB:
+ printf(" !C");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_HOST_PRECEDENCE:
+ printf(" !V");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+ printf(" !C");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_NET_UNKNOWN:
+ case ICMP_UNREACH_HOST_UNKNOWN:
+ printf(" !U");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_ISOLATED:
+ printf(" !I");
+ ++unreachable;
+ break;
+ case ICMP_UNREACH_TOSNET:
+ case ICMP_UNREACH_TOSHOST:
+ printf(" !T");
+ ++unreachable;
+ break;
+ default:
+ printf(" !<%d>", icmp_code);
+ ++unreachable;
+ break;
+ }
+ break;
+ } /* while (wait and read a packet) */
+
+ /* there was no packet at all? */
+ if (read_len == 0)
+ printf(" *");
+ } /* for (nprobes) */
+
+ bb_putchar('\n');
+ if (got_there
+ || (unreachable > 0 && unreachable >= nprobes - 1)
+ ) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int traceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int traceroute_main(int argc UNUSED_PARAM, char **argv)
+{
+ return common_traceroute_main(0, argv);
+}
+
+#if ENABLE_TRACEROUTE6
+int traceroute6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int traceroute6_main(int argc UNUSED_PARAM, char **argv)
+{
+ return common_traceroute_main(OPT_IPV6, argv);
+}
+#endif
diff --git a/ap/app/busybox/src/networking/tunctl.c b/ap/app/busybox/src/networking/tunctl.c
new file mode 100644
index 0000000..3a0870e
--- /dev/null
+++ b/ap/app/busybox/src/networking/tunctl.c
@@ -0,0 +1,157 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tun devices controller
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Original code:
+ * Jeff Dike
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//usage:#define tunctl_trivial_usage
+//usage: "[-f device] ([-t name] | -d name)" IF_FEATURE_TUNCTL_UG(" [-u owner] [-g group] [-b]")
+//usage:#define tunctl_full_usage "\n\n"
+//usage: "Create or delete tun interfaces\n"
+//usage: "\n -f name tun device (/dev/net/tun)"
+//usage: "\n -t name Create iface 'name'"
+//usage: "\n -d name Delete iface 'name'"
+//usage: IF_FEATURE_TUNCTL_UG(
+//usage: "\n -u owner Set iface owner"
+//usage: "\n -g group Set iface group"
+//usage: "\n -b Brief output"
+//usage: )
+//usage:
+//usage:#define tunctl_example_usage
+//usage: "# tunctl\n"
+//usage: "# tunctl -d tun0\n"
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <linux/if_tun.h>
+#include "libbb.h"
+
+/* TUNSETGROUP appeared in 2.6.23 */
+#ifndef TUNSETGROUP
+#define TUNSETGROUP _IOW('T', 206, int)
+#endif
+
+#define IOCTL(a, b, c) ioctl_or_perror_and_die(a, b, c, NULL)
+
+#if 1
+
+int tunctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tunctl_main(int argc UNUSED_PARAM, char **argv)
+{
+ struct ifreq ifr;
+ int fd;
+ const char *opt_name = "tap%d";
+ const char *opt_device = "/dev/net/tun";
+#if ENABLE_FEATURE_TUNCTL_UG
+ const char *opt_user, *opt_group;
+ long user = -1, group = -1;
+#endif
+ unsigned opts;
+
+ enum {
+ OPT_f = 1 << 0, // control device name (/dev/net/tun)
+ OPT_t = 1 << 1, // create named interface
+ OPT_d = 1 << 2, // delete named interface
+#if ENABLE_FEATURE_TUNCTL_UG
+ OPT_u = 1 << 3, // set new interface owner
+ OPT_g = 1 << 4, // set new interface group
+ OPT_b = 1 << 5, // brief output
+#endif
+ };
+
+ opt_complementary = "=0:t--d:d--t"; // no arguments; t ^ d
+ opts = getopt32(argv, "f:t:d:" IF_FEATURE_TUNCTL_UG("u:g:b"),
+ &opt_device, &opt_name, &opt_name
+ IF_FEATURE_TUNCTL_UG(, &opt_user, &opt_group));
+
+ // select device
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+ strncpy_IFNAMSIZ(ifr.ifr_name, opt_name);
+
+ // open device
+ fd = xopen(opt_device, O_RDWR);
+ IOCTL(fd, TUNSETIFF, (void *)&ifr);
+
+ // delete?
+ if (opts & OPT_d) {
+ IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)0);
+ bb_info_msg("Set '%s' %spersistent", ifr.ifr_name, "non");
+ return EXIT_SUCCESS;
+ }
+
+ // create
+#if ENABLE_FEATURE_TUNCTL_UG
+ if (opts & OPT_g) {
+ group = xgroup2gid(opt_group);
+ IOCTL(fd, TUNSETGROUP, (void *)(uintptr_t)group);
+ } else
+ user = geteuid();
+ if (opts & OPT_u)
+ user = xuname2uid(opt_user);
+ IOCTL(fd, TUNSETOWNER, (void *)(uintptr_t)user);
+#endif
+ IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)1);
+
+ // show info
+#if ENABLE_FEATURE_TUNCTL_UG
+ if (opts & OPT_b) {
+ puts(ifr.ifr_name);
+ } else {
+ printf("Set '%s' %spersistent", ifr.ifr_name, "");
+ printf(" and owned by uid %ld", user);
+ if (group != -1)
+ printf(" gid %ld", group);
+ bb_putchar('\n');
+ }
+#else
+ puts(ifr.ifr_name);
+#endif
+ return EXIT_SUCCESS;
+}
+
+#else
+
+/* -210 bytes: */
+
+int tunctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tunctl_main(int argc UNUSED_PARAM, char **argv)
+{
+ struct ifreq ifr;
+ int fd;
+ const char *opt_name = "tap%d";
+ const char *opt_device = "/dev/net/tun";
+ unsigned opts;
+
+ enum {
+ OPT_f = 1 << 0, // control device name (/dev/net/tun)
+ OPT_t = 1 << 1, // create named interface
+ OPT_d = 1 << 2, // delete named interface
+ };
+
+ opt_complementary = "=0:t--d:d--t"; // no arguments; t ^ d
+ opts = getopt32(argv, "f:t:d:u:g:b", // u, g, b accepted and ignored
+ &opt_device, &opt_name, &opt_name, NULL, NULL);
+
+ // set interface name
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+ strncpy_IFNAMSIZ(ifr.ifr_name, opt_name);
+
+ // open device
+ fd = xopen(opt_device, O_RDWR);
+ IOCTL(fd, TUNSETIFF, (void *)&ifr);
+
+ // create or delete interface
+ IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)(0 == (opts & OPT_d)));
+
+ return EXIT_SUCCESS;
+}
+
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/Config.src b/ap/app/busybox/src/networking/udhcp/Config.src
new file mode 100644
index 0000000..6bfa398
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/Config.src
@@ -0,0 +1,154 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+INSERT
+
+config UDHCPD
+ bool "udhcp server (udhcpd)"
+ default y
+ select PLATFORM_LINUX
+ help
+ udhcpd is a DHCP server geared primarily toward embedded systems,
+ while striving to be fully functional and RFC compliant.
+
+config DHCPRELAY
+ bool "dhcprelay"
+ default y
+ depends on UDHCPD
+ help
+ dhcprelay listens for dhcp requests on one or more interfaces
+ and forwards these requests to a different interface or dhcp
+ server.
+
+config DUMPLEASES
+ bool "Lease display utility (dumpleases)"
+ default y
+ depends on 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 y
+ depends on 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 FEATURE_UDHCPD_BASE_IP_ON_MAC
+ bool "Select IP address based on client MAC"
+ default n
+ depends on UDHCPD
+ help
+ If selected, udhcpd will base its selection of IP address to offer
+ on the client's hardware address. Otherwise udhcpd uses the next
+ consecutive free address.
+
+ This reduces the frequency of IP address changes for clients
+ which let their lease expire, and makes consecutive DHCPOFFERS
+ for the same client to (almost always) contain the same
+ IP address.
+
+config DHCPD_LEASES_FILE
+ string "Absolute path to lease file"
+ default "/var/lib/misc/udhcpd.leases"
+ depends on 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 UDHCPC
+ bool "udhcp client (udhcpc)"
+ default y
+ select PLATFORM_LINUX
+ 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 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 UDHCPD || UDHCPC
+ help
+ At the cost of ~300 bytes, enables -P port option.
+ This feature is typically not needed.
+
+config UDHCP_DEBUG
+ int "Maximum verbosity level for udhcp applets (0..9)"
+ default 9
+ range 0 9
+ depends on UDHCPD || UDHCPC || DHCPRELAY
+ help
+ Verbosity can be increased with multiple -v options.
+ This option controls how high it can be cranked up.
+
+ Bigger values result in bigger code. Levels above 1
+ are very verbose and useful for debugging only.
+
+config FEATURE_UDHCP_RFC3397
+ bool "Support for RFC3397 domain search (experimental)"
+ default y
+ depends on UDHCPD || UDHCPC
+ help
+ If selected, both client and server will support passing of domain
+ search lists via option 119, specified in RFC 3397,
+ and SIP servers option 120, specified in RFC 3361.
+
+config FEATURE_UDHCP_8021Q
+ bool "Support for 802.1Q VLAN parameters"
+ default y
+ depends on UDHCPD || UDHCPC
+ help
+ If selected, both client and server will support passing of VLAN
+ ID and priority via options 132 and 133 as per 802.1Q.
+
+config UDHCPC_DEFAULT_SCRIPT
+ string "Absolute path to config script"
+ default "/usr/share/udhcpc/default.script"
+ depends on 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 UDHCPD || 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/ap/app/busybox/src/networking/udhcp/Kbuild.src b/ap/app/busybox/src/networking/udhcp/Kbuild.src
new file mode 100644
index 0000000..b8767ba
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/Kbuild.src
@@ -0,0 +1,21 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under GPLv2 or later, see file LICENSE in this source tree.
+#
+
+lib-y:=
+
+INSERT
+
+lib-$(CONFIG_UDHCPC) += common.o packet.o signalpipe.o socket.o
+lib-$(CONFIG_UDHCPD) += common.o packet.o signalpipe.o socket.o
+
+lib-$(CONFIG_UDHCPC) += dhcpc.o
+lib-$(CONFIG_UDHCPD) += dhcpd.o arpping.o files.o leases.o static_leases.o
+lib-$(CONFIG_DUMPLEASES) += dumpleases.o
+lib-$(CONFIG_DHCPRELAY) += dhcprelay.o
+
+lib-$(CONFIG_FEATURE_UDHCPC_ARPING) += arpping.o
+lib-$(CONFIG_FEATURE_UDHCP_RFC3397) += domain_codec.o
diff --git a/ap/app/busybox/src/networking/udhcp/arpping.c b/ap/app/busybox/src/networking/udhcp/arpping.c
new file mode 100644
index 0000000..b43e52e
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/arpping.c
@@ -0,0 +1,133 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mostly stolen from: dhcpcd - DHCP client daemon
+ * by Yoichi Hariguchi <yoichi@fore.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#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_nip,
+ const uint8_t *safe_mac,
+ 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("can't 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-filled */ /* target hardware address */
+ memcpy(arp.tInaddr, &test_nip, sizeof(test_nip));/* 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 {
+ typedef uint32_t aliased_uint32_t FIX_ALIASING;
+ int r;
+ unsigned prevTime = monotonic_ms();
+
+ pfd[0].events = POLLIN;
+ r = safe_poll(pfd, 1, timeout_ms);
+ if (r < 0)
+ break;
+ if (r) {
+ r = safe_read(s, &arp, sizeof(arp));
+ if (r < 0)
+ break;
+
+ //log3("sHaddr %02x:%02x:%02x:%02x:%02x:%02x",
+ // arp.sHaddr[0], arp.sHaddr[1], arp.sHaddr[2],
+ // arp.sHaddr[3], arp.sHaddr[4], arp.sHaddr[5]);
+
+ 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 */
+ && *(aliased_uint32_t*)arp.sInaddr == test_nip
+ ) {
+ /* if ARP source MAC matches safe_mac
+ * (which is client's MAC), then it's not a conflict
+ * (client simply already has this IP and replies to ARPs!)
+ */
+ if (!safe_mac || memcmp(safe_mac, arp.sHaddr, 6) != 0)
+ rv = 0;
+ //else log2("sHaddr == safe_mac");
+ break;
+ }
+ }
+ timeout_ms -= (unsigned)monotonic_ms() - prevTime + 1;
+
+ /* We used to check "timeout_ms > 0", but
+ * this is more under/overflow-resistant
+ * (people did see overflows here when system time jumps):
+ */
+ } while ((unsigned)timeout_ms <= 2000);
+
+ ret:
+ close(s);
+ log1("%srp reply received for this address", rv ? "No a" : "A");
+ return rv;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/common.c b/ap/app/busybox/src/networking/udhcp/common.c
new file mode 100755
index 0000000..6aa12c5
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/common.c
@@ -0,0 +1,622 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "common.h"
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+unsigned dhcp_verbose;
+#endif
+
+const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/* Supported options are easily added here.
+ * See RFC2132 for more options.
+ * OPTION_REQ: these options are requested by udhcpc (unless -o).
+ */
+const struct dhcp_optflag dhcp_optflags[] = {
+ /* 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_HOST | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */
+ { OPTION_U16 , 0x0d }, /* DHCP_BOOT_SIZE */
+ { OPTION_STRING_HOST | 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 */
+//TODO: why do we request DHCP_BROADCAST? Can't we assume that
+//in the unlikely case it is different from typical N.N.255.255,
+//server would let us know anyway?
+ { OPTION_IP | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST */
+ { OPTION_IP_PAIR | OPTION_LIST , 0x21 }, /* DHCP_ROUTES */
+ { OPTION_STRING_HOST , 0x28 }, /* DHCP_NIS_DOMAIN */
+ { OPTION_IP | OPTION_LIST , 0x29 }, /* DHCP_NIS_SERVER */
+ { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x2c }, /* DHCP_WINS_SERVER */
+ { OPTION_U32 , 0x33 }, /* DHCP_LEASE_TIME */
+ { OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */
+ { OPTION_STRING , 0x38 }, /* DHCP_ERR_MESSAGE */
+//TODO: must be combined with 'sname' and 'file' handling:
+ { OPTION_STRING_HOST , 0x42 }, /* DHCP_TFTP_SERVER_NAME */
+ { OPTION_STRING , 0x43 }, /* DHCP_BOOT_FILE */
+//TODO: not a string, but a set of LASCII strings:
+// { OPTION_STRING , 0x4D }, /* DHCP_USER_CLASS */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ { OPTION_DNS_STRING | OPTION_LIST , 0x77 }, /* DHCP_DOMAIN_SEARCH */
+ { OPTION_SIP_SERVERS , 0x78 }, /* DHCP_SIP_SERVERS */
+#endif
+ { OPTION_STATIC_ROUTES | OPTION_LIST , 0x79 }, /* DHCP_STATIC_ROUTES */
+#if ENABLE_FEATURE_UDHCP_8021Q
+ { OPTION_U16 , 0x84 }, /* DHCP_VLAN_ID */
+ { OPTION_U8 , 0x85 }, /* DHCP_VLAN_PRIORITY */
+#endif
+ { OPTION_6RD , 0xd4 }, /* DHCP_6RD */
+ { OPTION_STATIC_ROUTES | OPTION_LIST , 0xf9 }, /* DHCP_MS_STATIC_ROUTES */
+ { OPTION_STRING , 0xfc }, /* DHCP_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.
+ * These entries are only used internally by udhcp[cd]
+ * to correctly encode options into packets.
+ */
+
+ { OPTION_IP , 0x32 }, /* DHCP_REQUESTED_IP */
+ { OPTION_U8 , 0x35 }, /* DHCP_MESSAGE_TYPE */
+ { OPTION_U16 , 0x39 }, /* DHCP_MAX_SIZE */
+//looks like these opts will work just fine even without these defs:
+// { OPTION_STRING , 0x3c }, /* DHCP_VENDOR */
+// /* not really a string: */
+// { OPTION_STRING , 0x3d }, /* DHCP_CLIENT_ID */
+ { 0, 0 } /* zeroed terminating entry */
+};
+
+/* Used for converting options from incoming packets to env variables
+ * for udhcpc stript, and for setting options for udhcpd via
+ * "opt OPTION_NAME OPTION_VALUE" directives in udhcpd.conf file.
+ */
+/* Must match dhcp_optflags[] 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 */
+ "routes" "\0" /* DHCP_ROUTES */
+ "nisdomain" "\0" /* DHCP_NIS_DOMAIN */
+ "nissrv" "\0" /* DHCP_NIS_SERVER */
+ "ntpsrv" "\0" /* DHCP_NTP_SERVER */
+ "wins" "\0" /* DHCP_WINS_SERVER */
+ "lease" "\0" /* DHCP_LEASE_TIME */
+ "serverid" "\0" /* DHCP_SERVER_ID */
+ "message" "\0" /* DHCP_ERR_MESSAGE */
+ "tftp" "\0" /* DHCP_TFTP_SERVER_NAME */
+ "bootfile" "\0" /* DHCP_BOOT_FILE */
+// "userclass" "\0" /* DHCP_USER_CLASS */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ "search" "\0" /* DHCP_DOMAIN_SEARCH */
+// doesn't work in udhcpd.conf since OPTION_SIP_SERVERS
+// is not handled yet by "string->option" conversion code:
+ "sipsrv" "\0" /* DHCP_SIP_SERVERS */
+#endif
+ "staticroutes" "\0"/* DHCP_STATIC_ROUTES */
+#if ENABLE_FEATURE_UDHCP_8021Q
+ "vlanid" "\0" /* DHCP_VLAN_ID */
+ "vlanpriority" "\0"/* DHCP_VLAN_PRIORITY */
+#endif
+ "ip6rd" "\0" /* DHCP_6RD */
+ "msstaticroutes""\0"/* DHCP_MS_STATIC_ROUTES */
+ "wpad" "\0" /* DHCP_WPAD */
+ ;
+
+/* Lengths of the option types in binary form.
+ * Used by:
+ * udhcp_str2optset: to determine how many bytes to allocate.
+ * xmalloc_optname_optval: to estimate string length
+ * from binary option length: (option[LEN] / dhcp_option_lengths[opt_type])
+ * is the number of elements, multiply it by one element's string width
+ * (len_of_option_as_string[opt_type]) and you know how wide string you need.
+ */
+const uint8_t dhcp_option_lengths[] ALIGN1 = {
+ [OPTION_IP] = 4,
+ [OPTION_IP_PAIR] = 8,
+// [OPTION_BOOLEAN] = 1,
+ [OPTION_STRING] = 1, /* ignored by udhcp_str2optset */
+ [OPTION_STRING_HOST] = 1, /* ignored by udhcp_str2optset */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ [OPTION_DNS_STRING] = 1, /* ignored by both udhcp_str2optset and xmalloc_optname_optval */
+ [OPTION_SIP_SERVERS] = 1,
+#endif
+ [OPTION_U8] = 1,
+ [OPTION_U16] = 2,
+// [OPTION_S16] = 2,
+ [OPTION_U32] = 4,
+ [OPTION_S32] = 4,
+ /* Just like OPTION_STRING, we use minimum length here */
+ [OPTION_STATIC_ROUTES] = 5,
+ //hub CVE-2016-2148
+ [OPTION_6RD] = 12, /* ignored by udhcp_str2optset */
+ /* The above value was chosen as follows:
+ * len_of_option_as_string[] for this option is >60: it's a string of the form
+ * "32 128 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 255.255.255.255 ".
+ * Each additional ipv4 address takes 4 bytes in binary option and appends
+ * another "255.255.255.255 " 16-byte string. We can set [OPTION_6RD] = 4
+ * but this severely overestimates string length: instead of 16 bytes,
+ * it adds >60 for every 4 bytes in binary option.
+ * We cheat and declare here that option is in units of 12 bytes.
+ * This adds more than 60 bytes for every three ipv4 addresses - more than enough.
+ * (Even 16 instead of 12 should work, but let's be paranoid).
+ */
+};
+
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+static void log_option(const char *pfx, const uint8_t *opt)
+{
+ if (dhcp_verbose >= 2) {
+ char buf[256 * 2 + 2];
+ *bin2hex(buf, (void*) (opt + OPT_DATA), opt[OPT_LEN]) = '\0';
+ bb_info_msg("%s: 0x%02x %s", pfx, opt[OPT_CODE], buf);
+ }
+}
+#else
+# define log_option(pfx, opt) ((void)0)
+#endif
+
+unsigned FAST_FUNC udhcp_option_idx(const char *name)
+{
+ int n = index_in_strings(dhcp_option_strings, name);
+ if (n >= 0)
+ return n;
+
+ {
+ char buf[sizeof(dhcp_option_strings)];
+ char *d = buf;
+ const char *s = dhcp_option_strings;
+ while (s < dhcp_option_strings + sizeof(dhcp_option_strings) - 2) {
+ *d++ = (*s == '\0' ? ' ' : *s);
+ s++;
+ }
+ *d = '\0';
+ bb_error_msg_and_die("unknown option '%s', known options: %s", name, buf);
+ }
+}
+
+/* Get an option with bounds checking (warning, result is not aligned) */
+uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code)
+{
+ uint8_t *optionptr;
+ int len;
+ int rem;
+ int overload = 0;
+ enum {
+ FILE_FIELD101 = FILE_FIELD * 0x101,
+ SNAME_FIELD101 = SNAME_FIELD * 0x101,
+ };
+
+ /* option bytes: [code][len][data1][data2]..[dataLEN] */
+ optionptr = packet->options;
+ rem = sizeof(packet->options);
+ while (1) {
+ if (rem <= 0) {
+ complain:
+ bb_error_msg("bad packet, malformed option field");
+ return NULL;
+ }
+
+ /* DHCP_PADDING and DHCP_END have no [len] byte */
+ if (optionptr[OPT_CODE] == DHCP_PADDING) {
+ rem--;
+ optionptr++;
+ continue;
+ }
+ if (optionptr[OPT_CODE] == DHCP_END) {
+ if ((overload & FILE_FIELD101) == FILE_FIELD) {
+ /* can use packet->file, and didn't look at it yet */
+ overload |= FILE_FIELD101; /* "we looked at it" */
+ optionptr = packet->file;
+ rem = sizeof(packet->file);
+ continue;
+ }
+ if ((overload & SNAME_FIELD101) == SNAME_FIELD) {
+ /* can use packet->sname, and didn't look at it yet */
+ overload |= SNAME_FIELD101; /* "we looked at it" */
+ optionptr = packet->sname;
+ rem = sizeof(packet->sname);
+ continue;
+ }
+ break;
+ }
+
+ if (rem <= OPT_LEN)//CVE-2018-20679
+ goto complain; /* complain and return NULL */
+ len = 2 + optionptr[OPT_LEN];
+ rem -= len;
+ if (rem < 0)
+ goto complain; /* complain and return NULL */
+
+ if (optionptr[OPT_CODE] == code) {
+ if (optionptr[OPT_LEN] == 0) {
+ /* So far no valid option with length 0 known.
+ * Having this check means that searching
+ * for DHCP_MESSAGE_TYPE need not worry
+ * that returned pointer might be unsafe
+ * to dereference.CVE-2018-20679
+ */
+ goto complain; /* complain and return NULL */
+ }
+ log_option("option found", optionptr);
+ return optionptr + OPT_DATA;
+ }
+
+ if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) {
+ if (len >= 3)//CVE-2018-20679
+ overload |= optionptr[OPT_DATA];
+ /* fall through */
+ }
+ optionptr += len;
+ }
+
+ /* log3 because udhcpc uses it a lot - very noisy */
+ log3("option 0x%02x not found", code);
+ return NULL;
+}
+/*CVE-2018-20679*/
+uint8_t* FAST_FUNC udhcp_get_option32(struct dhcp_packet *packet, int code)
+{
+ uint8_t *r = udhcp_get_option(packet, code);
+ if (r) {
+ //if (r[-1] != 4)CVE-2019-5747
+ if (r[-OPT_DATA + OPT_LEN] != 4)
+ r = NULL;
+ }
+ return r;
+}
+
+/* Return the position of the 'end' option (no bounds checking) */
+int FAST_FUNC udhcp_end_option(uint8_t *optionptr)
+{
+ int i = 0;
+
+ while (optionptr[i] != DHCP_END) {
+ if (optionptr[i] != DHCP_PADDING)
+ i += optionptr[i + OPT_LEN] + OPT_DATA-1;
+ i++;
+ }
+ return i;
+}
+
+/* Add an option (supplied in binary form) to the options.
+ * Option format: [code][len][data1][data2]..[dataLEN]
+ */
+void FAST_FUNC udhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
+{
+ unsigned len;
+ uint8_t *optionptr = packet->options;
+ unsigned end = udhcp_end_option(optionptr);
+
+ len = OPT_DATA + addopt[OPT_LEN];
+ /* end position + (option code/length + addopt length) + end option */
+ if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE) {
+//TODO: learn how to use overflow option if we exhaust packet->options[]
+ bb_error_msg("option 0x%02x did not fit into the packet",
+ addopt[OPT_CODE]);
+ return;
+ }
+ log_option("Adding option", addopt);
+ memcpy(optionptr + end, addopt, len);
+ optionptr[end + len] = DHCP_END;
+}
+
+/* Add an one to four byte option to a packet */
+void FAST_FUNC udhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code, uint32_t data)
+{
+ const struct dhcp_optflag *dh;
+
+ for (dh = dhcp_optflags; dh->code; dh++) {
+ if (dh->code == code) {
+ uint8_t option[6], len;
+
+ option[OPT_CODE] = code;
+ len = dhcp_option_lengths[dh->flags & OPTION_TYPE_MASK];
+ option[OPT_LEN] = len;
+ if (BB_BIG_ENDIAN)
+ data <<= 8 * (4 - len);
+ /* Assignment is unaligned! */
+ move_to_unaligned32(&option[OPT_DATA], data);
+ udhcp_add_binary_option(packet, option);
+ return;
+ }
+ }
+
+ bb_error_msg("can't add option 0x%02x", code);
+}
+
+/* Find option 'code' in opt_list */
+struct option_set* FAST_FUNC udhcp_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;
+}
+
+/* Parse string to IP in network order */
+int FAST_FUNC udhcp_str2nip(const char *str, void *arg)
+{
+ len_and_sockaddr *lsa;
+
+ lsa = host_and_af2sockaddr(str, 0, AF_INET);
+ if (!lsa)
+ return 0;
+ /* arg maybe unaligned */
+ move_to_unaligned32((uint32_t*)arg, lsa->u.sin.sin_addr.s_addr);
+ free(lsa);
+ return 1;
+}
+
+/* udhcp_str2optset:
+ * Parse string option representation to binary form and add it to opt_list.
+ * Called to parse "udhcpc -x OPTNAME:OPTVAL"
+ * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives.
+ */
+/* helper for the helper */
+static char *allocate_tempopt_if_needed(
+ const struct dhcp_optflag *optflag,
+ char *buffer,
+ int *length_p)
+{
+ char *allocated = NULL;
+ if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) {
+ const char *end;
+ allocated = xstrdup(buffer); /* more than enough */
+ end = hex2bin(allocated, buffer, 255);
+ if (errno)
+ bb_error_msg_and_die("malformed hex string '%s'", buffer);
+ *length_p = end - allocated;
+ }
+ return allocated;
+}
+/* helper: add an option to the opt_list */
+static NOINLINE void attach_option(
+ struct option_set **opt_list,
+ const struct dhcp_optflag *optflag,
+ char *buffer,
+ int length)
+{
+ struct option_set *existing, *new, **curr;
+ char *allocated = NULL;
+
+ existing = udhcp_find_option(*opt_list, optflag->code);
+ if (!existing) {
+ log2("Attaching option %02x to list", optflag->code);
+ allocated = allocate_tempopt_if_needed(optflag, buffer, &length);
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) {
+ /* reuse buffer and length for RFC1035-formatted string */
+ allocated = buffer = (char *)dname_enc(NULL, 0, buffer, &length);
+ }
+#endif
+ /* make a new option */
+ new = xmalloc(sizeof(*new));
+ new->data = xmalloc(length + OPT_DATA);
+ new->data[OPT_CODE] = optflag->code;
+ new->data[OPT_LEN] = length;
+ memcpy(new->data + OPT_DATA, (allocated ? allocated : buffer), length);
+
+ curr = opt_list;
+ while (*curr && (*curr)->data[OPT_CODE] < optflag->code)
+ curr = &(*curr)->next;
+
+ new->next = *curr;
+ *curr = new;
+ goto ret;
+ }
+
+ if (optflag->flags & OPTION_LIST) {
+ unsigned old_len;
+
+ /* add it to an existing option */
+ log2("Attaching option %02x to existing member of list", optflag->code);
+ allocated = allocate_tempopt_if_needed(optflag, buffer, &length);
+ old_len = existing->data[OPT_LEN];
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) {
+ /* reuse buffer and length for RFC1035-formatted string */
+ allocated = buffer = (char *)dname_enc(existing->data + OPT_DATA, old_len, buffer, &length);
+ }
+#endif
+ if (old_len + length < 255) {
+ /* actually 255 is ok too, but adding a space can overlow it */
+
+ existing->data = xrealloc(existing->data, OPT_DATA + 1 + old_len + length);
+ if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING
+ || (optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING_HOST
+ ) {
+ /* add space separator between STRING options in a list */
+ existing->data[OPT_DATA + old_len] = ' ';
+ old_len++;
+ }
+ memcpy(existing->data + OPT_DATA + old_len, buffer, length);
+ existing->data[OPT_LEN] = old_len + length;
+ } /* else, ignore the data, we could put this in a second option in the future */
+ } /* else, ignore the new data */
+
+ ret:
+ free(allocated);
+}
+
+int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg)
+{
+ struct option_set **opt_list = arg;
+ char *opt, *val;
+ char *str;
+ const struct dhcp_optflag *optflag;
+ struct dhcp_optflag bin_optflag;
+ unsigned optcode;
+ int retval, length;
+ /* IP_PAIR needs 8 bytes, STATIC_ROUTES needs 9 max */
+ char buffer[9] ALIGNED(4);
+ uint16_t *result_u16 = (uint16_t *) buffer;
+ uint32_t *result_u32 = (uint32_t *) buffer;
+
+ /* Cheat, the only *const* str possible is "" */
+ str = (char *) const_str;
+ opt = strtok(str, " \t=");
+ if (!opt)
+ return 0;
+
+ optcode = bb_strtou(opt, NULL, 0);
+ if (!errno && optcode < 255) {
+ /* Raw (numeric) option code */
+ bin_optflag.flags = OPTION_BIN;
+ bin_optflag.code = optcode;
+ optflag = &bin_optflag;
+ } else {
+ optflag = &dhcp_optflags[udhcp_option_idx(opt)];
+ }
+
+ retval = 0;
+ do {
+ val = strtok(NULL, ", \t");
+ if (!val)
+ break;
+ length = dhcp_option_lengths[optflag->flags & OPTION_TYPE_MASK];
+ retval = 0;
+ opt = buffer; /* new meaning for variable opt */
+ switch (optflag->flags & OPTION_TYPE_MASK) {
+ case OPTION_IP:
+ retval = udhcp_str2nip(val, buffer);
+ break;
+ case OPTION_IP_PAIR:
+ retval = udhcp_str2nip(val, buffer);
+ val = strtok(NULL, ", \t/-");
+ if (!val)
+ retval = 0;
+ if (retval)
+ retval = udhcp_str2nip(val, buffer + 4);
+ break;
+ case OPTION_STRING:
+ case OPTION_STRING_HOST:
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ case OPTION_DNS_STRING:
+#endif
+ length = strnlen(val, 254);
+ if (length > 0) {
+ opt = val;
+ retval = 1;
+ }
+ break;
+// case OPTION_BOOLEAN: {
+// static const char no_yes[] ALIGN1 = "no\0yes\0";
+// buffer[0] = retval = index_in_strings(no_yes, val);
+// retval++; /* 0 - bad; 1: "no" 2: "yes" */
+// break;
+// }
+ case OPTION_U8:
+ buffer[0] = bb_strtou32(val, NULL, 0);
+ retval = (errno == 0);
+ break;
+ /* htonX are macros in older libc's, using temp var
+ * in code below for safety */
+ /* TODO: use bb_strtoX? */
+ case OPTION_U16: {
+ uint32_t tmp = bb_strtou32(val, NULL, 0);
+ *result_u16 = htons(tmp);
+ retval = (errno == 0 /*&& tmp < 0x10000*/);
+ break;
+ }
+// case OPTION_S16: {
+// long tmp = bb_strtoi32(val, NULL, 0);
+// *result_u16 = htons(tmp);
+// retval = (errno == 0);
+// break;
+// }
+ case OPTION_U32: {
+ uint32_t tmp = bb_strtou32(val, NULL, 0);
+ *result_u32 = htonl(tmp);
+ retval = (errno == 0);
+ break;
+ }
+ case OPTION_S32: {
+ int32_t tmp = bb_strtoi32(val, NULL, 0);
+ *result_u32 = htonl(tmp);
+ retval = (errno == 0);
+ break;
+ }
+ case OPTION_STATIC_ROUTES: {
+ /* Input: "a.b.c.d/m" */
+ /* Output: mask(1 byte),pfx(0-4 bytes),gw(4 bytes) */
+ unsigned mask;
+ char *slash = strchr(val, '/');
+ if (slash) {
+ *slash = '\0';
+ retval = udhcp_str2nip(val, buffer + 1);
+ buffer[0] = mask = bb_strtou(slash + 1, NULL, 10);
+ val = strtok(NULL, ", \t/-");
+ if (!val || mask > 32 || errno)
+ retval = 0;
+ if (retval) {
+ length = ((mask + 7) >> 3) + 5;
+ retval = udhcp_str2nip(val, buffer + (length - 4));
+ }
+ }
+ break;
+ }
+ case OPTION_BIN: /* handled in attach_option() */
+ opt = val;
+ retval = 1;
+ default:
+ break;
+ }
+ if (retval)
+ attach_option(opt_list, optflag, opt, length);
+ } while (retval && (optflag->flags & OPTION_LIST));
+
+ return retval;
+}
+
+/* note: ip is a pointer to an IPv6 in network order, possibly misaliged */
+int FAST_FUNC sprint_nip6(char *dest, /*const char *pre,*/ const uint8_t *ip)
+{
+ char hexstrbuf[16 * 2];
+ bin2hex(hexstrbuf, (void*)ip, 16);
+ return sprintf(dest, /* "%s" */
+ "%.4s:%.4s:%.4s:%.4s:%.4s:%.4s:%.4s:%.4s",
+ /* pre, */
+ hexstrbuf + 0 * 4,
+ hexstrbuf + 1 * 4,
+ hexstrbuf + 2 * 4,
+ hexstrbuf + 3 * 4,
+ hexstrbuf + 4 * 4,
+ hexstrbuf + 5 * 4,
+ hexstrbuf + 6 * 4,
+ hexstrbuf + 7 * 4
+ );
+}
diff --git a/ap/app/busybox/src/networking/udhcp/common.h b/ap/app/busybox/src/networking/udhcp/common.h
new file mode 100755
index 0000000..8cf6140
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/common.h
@@ -0,0 +1,323 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 source tree.
+ */
+#ifndef UDHCP_COMMON_H
+#define UDHCP_COMMON_H 1
+
+#include "libbb.h"
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+extern const uint8_t MAC_BCAST_ADDR[6] ALIGN2; /* six all-ones */
+
+
+/*** DHCP packet ***/
+
+/* DHCP protocol. See RFC 2131 */
+#define DHCP_MAGIC 0x63825363
+#define DHCP_OPTIONS_BUFSIZE 308
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+
+//TODO: rename ciaddr/yiaddr/chaddr
+struct dhcp_packet {
+ uint8_t op; /* BOOTREQUEST or 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 */
+ /* IP address of next server to use in bootstrap, returned in DHCPOFFER, DHCPACK by server */
+ uint32_t siaddr_nip;
+ uint32_t gateway_nip; /* 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;
+#define DHCP_PKT_SNAME_LEN 64
+#define DHCP_PKT_FILE_LEN 128
+#define DHCP_PKT_SNAME_LEN_STR "64"
+#define DHCP_PKT_FILE_LEN_STR "128"
+
+struct ip_udp_dhcp_packet {
+ struct iphdr ip;
+ struct udphdr udp;
+ struct dhcp_packet data;
+} PACKED;
+
+struct udp_dhcp_packet {
+ struct udphdr udp;
+ struct dhcp_packet data;
+} PACKED;
+
+enum {
+ IP_UDP_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+ UDP_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+ DHCP_SIZE = sizeof(struct dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+};
+
+/* Let's see whether compiler understood us right */
+struct BUG_bad_sizeof_struct_ip_udp_dhcp_packet {
+ char c[IP_UDP_DHCP_SIZE == 576 ? 1 : -1];
+};
+
+
+/*** Options ***/
+
+enum {
+ OPTION_IP = 1,
+ OPTION_IP_PAIR,
+ OPTION_STRING,
+ /* Opts of STRING_HOST type will be sanitized before they are passed
+ * to udhcpc script's environment: */
+ OPTION_STRING_HOST,
+// OPTION_BOOLEAN,
+ OPTION_U8,
+ OPTION_U16,
+// OPTION_S16,
+ OPTION_U32,
+ OPTION_S32,
+ OPTION_BIN,
+ OPTION_STATIC_ROUTES,
+ OPTION_6RD,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ OPTION_DNS_STRING, /* RFC1035 compressed domain name list */
+ OPTION_SIP_SERVERS,
+#endif
+
+ OPTION_TYPE_MASK = 0x0f,
+ /* Client requests this option by default */
+ OPTION_REQ = 0x10,
+ /* There can be a list of 1 or more of these */
+ OPTION_LIST = 0x20,
+};
+
+/* DHCP option codes (partial list). See RFC 2132 and
+ * http://www.iana.org/assignments/bootp-dhcp-parameters/
+ * Commented out options are handled by common option machinery,
+ * uncommented ones have special cases (grep for them to see).
+ */
+#define DHCP_PADDING 0x00
+#define DHCP_SUBNET 0x01
+//#define DHCP_TIME_OFFSET 0x02 /* (localtime - UTC_time) in seconds. signed */
+//#define DHCP_ROUTER 0x03
+//#define DHCP_TIME_SERVER 0x04 /* RFC 868 time server (32-bit, 0 = 1.1.1900) */
+//#define DHCP_NAME_SERVER 0x05 /* IEN 116 _really_ ancient kind of NS */
+//#define DHCP_DNS_SERVER 0x06
+//#define DHCP_LOG_SERVER 0x07 /* port 704 UDP log (not syslog) */
+//#define DHCP_COOKIE_SERVER 0x08 /* "quote of the day" server */
+//#define DHCP_LPR_SERVER 0x09
+#define DHCP_HOST_NAME 0x0c /* either client informs server or server gives name to client */
+//#define DHCP_BOOT_SIZE 0x0d
+//#define DHCP_DOMAIN_NAME 0x0f /* server gives domain suffix */
+//#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_ROUTES 0x21
+//#define DHCP_NIS_DOMAIN 0x28
+//#define DHCP_NIS_SERVER 0x29
+//#define DHCP_NTP_SERVER 0x2a
+//#define DHCP_WINS_SERVER 0x2c
+#define DHCP_REQUESTED_IP 0x32 /* sent by client if specific IP is wanted */
+#define DHCP_LEASE_TIME 0x33
+#define DHCP_OPTION_OVERLOAD 0x34
+#define DHCP_MESSAGE_TYPE 0x35
+#define DHCP_SERVER_ID 0x36 /* by default server's IP */
+#define DHCP_PARAM_REQ 0x37 /* list of options client wants */
+//#define DHCP_ERR_MESSAGE 0x38 /* error message when sending NAK etc */
+#define DHCP_MAX_SIZE 0x39
+#define DHCP_VENDOR 0x3c /* client's vendor (a string) */
+#define DHCP_CLIENT_ID 0x3d /* by default client's MAC addr, but may be arbitrarily long */
+//#define DHCP_TFTP_SERVER_NAME 0x42 /* same as 'sname' field */
+//#define DHCP_BOOT_FILE 0x43 /* same as 'file' field */
+//#define DHCP_USER_CLASS 0x4d /* RFC 3004. set of LASCII strings. "I am a printer" etc */
+#define DHCP_FQDN 0x51 /* client asks to update DNS to map its FQDN to its new IP */
+//#define DHCP_DOMAIN_SEARCH 0x77 /* RFC 3397. set of ASCIZ string, DNS-style compressed */
+//#define DHCP_SIP_SERVERS 0x78 /* RFC 3361. flag byte, then: 0: domain names, 1: IP addrs */
+//#define DHCP_STATIC_ROUTES 0x79 /* RFC 3442. (mask,ip,router) tuples */
+#define DHCP_VLAN_ID 0x84 /* 802.1P VLAN ID */
+#define DHCP_VLAN_PRIORITY 0x85 /* 802.1Q VLAN priority */
+//#define DHCP_MS_STATIC_ROUTES 0xf9 /* Microsoft's pre-RFC 3442 code for 0x79? */
+//#define DHCP_WPAD 0xfc /* MSIE's Web Proxy Autodiscovery Protocol */
+#define DHCP_END 0xff
+
+/* Offsets in option byte sequence */
+#define OPT_CODE 0
+#define OPT_LEN 1
+#define OPT_DATA 2
+/* Bits in "overload" option */
+#define OPTION_FIELD 0
+#define FILE_FIELD 1
+#define SNAME_FIELD 2
+
+/* DHCP_MESSAGE_TYPE values */
+#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 DHCP_MINTYPE DHCPDISCOVER
+#define DHCP_MAXTYPE DHCPINFORM
+
+struct dhcp_optflag {
+ uint8_t flags;
+ uint8_t code;
+};
+
+struct option_set {
+ uint8_t *data;
+ struct option_set *next;
+};
+
+extern const struct dhcp_optflag dhcp_optflags[];
+extern const char dhcp_option_strings[] ALIGN1;
+extern const uint8_t dhcp_option_lengths[] ALIGN1;
+
+unsigned FAST_FUNC udhcp_option_idx(const char *name);
+
+uint8_t *udhcp_get_option(struct dhcp_packet *packet, int code) FAST_FUNC;
+/* Same as above + ensures that option length is 4 bytes
+ * (returns NULL if size is different) CVE-2018-20679
+ */
+uint8_t *udhcp_get_option32(struct dhcp_packet *packet, int code) FAST_FUNC;
+int udhcp_end_option(uint8_t *optionptr) FAST_FUNC;
+void udhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt) FAST_FUNC;
+void udhcp_add_simple_option(struct dhcp_packet *packet, 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
+struct option_set *udhcp_find_option(struct option_set *opt_list, uint8_t code) FAST_FUNC;
+
+
+// RFC 2131 Table 5: Fields and options used by DHCP clients
+//
+// Fields 'hops', 'yiaddr', 'siaddr', 'giaddr' are always zero
+//
+// Field DHCPDISCOVER DHCPINFORM DHCPREQUEST DHCPDECLINE DHCPRELEASE
+// ----- ------------ ------------ ----------- ----------- -----------
+// 'xid' selected by client selected by client 'xid' from server selected by client selected by client
+// DHCPOFFER message
+// 'secs' 0 or seconds since 0 or seconds since 0 or seconds since 0 0
+// DHCP process started DHCP process started DHCP process started
+// 'flags' Set 'BROADCAST' Set 'BROADCAST' Set 'BROADCAST' 0 0
+// flag if client flag if client flag if client
+// requires broadcast requires broadcast requires broadcast
+// reply reply reply
+// 'ciaddr' 0 client's IP 0 or client's IP 0 client's IP
+// (BOUND/RENEW/REBIND)
+// 'chaddr' client's MAC client's MAC client's MAC client's MAC client's MAC
+// 'sname' options or sname options or sname options or sname (unused) (unused)
+// 'file' options or file options or file options or file (unused) (unused)
+// 'options' options options options message type opt message type opt
+//
+// Option DHCPDISCOVER DHCPINFORM DHCPREQUEST DHCPDECLINE DHCPRELEASE
+// ------ ------------ ---------- ----------- ----------- -----------
+// Requested IP address MAY MUST NOT MUST (in MUST MUST NOT
+// SELECTING or
+// INIT-REBOOT)
+// MUST NOT (in
+// BOUND or
+// RENEWING)
+// IP address lease time MAY MUST NOT MAY MUST NOT MUST NOT
+// Use 'file'/'sname' fields MAY MAY MAY MAY MAY
+// Client identifier MAY MAY MAY MAY MAY
+// Vendor class identifier MAY MAY MAY MUST NOT MUST NOT
+// Server identifier MUST NOT MUST NOT MUST (after MUST MUST
+// SELECTING)
+// MUST NOT (after
+// INIT-REBOOT,
+// BOUND, RENEWING
+// or REBINDING)
+// Parameter request list MAY MAY MAY MUST NOT MUST NOT
+// Maximum message size MAY MAY MAY MUST NOT MUST NOT
+// Message SHOULD NOT SHOULD NOT SHOULD NOT SHOULD SHOULD
+// Site-specific MAY MAY MAY MUST NOT MUST NOT
+// All others MAY MAY MAY MUST NOT MUST NOT
+
+
+/*** Logging ***/
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+# define IF_UDHCP_VERBOSE(...) __VA_ARGS__
+extern unsigned dhcp_verbose;
+# define log1(...) do { if (dhcp_verbose >= 1) bb_info_msg(__VA_ARGS__); } while (0)
+# if CONFIG_UDHCP_DEBUG >= 2
+void udhcp_dump_packet(struct dhcp_packet *packet) FAST_FUNC;
+# define log2(...) do { if (dhcp_verbose >= 2) bb_info_msg(__VA_ARGS__); } while (0)
+# else
+# define udhcp_dump_packet(...) ((void)0)
+# define log2(...) ((void)0)
+# endif
+# if CONFIG_UDHCP_DEBUG >= 3
+# define log3(...) do { if (dhcp_verbose >= 3) bb_info_msg(__VA_ARGS__); } while (0)
+# else
+# define log3(...) ((void)0)
+# endif
+#else
+# define IF_UDHCP_VERBOSE(...)
+# define udhcp_dump_packet(...) ((void)0)
+# define log1(...) ((void)0)
+# define log2(...) ((void)0)
+# define log3(...) ((void)0)
+#endif
+
+
+/*** Other shared functions ***/
+
+/* 2nd param is "uint32_t*" */
+int FAST_FUNC udhcp_str2nip(const char *str, void *arg);
+/* 2nd param is "struct option_set**" */
+int FAST_FUNC udhcp_str2optset(const char *str, void *arg);
+
+void udhcp_init_header(struct dhcp_packet *packet, char type) FAST_FUNC;
+
+int udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd) FAST_FUNC;
+
+int udhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_nip, int source_port,
+ uint32_t dest_nip, int dest_port, const uint8_t *dest_arp,
+ int ifindex) FAST_FUNC;
+
+int udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_nip, int source_port,
+ uint32_t dest_nip, int dest_port) FAST_FUNC;
+
+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 *nip, uint8_t *mac) 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_nip,
+ const uint8_t *safe_mac,
+ uint32_t from_ip,
+ uint8_t *from_mac,
+ const char *interface) FAST_FUNC;
+
+/* note: ip is a pointer to an IPv6 in network order, possibly misaliged */
+int sprint_nip6(char *dest, /*const char *pre,*/ const uint8_t *ip) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/d6_common.h b/ap/app/busybox/src/networking/udhcp/d6_common.h
new file mode 100644
index 0000000..eb211ea
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/d6_common.h
@@ -0,0 +1,127 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2011 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#ifndef UDHCP_D6_COMMON_H
+#define UDHCP_D6_COMMON_H 1
+
+#include <netinet/ip6.h>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+
+/*** DHCPv6 packet ***/
+
+/* DHCPv6 protocol. See RFC 3315 */
+#define D6_MSG_SOLICIT 1
+#define D6_MSG_ADVERTISE 2
+#define D6_MSG_REQUEST 3
+#define D6_MSG_CONFIRM 4
+#define D6_MSG_RENEW 5
+#define D6_MSG_REBIND 6
+#define D6_MSG_REPLY 7
+#define D6_MSG_RELEASE 8
+#define D6_MSG_DECLINE 9
+#define D6_MSG_RECONFIGURE 10
+#define D6_MSG_INFORMATION_REQUEST 11
+#define D6_MSG_RELAY_FORW 12
+#define D6_MSG_RELAY_REPL 13
+
+struct d6_packet {
+ union {
+ uint8_t d6_msg_type;
+ uint32_t d6_xid32;
+ } d6_u;
+ uint8_t d6_options[576 - sizeof(struct iphdr) - sizeof(struct udphdr) - 4
+ + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS];
+} PACKED;
+#define d6_msg_type d6_u.d6_msg_type
+#define d6_xid32 d6_u.d6_xid32
+
+struct ip6_udp_d6_packet {
+ struct ip6_hdr ip6;
+ struct udphdr udp;
+ struct d6_packet data;
+} PACKED;
+
+struct udp_d6_packet {
+ struct udphdr udp;
+ struct d6_packet data;
+} PACKED;
+
+/*** Options ***/
+
+struct d6_option {
+ uint8_t code_hi;
+ uint8_t code;
+ uint8_t len_hi;
+ uint8_t len;
+ uint8_t data[1];
+} PACKED;
+
+#define D6_OPT_CLIENTID 1
+#define D6_OPT_SERVERID 2
+#define D6_OPT_IA_NA 3
+#define D6_OPT_IA_TA 4
+#define D6_OPT_IAADDR 5
+#define D6_OPT_ORO 6
+#define D6_OPT_PREFERENCE 7
+#define D6_OPT_ELAPSED_TIME 8
+#define D6_OPT_RELAY_MSG 9
+#define D6_OPT_AUTH 11
+#define D6_OPT_UNICAST 12
+#define D6_OPT_STATUS_CODE 13
+#define D6_OPT_RAPID_COMMIT 14
+#define D6_OPT_USER_CLASS 15
+#define D6_OPT_VENDOR_CLASS 16
+#define D6_OPT_VENDOR_OPTS 17
+#define D6_OPT_INTERFACE_ID 18
+#define D6_OPT_RECONF_MSG 19
+#define D6_OPT_RECONF_ACCEPT 20
+
+#define D6_OPT_IA_PD 25
+#define D6_OPT_IAPREFIX 26
+
+/*** Other shared functions ***/
+
+struct client6_data_t {
+ struct d6_option *server_id;
+ struct d6_option *ia_na;
+ char **env_ptr;
+ unsigned env_idx;
+};
+
+#define client6_data (*(struct client6_data_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE - sizeof(struct client6_data_t)]))
+
+int FAST_FUNC d6_listen_socket(int port, const char *inf);
+
+int FAST_FUNC d6_recv_kernel_packet(
+ struct in6_addr *peer_ipv6,
+ struct d6_packet *packet, int fd
+);
+
+int FAST_FUNC d6_send_raw_packet(
+ struct d6_packet *d6_pkt, unsigned d6_pkt_size,
+ struct in6_addr *src_ipv6, int source_port,
+ struct in6_addr *dst_ipv6, int dest_port, const uint8_t *dest_arp,
+ int ifindex
+);
+
+int FAST_FUNC d6_send_kernel_packet(
+ struct d6_packet *d6_pkt, unsigned d6_pkt_size,
+ struct in6_addr *src_ipv6, int source_port,
+ struct in6_addr *dst_ipv6, int dest_port
+);
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+void FAST_FUNC d6_dump_packet(struct d6_packet *packet);
+#else
+# define d6_dump_packet(packet) ((void)0)
+#endif
+
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/d6_dhcpc.c b/ap/app/busybox/src/networking/udhcp/d6_dhcpc.c
new file mode 100644
index 0000000..c44220b
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/d6_dhcpc.c
@@ -0,0 +1,1492 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * DHCPv6 client.
+ *
+ * 2011-11.
+ * WARNING: THIS CODE IS INCOMPLETE. IT IS NOWHERE NEAR
+ * TO BE READY FOR PRODUCTION USE.
+ *
+ * Copyright (C) 2011 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//config:config UDHCPC6
+//config: bool "udhcp client for DHCPv6 (udhcpc6)"
+//config: default n # not yet ready
+//config: depends on FEATURE_IPV6
+//config: help
+//config: udhcpc6 is a DHCPv6 client
+
+//applet:IF_UDHCPC6(APPLET(udhcpc6, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_UDHCPC6) += d6_dhcpc.o d6_packet.o d6_socket.o common.o socket.o signalpipe.o
+
+
+#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 "d6_common.h"
+
+#include <netinet/if_ether.h>
+#include <netpacket/packet.h>
+#include <linux/filter.h>
+
+/* "struct client_config_t client_config" is in bb_common_bufsiz1 */
+
+
+#if ENABLE_LONG_OPTS
+static const char udhcpc6_longopts[] ALIGN1 =
+ "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"
+ "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"
+/// IF_FEATURE_UDHCPC_ARPING("arping\0" No_argument "a")
+ IF_FEATURE_UDHCP_PORT("client-port\0" Required_argument "P")
+ ;
+#endif
+/* Must match getopt32 option string order */
+enum {
+ OPT_i = 1 << 0,
+ OPT_n = 1 << 1,
+ OPT_p = 1 << 2,
+ OPT_q = 1 << 3,
+ OPT_R = 1 << 4,
+ OPT_r = 1 << 5,
+ OPT_s = 1 << 6,
+ OPT_T = 1 << 7,
+ OPT_t = 1 << 8,
+ OPT_S = 1 << 9,
+ OPT_A = 1 << 10,
+ OPT_O = 1 << 11,
+ OPT_o = 1 << 12,
+ OPT_x = 1 << 13,
+ OPT_f = 1 << 14,
+/* The rest has variable bit positions, need to be clever */
+ OPTBIT_f = 14,
+ USE_FOR_MMU( OPTBIT_b,)
+ ///IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
+ IF_FEATURE_UDHCP_PORT( OPTBIT_P,)
+ USE_FOR_MMU( OPT_b = 1 << OPTBIT_b,)
+ ///IF_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,)
+ IF_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,)
+};
+
+
+/*** Utility functions ***/
+
+static void *d6_find_option(uint8_t *option, uint8_t *option_end, unsigned code)
+{
+ /* "length minus 4" */
+ int len_m4 = option_end - option - 4;
+ while (len_m4 >= 0) {
+ /* Next option's len is too big? */
+ if (option[3] > len_m4)
+ return NULL; /* yes. bogus packet! */
+ /* So far we treat any opts with code >255
+ * or len >255 as bogus, and stop at once.
+ * This simplifies big-endian handling.
+ */
+ if (option[0] != 0 || option[2] != 0)
+ return NULL;
+ /* Option seems to be valid */
+ /* Does its code match? */
+ if (option[1] == code)
+ return option; /* yes! */
+ option += option[3] + 4;
+ len_m4 -= option[3] + 4;
+ }
+ return NULL;
+}
+
+static void *d6_copy_option(uint8_t *option, uint8_t *option_end, unsigned code)
+{
+ uint8_t *opt = d6_find_option(option, option_end, code);
+ if (!opt)
+ return opt;
+ return memcpy(xmalloc(opt[3] + 4), opt, opt[3] + 4);
+}
+
+static void *d6_store_blob(void *dst, const void *src, unsigned len)
+{
+ memcpy(dst, src, len);
+ return dst + len;
+}
+
+
+/*** Script execution code ***/
+
+static char** new_env(void)
+{
+ client6_data.env_ptr = xrealloc_vector(client6_data.env_ptr, 3, client6_data.env_idx);
+ return &client6_data.env_ptr[client6_data.env_idx++];
+}
+
+/* put all the parameters into the environment */
+static void option_to_env(uint8_t *option, uint8_t *option_end)
+{
+ /* "length minus 4" */
+ int len_m4 = option_end - option - 4;
+ while (len_m4 >= 0) {
+ uint32_t v32;
+ char ipv6str[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")];
+
+ if (option[0] != 0 || option[2] != 0)
+ break;
+
+ switch (option[1]) {
+ //case D6_OPT_CLIENTID:
+ //case D6_OPT_SERVERID:
+ case D6_OPT_IA_NA:
+ case D6_OPT_IA_PD:
+ option_to_env(option + 16, option + 4 + option[3]);
+ break;
+ //case D6_OPT_IA_TA:
+ case D6_OPT_IAADDR:
+/* 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IAADDR | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * | IPv6 address |
+ * | |
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | preferred-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | valid-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ sprint_nip6(ipv6str, option + 4);
+ *new_env() = xasprintf("ipv6=%s", ipv6str);
+
+ move_from_unaligned32(v32, option + 4 + 16 + 4);
+ *new_env() = xasprintf("lease=%u", (unsigned)v32);
+ break;
+
+ //case D6_OPT_ORO:
+ //case D6_OPT_PREFERENCE:
+ //case D6_OPT_ELAPSED_TIME:
+ //case D6_OPT_RELAY_MSG:
+ //case D6_OPT_AUTH:
+ //case D6_OPT_UNICAST:
+ //case D6_OPT_STATUS_CODE:
+ //case D6_OPT_RAPID_COMMIT:
+ //case D6_OPT_USER_CLASS:
+ //case D6_OPT_VENDOR_CLASS:
+ //case D6_OPT_VENDOR_OPTS:
+ //case D6_OPT_INTERFACE_ID:
+ //case D6_OPT_RECONF_MSG:
+ //case D6_OPT_RECONF_ACCEPT:
+
+ case D6_OPT_IAPREFIX:
+/* 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IAPREFIX | option-length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | preferred-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | valid-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | prefix-length | |
+ * +-+-+-+-+-+-+-+-+ IPv6 prefix |
+ * | (16 octets) |
+ * | |
+ * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ */
+ //move_from_unaligned32(v32, option + 4 + 4);
+ //*new_env() = xasprintf("lease=%u", (unsigned)v32);
+
+ sprint_nip6(ipv6str, option + 4 + 4 + 1);
+ *new_env() = xasprintf("ipv6prefix=%s/%u", ipv6str, (unsigned)(option[4 + 4]));
+ }
+ option += 4 + option[3];
+ len_m4 -= 4 + option[3];
+ }
+}
+
+static char **fill_envp(struct d6_packet *packet)
+{
+ char **envp, **curr;
+
+ client6_data.env_ptr = NULL;
+ client6_data.env_idx = 0;
+
+ *new_env() = xasprintf("interface=%s", client_config.interface);
+
+ if (packet)
+ option_to_env(packet->d6_options, packet->d6_options + sizeof(packet->d6_options));
+
+ envp = curr = client6_data.env_ptr;
+ while (*curr)
+ putenv(*curr++);
+
+ return envp;
+}
+
+/* Call a script with a par file and env vars */
+static void d6_run_script(struct d6_packet *packet, const char *name)
+{
+ char **envp, **curr;
+ char *argv[3];
+
+ envp = fill_envp(packet);
+
+ /* call script */
+ log1("Executing %s %s", client_config.script, name);
+ argv[0] = (char*) client_config.script;
+ argv[1] = (char*) name;
+ argv[2] = NULL;
+ spawn_and_wait(argv);
+
+ for (curr = envp; *curr; curr++) {
+ log2(" %s", *curr);
+ bb_unsetenv_and_free(*curr);
+ }
+ free(envp);
+}
+
+
+/*** Sending/receiving packets ***/
+
+static ALWAYS_INLINE uint32_t random_xid(void)
+{
+ uint32_t t = rand() & htonl(0x00ffffff);
+ return t;
+}
+
+/* Initialize the packet with the proper defaults */
+static uint8_t *init_d6_packet(struct d6_packet *packet, char type, uint32_t xid)
+{
+ struct d6_option *clientid;
+
+ memset(packet, 0, sizeof(*packet));
+
+ packet->d6_xid32 = xid;
+ packet->d6_msg_type = type;
+
+ clientid = (void*)client_config.clientid;
+ return d6_store_blob(packet->d6_options, clientid, clientid->len + 2+2);
+}
+
+static uint8_t *add_d6_client_options(uint8_t *ptr)
+{
+ return ptr;
+ //uint8_t c;
+ //int i, end, len;
+
+ /* Add a "param req" option with the list of options we'd like to have
+ * from stubborn DHCP servers. Pull the data from the struct in common.c.
+ * No bounds checking because it goes towards the head of the packet. */
+ //...
+
+ /* Add -x options if any */
+ //...
+}
+
+static int d6_mcast_from_client_config_ifindex(struct d6_packet *packet, uint8_t *end)
+{
+ static const uint8_t FF02__1_2[16] = {
+ 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ };
+
+ return d6_send_raw_packet(
+ packet, (end - (uint8_t*) packet),
+ /*src*/ NULL, CLIENT_PORT,
+ /*dst*/ (struct in6_addr*)FF02__1_2, SERVER_PORT, MAC_BCAST_ADDR,
+ client_config.ifindex
+ );
+}
+
+/* Milticast a DHCPv6 Solicit packet to the network, with an optionally requested IP.
+ *
+ * RFC 3315 17.1.1. Creation of Solicit Messages
+ *
+ * The client MUST include a Client Identifier option to identify itself
+ * to the server. The client includes IA options for any IAs to which
+ * it wants the server to assign addresses. The client MAY include
+ * addresses in the IAs as a hint to the server about addresses for
+ * which the client has a preference. ...
+ *
+ * The client uses IA_NA options to request the assignment of non-
+ * temporary addresses and uses IA_TA options to request the assignment
+ * of temporary addresses. Either IA_NA or IA_TA options, or a
+ * combination of both, can be included in DHCP messages.
+ *
+ * The client SHOULD include an Option Request option (see section 22.7)
+ * to indicate the options the client is interested in receiving. The
+ * client MAY additionally include instances of those options that are
+ * identified in the Option Request option, with data values as hints to
+ * the server about parameter values the client would like to have
+ * returned.
+ *
+ * The client includes a Reconfigure Accept option (see section 22.20)
+ * if the client is willing to accept Reconfigure messages from the
+ * server.
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | OPTION_CLIENTID | option-len |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ . .
+ . DUID .
+ . (variable length) .
+ . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | OPTION_IA_NA | option-len |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | IAID (4 octets) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | T1 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | T2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ . IA_NA-options .
+ . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | OPTION_IAADDR | option-len |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ | IPv6 address |
+ | |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | preferred-lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | valid-lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ . .
+ . IAaddr-options .
+ . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | OPTION_ORO | option-len |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | requested-option-code-1 | requested-option-code-2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ... |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | OPTION_RECONF_ACCEPT | 0 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ipv6)
+{
+ struct d6_packet packet;
+ uint8_t *opt_ptr;
+ unsigned len;
+
+ /* Fill in: msg type, client id */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_SOLICIT, xid);
+
+ /* Create new IA_NA, optionally with included IAADDR with requested IP */
+ free(client6_data.ia_na);
+ len = requested_ipv6 ? 2+2+4+4+4 + 2+2+16+4+4 : 2+2+4+4+4;
+ client6_data.ia_na = xzalloc(len);
+ client6_data.ia_na->code = D6_OPT_IA_NA;
+ client6_data.ia_na->len = len - 4;
+ *(uint32_t*)client6_data.ia_na->data = rand(); /* IAID */
+ if (requested_ipv6) {
+ struct d6_option *iaaddr = (void*)(client6_data.ia_na->data + 4+4+4);
+ iaaddr->code = D6_OPT_IAADDR;
+ iaaddr->len = 16+4+4;
+ memcpy(iaaddr->data, requested_ipv6, 16);
+ }
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, len);
+
+ /* Add options:
+ * "param req" option according to -O, options specified with -x
+ */
+ opt_ptr = add_d6_client_options(opt_ptr);
+
+ bb_info_msg("Sending discover...");
+ return d6_mcast_from_client_config_ifindex(&packet, opt_ptr);
+}
+
+/* Multicast a DHCPv6 request message
+ *
+ * RFC 3315 18.1.1. Creation and Transmission of Request Messages
+ *
+ * The client uses a Request message to populate IAs with addresses and
+ * obtain other configuration information. The client includes one or
+ * more IA options in the Request message. The server then returns
+ * addresses and other information about the IAs to the client in IA
+ * options in a Reply message.
+ *
+ * The client generates a transaction ID and inserts this value in the
+ * "transaction-id" field.
+ *
+ * The client places the identifier of the destination server in a
+ * Server Identifier option.
+ *
+ * The client MUST include a Client Identifier option to identify itself
+ * to the server. The client adds any other appropriate options,
+ * including one or more IA options (if the client is requesting that
+ * the server assign it some network addresses).
+ *
+ * The client MUST include an Option Request option (see section 22.7)
+ * to indicate the options the client is interested in receiving. The
+ * client MAY include options with data values as hints to the server
+ * about parameter values the client would like to have returned.
+ *
+ * The client includes a Reconfigure Accept option (see section 22.20)
+ * indicating whether or not the client is willing to accept Reconfigure
+ * messages from the server.
+ */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_d6_select(uint32_t xid)
+{
+ struct d6_packet packet;
+ uint8_t *opt_ptr;
+
+ /* Fill in: msg type, client id */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST, xid);
+
+ /* server id */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+ /* IA NA (contains requested IP) */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+
+ /* Add options:
+ * "param req" option according to -O, options specified with -x
+ */
+ opt_ptr = add_d6_client_options(opt_ptr);
+
+ bb_info_msg("Sending select...");
+ return d6_mcast_from_client_config_ifindex(&packet, opt_ptr);
+}
+
+/* Unicast or broadcast a DHCP renew message
+ *
+ * RFC 3315 18.1.3. Creation and Transmission of Renew Messages
+ *
+ * To extend the valid and preferred lifetimes for the addresses
+ * associated with an IA, the client sends a Renew message to the server
+ * from which the client obtained the addresses in the IA containing an
+ * IA option for the IA. The client includes IA Address options in the
+ * IA option for the addresses associated with the IA. The server
+ * determines new lifetimes for the addresses in the IA according to the
+ * administrative configuration of the server. The server may also add
+ * new addresses to the IA. The server may remove addresses from the IA
+ * by setting the preferred and valid lifetimes of those addresses to
+ * zero.
+ *
+ * The server controls the time at which the client contacts the server
+ * to extend the lifetimes on assigned addresses through the T1 and T2
+ * parameters assigned to an IA.
+ *
+ * At time T1 for an IA, the client initiates a Renew/Reply message
+ * exchange to extend the lifetimes on any addresses in the IA. The
+ * client includes an IA option with all addresses currently assigned to
+ * the IA in its Renew message.
+ *
+ * If T1 or T2 is set to 0 by the server (for an IA_NA) or there are no
+ * T1 or T2 times (for an IA_TA), the client may send a Renew or Rebind
+ * message, respectively, at the client's discretion.
+ *
+ * The client sets the "msg-type" field to RENEW. The client generates
+ * a transaction ID and inserts this value in the "transaction-id"
+ * field.
+ *
+ * The client places the identifier of the destination server in a
+ * Server Identifier option.
+ *
+ * The client MUST include a Client Identifier option to identify itself
+ * to the server. The client adds any appropriate options, including
+ * one or more IA options. The client MUST include the list of
+ * addresses the client currently has associated with the IAs in the
+ * Renew message.
+ *
+ * The client MUST include an Option Request option (see section 22.7)
+ * to indicate the options the client is interested in receiving. The
+ * client MAY include options with data values as hints to the server
+ * about parameter values the client would like to have returned.
+ */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_d6_renew(uint32_t xid, struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
+{
+ struct d6_packet packet;
+ uint8_t *opt_ptr;
+
+ /* Fill in: msg type, client id */
+ opt_ptr = init_d6_packet(&packet, DHCPREQUEST, xid);
+
+ /* server id */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+ /* IA NA (contains requested IP) */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+
+ /* Add options:
+ * "param req" option according to -O, options specified with -x
+ */
+ opt_ptr = add_d6_client_options(opt_ptr);
+
+ bb_info_msg("Sending renew...");
+ if (server_ipv6)
+ return d6_send_kernel_packet(
+ &packet, (opt_ptr - (uint8_t*) &packet),
+ our_cur_ipv6, CLIENT_PORT,
+ server_ipv6, SERVER_PORT
+ );
+ return d6_mcast_from_client_config_ifindex(&packet, opt_ptr);
+}
+
+/* Unicast a DHCP release message */
+static int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
+{
+ struct d6_packet packet;
+ uint8_t *opt_ptr;
+
+ /* Fill in: msg type, client id */
+ opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE, random_xid());
+ /* server id */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+ /* IA NA (contains our current IP) */
+ opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+
+ bb_info_msg("Sending release...");
+ return d6_send_kernel_packet(
+ &packet, (opt_ptr - (uint8_t*) &packet),
+ our_cur_ipv6, CLIENT_PORT,
+ server_ipv6, SERVER_PORT
+ );
+}
+
+/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int d6_recv_raw_packet(struct in6_addr *peer_ipv6
+ UNUSED_PARAM
+ , struct d6_packet *d6_pkt, int fd)
+{
+ int bytes;
+ struct ip6_udp_d6_packet packet;
+
+ bytes = safe_read(fd, &packet, sizeof(packet));
+ if (bytes < 0) {
+ log1("Packet read error, ignoring");
+ /* NB: possible down interface, etc. Caller should pause. */
+ return bytes; /* returns -1 */
+ }
+
+ if (bytes < (int) (sizeof(packet.ip6) + sizeof(packet.udp))) {
+ log1("Packet is too short, ignoring");
+ return -2;
+ }
+
+ if (bytes < sizeof(packet.ip6) + ntohs(packet.ip6.ip6_plen)) {
+ /* packet is bigger than sizeof(packet), we did partial read */
+ log1("Oversized packet, ignoring");
+ return -2;
+ }
+
+ /* ignore any extra garbage bytes */
+ bytes = sizeof(packet.ip6) + ntohs(packet.ip6.ip6_plen);
+
+ /* make sure its the right packet for us, and that it passes sanity checks */
+ if (packet.ip6.ip6_nxt != IPPROTO_UDP
+ || (packet.ip6.ip6_vfc >> 4) != 6
+ || packet.udp.dest != htons(CLIENT_PORT)
+ /* || bytes > (int) sizeof(packet) - can't happen */
+ || packet.udp.len != packet.ip6.ip6_plen
+ ) {
+ log1("Unrelated/bogus packet, ignoring");
+ return -2;
+ }
+
+//How to do this for ipv6?
+// /* 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 != inet_cksum((uint16_t *)&packet, bytes)) {
+// log1("Packet with bad UDP checksum received, ignoring");
+// return -2;
+// }
+
+ log1("Received a packet");
+ d6_dump_packet(&packet.data);
+
+ bytes -= sizeof(packet.ip6) + sizeof(packet.udp);
+ memcpy(d6_pkt, &packet.data, bytes);
+ return bytes;
+}
+
+
+/*** Main ***/
+
+static int sockfd = -1;
+
+#define LISTEN_NONE 0
+#define LISTEN_KERNEL 1
+#define LISTEN_RAW 2
+static smallint listen_mode;
+
+/* initial state: (re)start DHCP negotiation */
+#define INIT_SELECTING 0
+/* discover was sent, DHCPOFFER reply received */
+#define REQUESTING 1
+/* select/renew was sent, DHCPACK reply received */
+#define BOUND 2
+/* half of lease passed, want to renew it by sending unicast renew requests */
+#define RENEWING 3
+/* renew requests were not answered, lease is almost over, send broadcast renew */
+#define REBINDING 4
+/* manually requested renew (SIGUSR1) */
+#define RENEW_REQUESTED 5
+/* release, possibly manually requested (SIGUSR2) */
+#define RELEASED 6
+static smallint state;
+
+static int d6_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?
+ */
+#if 0
+ static const struct sock_filter filter_instr[] = {
+ /* load 9th byte (protocol) */
+ BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9),
+ /* jump to L1 if it is IPPROTO_UDP, else to L4 */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 0, 6),
+ /* L1: load halfword from offset 6 (flags and frag offset) */
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6),
+ /* jump to L4 if any bits in frag offset field are set, else to L2 */
+ BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x1fff, 4, 0),
+ /* L2: skip IP header (load index reg with header len) */
+ BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0),
+ /* load udp destination port from halfword[header_len + 2] */
+ BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2),
+ /* jump to L3 if udp dport is CLIENT_PORT, else to L4 */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1),
+ /* L3: accept packet */
+ BPF_STMT(BPF_RET|BPF_K, 0xffffffff),
+ /* L4: discard packet */
+ BPF_STMT(BPF_RET|BPF_K, 0),
+ };
+ static const struct sock_fprog filter_prog = {
+ .len = sizeof(filter_instr) / sizeof(filter_instr[0]),
+ /* casting const away: */
+ .filter = (struct sock_filter *) filter_instr,
+ };
+#endif
+
+ log1("Opening raw socket on ifindex %d", ifindex); //log2?
+
+ fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
+ log1("Got raw socket fd %d", fd); //log2?
+
+ sock.sll_family = AF_PACKET;
+ sock.sll_protocol = htons(ETH_P_IPV6);
+ sock.sll_ifindex = ifindex;
+ xbind(fd, (struct sockaddr *) &sock, sizeof(sock));
+
+#if 0
+ if (CLIENT_PORT == 68) {
+ /* Use only if standard port is in use */
+ /* Ignoring error (kernel may lack support for this) */
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
+ sizeof(filter_prog)) >= 0)
+ log1("Attached filter to raw socket fd %d", fd); // log?
+ }
+#endif
+
+ log1("Created raw socket");
+
+ return fd;
+}
+
+static void change_listen_mode(int new_mode)
+{
+ log1("Entering listen mode: %s",
+ new_mode != LISTEN_NONE
+ ? (new_mode == LISTEN_KERNEL ? "kernel" : "raw")
+ : "none"
+ );
+
+ listen_mode = new_mode;
+ if (sockfd >= 0) {
+ close(sockfd);
+ sockfd = -1;
+ }
+ if (new_mode == LISTEN_KERNEL)
+ sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+ else if (new_mode != LISTEN_NONE)
+ sockfd = d6_raw_socket(client_config.ifindex);
+ /* else LISTEN_NONE: sockfd stays closed */
+}
+
+/* Called only on SIGUSR1 */
+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 */
+ d6_run_script(NULL, "deconfig");
+ case REQUESTING:
+ case RELEASED:
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ break;
+ case INIT_SELECTING:
+ break;
+ }
+}
+
+static void perform_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
+{
+ /* send release packet */
+ if (state == BOUND || state == RENEWING || state == REBINDING) {
+ bb_info_msg("Unicasting a release");
+ send_d6_release(server_ipv6, our_cur_ipv6); /* unicast */
+ d6_run_script(NULL, "deconfig");
+ }
+ bb_info_msg("Entering released state");
+
+ change_listen_mode(LISTEN_NONE);
+ state = RELEASED;
+}
+
+///static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
+///{
+/// uint8_t *storage;
+/// int len = strnlen(str, 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;
+///}
+
+#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
+
+//usage:#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+//usage:# define IF_UDHCP_VERBOSE(...) __VA_ARGS__
+//usage:#else
+//usage:# define IF_UDHCP_VERBOSE(...)
+//usage:#endif
+//usage:#define udhcpc6_trivial_usage
+//usage: "[-fbnq"IF_UDHCP_VERBOSE("v")"oR] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n"
+//usage: " [-x OPT:VAL]... [-O OPT]..." IF_FEATURE_UDHCP_PORT(" [-P N]")
+//usage:#define udhcpc6_full_usage "\n"
+//usage: IF_LONG_OPTS(
+//usage: "\n -i,--interface IFACE Interface to use (default eth0)"
+//usage: "\n -p,--pidfile FILE Create pidfile"
+//usage: "\n -s,--script PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")"
+//usage: "\n -B,--broadcast Request broadcast replies"
+//usage: "\n -t,--retries N Send up to N discover packets"
+//usage: "\n -T,--timeout N Pause between packets (default 3 seconds)"
+//usage: "\n -A,--tryagain N Wait N seconds after failure (default 20)"
+//usage: "\n -f,--foreground Run in foreground"
+//usage: USE_FOR_MMU(
+//usage: "\n -b,--background Background if lease is not obtained"
+//usage: )
+//usage: "\n -n,--now Exit if lease is not obtained"
+//usage: "\n -q,--quit Exit after obtaining lease"
+//usage: "\n -R,--release Release IP on exit"
+//usage: "\n -S,--syslog Log to syslog too"
+//usage: IF_FEATURE_UDHCP_PORT(
+//usage: "\n -P,--client-port N Use port N (default 546)"
+//usage: )
+////usage: IF_FEATURE_UDHCPC_ARPING(
+////usage: "\n -a,--arping Use arping to validate offered address"
+////usage: )
+//usage: "\n -O,--request-option OPT Request option OPT from server (cumulative)"
+//usage: "\n -o,--no-default-options Don't request any options (unless -O is given)"
+//usage: "\n -r,--request IP Request this IP address"
+//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)"
+//usage: "\n Examples of string, numeric, and hex byte opts:"
+//usage: "\n -x hostname:bbox - option 12"
+//usage: "\n -x lease:3600 - option 51 (lease time)"
+//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)"
+//usage: IF_UDHCP_VERBOSE(
+//usage: "\n -v Verbose"
+//usage: )
+//usage: )
+//usage: IF_NOT_LONG_OPTS(
+//usage: "\n -i IFACE Interface to use (default eth0)"
+//usage: "\n -p FILE Create pidfile"
+//usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")"
+//usage: "\n -B Request broadcast replies"
+//usage: "\n -t N Send up to N discover packets"
+//usage: "\n -T N Pause between packets (default 3 seconds)"
+//usage: "\n -A N Wait N seconds (default 20) after failure"
+//usage: "\n -f Run in foreground"
+//usage: USE_FOR_MMU(
+//usage: "\n -b Background if lease is not obtained"
+//usage: )
+//usage: "\n -n Exit if lease is not obtained"
+//usage: "\n -q Exit after obtaining lease"
+//usage: "\n -R Release IP on exit"
+//usage: "\n -S Log to syslog too"
+//usage: IF_FEATURE_UDHCP_PORT(
+//usage: "\n -P N Use port N (default 546)"
+//usage: )
+////usage: IF_FEATURE_UDHCPC_ARPING(
+////usage: "\n -a Use arping to validate offered address"
+////usage: )
+//usage: "\n -O OPT Request option OPT from server (cumulative)"
+//usage: "\n -o Don't request any options (unless -O is given)"
+//usage: "\n -r IP Request this IP address"
+//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)"
+//usage: "\n Examples of string, numeric, and hex byte opts:"
+//usage: "\n -x hostname:bbox - option 12"
+//usage: "\n -x lease:3600 - option 51 (lease time)"
+//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)"
+//usage: IF_UDHCP_VERBOSE(
+//usage: "\n -v Verbose"
+//usage: )
+//usage: )
+//usage: "\nSignals:"
+//usage: "\n USR1 Renew lease"
+//usage: "\n USR2 Release lease"
+
+
+int udhcpc6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *str_r;
+ IF_FEATURE_UDHCP_PORT(char *str_P;)
+ void *clientid_mac_ptr;
+ llist_t *list_O = NULL;
+ llist_t *list_x = NULL;
+ int tryagain_timeout = 20;
+ int discover_timeout = 3;
+ int discover_retries = 3;
+ struct in6_addr srv6_buf;
+ struct in6_addr ipv6_buf;
+ struct in6_addr *requested_ipv6;
+ uint32_t xid = 0;
+ int packet_num;
+ int timeout; /* must be signed */
+ unsigned already_waited_sec;
+ unsigned opt;
+ int max_fd;
+ int retval;
+ fd_set rfds;
+
+ /* Default options */
+ IF_FEATURE_UDHCP_PORT(SERVER_PORT = 547;)
+ IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 546;)
+ client_config.interface = "eth0";
+ client_config.script = CONFIG_UDHCPC_DEFAULT_SCRIPT;
+
+ /* Parse command line */
+ /* O,x: list; -T,-t,-A take numeric param */
+ opt_complementary = "O::x::T+:t+:A+" IF_UDHCP_VERBOSE(":vv") ;
+ IF_LONG_OPTS(applet_long_options = udhcpc6_longopts;)
+ opt = getopt32(argv, "i:np:qRr:s:T:t:SA:O:ox:f"
+ USE_FOR_MMU("b")
+ ///IF_FEATURE_UDHCPC_ARPING("a")
+ IF_FEATURE_UDHCP_PORT("P:")
+ "v"
+ , &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
+ , &list_x
+ IF_FEATURE_UDHCP_PORT(, &str_P)
+ IF_UDHCP_VERBOSE(, &dhcp_verbose)
+ );
+ requested_ipv6 = NULL;
+ if (opt & OPT_r) {
+ if (inet_pton(AF_INET6, str_r, &ipv6_buf) <= 0)
+ bb_error_msg_and_die("bad IPv6 address '%s'", str_r);
+ requested_ipv6 = &ipv6_buf;
+ }
+#if ENABLE_FEATURE_UDHCP_PORT
+ if (opt & OPT_P) {
+ CLIENT_PORT = xatou16(str_P);
+ SERVER_PORT = CLIENT_PORT - 1;
+ }
+#endif
+ while (list_O) {
+ char *optstr = llist_pop(&list_O);
+ unsigned n = bb_strtou(optstr, NULL, 0);
+ if (errno || n > 254) {
+ n = udhcp_option_idx(optstr);
+ n = dhcp_optflags[n].code;
+ }
+ client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+ }
+ if (!(opt & OPT_o)) {
+ /*
+ unsigned i, n;
+ for (i = 0; (n = dhcp_optflags[i].code) != 0; i++) {
+ if (dhcp_optflags[i].flags & OPTION_REQ) {
+ client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+ }
+ }
+ */
+ }
+ while (list_x) {
+ char *optstr = llist_pop(&list_x);
+ char *colon = strchr(optstr, ':');
+ if (colon)
+ *colon = ' ';
+ /* now it looks similar to udhcpd's config file line:
+ * "optname optval", using the common routine: */
+ udhcp_str2optset(optstr, &client_config.options);
+ }
+
+ if (udhcp_read_interface(client_config.interface,
+ &client_config.ifindex,
+ NULL,
+ client_config.client_mac)
+ ) {
+ return 1;
+ }
+
+ /* Create client ID based on mac, set clientid_mac_ptr */
+ {
+ struct d6_option *clientid;
+ clientid = xzalloc(2+2+2+2+6);
+ clientid->code = D6_OPT_CLIENTID;
+ clientid->len = 2+2+6;
+ clientid->data[1] = 3; /* DUID-LL */
+ clientid->data[3] = 1; /* ethernet */
+ clientid_mac_ptr = clientid->data + 2+2;
+ memcpy(clientid_mac_ptr, client_config.client_mac, 6);
+ client_config.clientid = (void*)clientid;
+ }
+
+#if !BB_MMU
+ /* on NOMMU reexec (i.e., background) early */
+ if (!(opt & OPT_f)) {
+ bb_daemonize_or_rexec(0 /* flags */, argv);
+ logmode = LOGMODE_NONE;
+ }
+#endif
+ if (opt & OPT_S) {
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ 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);
+ /* Set up the signal pipe */
+ udhcp_sp_setup();
+ /* We want random_xid to be random... */
+ srand(monotonic_us());
+
+ state = INIT_SELECTING;
+ d6_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 (;;) {
+ struct timeval tv;
+ struct d6_packet packet;
+ uint8_t *packet_end;
+ /* silence "uninitialized!" warning */
+ unsigned timestamp_before_wait = timestamp_before_wait;
+
+ //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode);
+
+ /* Was opening raw or udp socket here
+ * if (listen_mode != LISTEN_NONE && sockfd < 0),
+ * but on fast network renew responses return faster
+ * than we open sockets. Thus this code is moved
+ * to change_listen_mode(). Thus we open listen socket
+ * BEFORE we send renew request (see "case BOUND:"). */
+
+ 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 with retval = 0, else... */
+ if ((int)tv.tv_sec > 0) {
+ log1("Waiting on select %u seconds", (int)tv.tv_sec);
+ timestamp_before_wait = (unsigned)monotonic_sec();
+ retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
+ if (retval < 0) {
+ /* EINTR? A signal was caught, don't panic */
+ if (errno == EINTR) {
+ already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
+ 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) {
+ /* When running on a bridge, the ifindex may have changed
+ * (e.g. if member interfaces were added/removed
+ * or if the status of the bridge changed).
+ * Refresh ifindex and client_mac:
+ */
+ if (udhcp_read_interface(client_config.interface,
+ &client_config.ifindex,
+ NULL,
+ client_config.client_mac)
+ ) {
+ goto ret0; /* iface is gone? */
+ }
+ memcpy(clientid_mac_ptr, client_config.client_mac, 6);
+
+ /* We will restart the wait in any case */
+ already_waited_sec = 0;
+
+ switch (state) {
+ case INIT_SELECTING:
+ if (!discover_retries || packet_num < discover_retries) {
+ if (packet_num == 0)
+ xid = random_xid();
+ /* multicast */
+ send_d6_discover(xid, requested_ipv6);
+ timeout = discover_timeout;
+ packet_num++;
+ continue;
+ }
+ leasefail:
+ d6_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 REQUESTING:
+ if (!discover_retries || packet_num < discover_retries) {
+ /* send multicast select packet */
+ send_d6_select(xid);
+ timeout = discover_timeout;
+ packet_num++;
+ continue;
+ }
+ /* Timed out, go back to init state.
+ * "discover...select...discover..." loops
+ * were seen in the wild. Treat them similarly
+ * to "no response to discover" case */
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ goto leasefail;
+ case BOUND:
+ /* 1/2 lease passed, enter renewing state */
+ state = RENEWING;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ change_listen_mode(LISTEN_KERNEL);
+ log1("Entering renew state");
+ /* fall right through */
+ case RENEW_REQUESTED: /* manual (SIGUSR1) renew */
+ case_RENEW_REQUESTED:
+ case RENEWING:
+ if (timeout > 60) {
+ /* send an unicast renew request */
+ /* Sometimes observed to fail (EADDRNOTAVAIL) to bind
+ * a new UDP socket for sending inside send_renew.
+ * I hazard to guess existing listening socket
+ * is somehow conflicting with it, but why is it
+ * not deterministic then?! Strange.
+ * Anyway, it does recover by eventually failing through
+ * into INIT_SELECTING state.
+ */
+ send_d6_renew(xid, &srv6_buf, requested_ipv6);
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter rebinding state */
+ log1("Entering rebinding state");
+ state = REBINDING;
+ /* fall right through */
+ case REBINDING:
+ /* Switch to bcast receive */
+ change_listen_mode(LISTEN_RAW);
+ /* Lease is *really* about to run out,
+ * try to find DHCP server using broadcast */
+ if (timeout > 0) {
+ /* send a broadcast renew request */
+ send_d6_renew(xid, /*server_ipv6:*/ NULL, requested_ipv6);
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter init state */
+ bb_info_msg("Lease lost, entering init state");
+ d6_run_script(NULL, "deconfig");
+ state = INIT_SELECTING;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ /*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 */
+ } /* if select timed out */
+
+ /* select() didn't timeout, something happened */
+
+ /* Is it a signal? */
+ /* note: udhcp_sp_read checks FD_ISSET before reading */
+ switch (udhcp_sp_read(&rfds)) {
+ case SIGUSR1:
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ already_waited_sec = 0;
+ perform_renew();
+ if (state == RENEW_REQUESTED) {
+ /* We might be either on the same network
+ * (in which case renew might work),
+ * or we might be on a completely different one
+ * (in which case renew won't ever succeed).
+ * For the second case, must make sure timeout
+ * is not too big, or else we can send
+ * futile renew requests for hours.
+ * (Ab)use -A TIMEOUT value (usually 20 sec)
+ * as a cap on the timeout.
+ */
+ if (timeout > tryagain_timeout)
+ timeout = tryagain_timeout;
+ goto case_RENEW_REQUESTED;
+ }
+ /* Start things over */
+ packet_num = 0;
+ /* Kill any timeouts, user wants this to hurry along */
+ timeout = 0;
+ continue;
+ case SIGUSR2:
+ perform_d6_release(&srv6_buf, requested_ipv6);
+ timeout = INT_MAX;
+ continue;
+ case SIGTERM:
+ bb_info_msg("Received SIGTERM");
+ goto ret0;
+ }
+
+ /* Is it a packet? */
+ if (listen_mode == LISTEN_NONE || !FD_ISSET(sockfd, &rfds))
+ continue; /* no */
+
+ {
+ int len;
+
+ /* A packet is ready, read it */
+ if (listen_mode == LISTEN_KERNEL)
+ len = d6_recv_kernel_packet(&srv6_buf, &packet, sockfd);
+ else
+ len = d6_recv_raw_packet(&srv6_buf, &packet, sockfd);
+ if (len == -1) {
+ /* Error is severe, reopen socket */
+ bb_info_msg("Read error: %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;
+ packet_end = (uint8_t*)&packet + len;
+ }
+
+ if ((packet.d6_xid32 & htonl(0x00ffffff)) != xid) {
+ log1("xid %x (our is %x), ignoring packet",
+ (unsigned)(packet.d6_xid32 & htonl(0x00ffffff)), (unsigned)xid);
+ continue;
+ }
+
+ switch (state) {
+ case INIT_SELECTING:
+ if (packet.d6_msg_type == D6_MSG_ADVERTISE)
+ goto type_is_ok;
+ /* DHCPv6 has "Rapid Commit", when instead of Advertise,
+ * server sends Reply right away.
+ * Fall through to check for this case.
+ */
+ case REQUESTING:
+ case RENEWING:
+ case RENEW_REQUESTED:
+ case REBINDING:
+ if (packet.d6_msg_type == D6_MSG_REPLY) {
+ uint32_t lease_seconds;
+ struct d6_option *option, *iaaddr;
+ type_is_ok:
+ option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE);
+ if (option && option->data[4] != 0) {
+ /* return to init state */
+ bb_info_msg("Received DHCP NAK (%u)", option->data[4]);
+ d6_run_script(&packet, "nak");
+ if (state != REQUESTING)
+ d6_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ sleep(3); /* avoid excessive network traffic */
+ state = INIT_SELECTING;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ requested_ipv6 = NULL;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ continue;
+ }
+ option = d6_copy_option(packet.d6_options, packet_end, D6_OPT_SERVERID);
+ if (!option) {
+ bb_error_msg("no server ID, ignoring packet");
+ continue;
+ /* still selecting - this server looks bad */
+ }
+//Note: we do not bother comparing server IDs in Advertise and Reply msgs.
+//server_id variable is used solely for creation of proper server_id option
+//in outgoing packets. (why DHCPv6 even introduced it is a mystery).
+ free(client6_data.server_id);
+ client6_data.server_id = option;
+ if (packet.d6_msg_type == D6_MSG_ADVERTISE) {
+ /* enter requesting state */
+ state = REQUESTING;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ continue;
+ }
+ /* It's a D6_MSG_REPLY */
+/*
+ * RFC 3315 18.1.8. Receipt of Reply Messages
+ *
+ * Upon the receipt of a valid Reply message in response to a Solicit
+ * (with a Rapid Commit option), Request, Confirm, Renew, Rebind or
+ * Information-request message, the client extracts the configuration
+ * information contained in the Reply. The client MAY choose to report
+ * any status code or message from the status code option in the Reply
+ * message.
+ *
+ * The client SHOULD perform duplicate address detection [17] on each of
+ * the addresses in any IAs it receives in the Reply message before
+ * using that address for traffic. If any of the addresses are found to
+ * be in use on the link, the client sends a Decline message to the
+ * server as described in section 18.1.7.
+ *
+ * If the Reply was received in response to a Solicit (with a Rapid
+ * Commit option), Request, Renew or Rebind message, the client updates
+ * the information it has recorded about IAs from the IA options
+ * contained in the Reply message:
+ *
+ * - Record T1 and T2 times.
+ *
+ * - Add any new addresses in the IA option to the IA as recorded by
+ * the client.
+ *
+ * - Update lifetimes for any addresses in the IA option that the
+ * client already has recorded in the IA.
+ *
+ * - Discard any addresses from the IA, as recorded by the client, that
+ * have a valid lifetime of 0 in the IA Address option.
+ *
+ * - Leave unchanged any information about addresses the client has
+ * recorded in the IA but that were not included in the IA from the
+ * server.
+ *
+ * Management of the specific configuration information is detailed in
+ * the definition of each option in section 22.
+ *
+ * If the client receives a Reply message with a Status Code containing
+ * UnspecFail, the server is indicating that it was unable to process
+ * the message due to an unspecified failure condition. If the client
+ * retransmits the original message to the same server to retry the
+ * desired operation, the client MUST limit the rate at which it
+ * retransmits the message and limit the duration of the time during
+ * which it retransmits the message.
+ *
+ * When the client receives a Reply message with a Status Code option
+ * with the value UseMulticast, the client records the receipt of the
+ * message and sends subsequent messages to the server through the
+ * interface on which the message was received using multicast. The
+ * client resends the original message using multicast.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IA_NA | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | IAID (4 octets) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | T1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | T2 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * . IA_NA-options .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IAADDR | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * | IPv6 address |
+ * | |
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | preferred-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | valid-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * . .
+ * . IAaddr-options .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ free(client6_data.ia_na);
+ client6_data.ia_na = d6_copy_option(packet.d6_options, packet_end, D6_OPT_IA_NA);
+ if (!client6_data.ia_na) {
+ bb_error_msg("no %s option, ignoring packet", "IA_NA");
+ continue;
+ }
+ if (client6_data.ia_na->len < (4 + 4 + 4) + (2 + 2 + 16 + 4 + 4)) {
+ bb_error_msg("IA_NA option is too short:%d bytes", client6_data.ia_na->len);
+ continue;
+ }
+ iaaddr = d6_find_option(client6_data.ia_na->data + 4 + 4 + 4,
+ client6_data.ia_na->data + client6_data.ia_na->len,
+ D6_OPT_IAADDR
+ );
+ if (!iaaddr) {
+ bb_error_msg("no %s option, ignoring packet", "IAADDR");
+ continue;
+ }
+ if (iaaddr->len < (16 + 4 + 4)) {
+ bb_error_msg("IAADDR option is too short:%d bytes", iaaddr->len);
+ continue;
+ }
+ /* Note: the address is sufficiently aligned for cast:
+ * we _copied_ IA-NA, and copy is always well-aligned.
+ */
+ requested_ipv6 = (struct in6_addr*) iaaddr->data;
+ move_from_unaligned32(lease_seconds, iaaddr->data + 16 + 4);
+ lease_seconds = ntohl(lease_seconds);
+ /* paranoia: must not be too small and not prone to overflows */
+ if (lease_seconds < 0x10)
+ lease_seconds = 0x10;
+/// TODO: check for 0 lease time?
+ if (lease_seconds >= 0x10000000)
+ lease_seconds = 0x0fffffff;
+ /* enter bound state */
+ timeout = lease_seconds / 2;
+ bb_info_msg("Lease obtained, lease time %u",
+ /*inet_ntoa(temp_addr),*/ (unsigned)lease_seconds);
+ d6_run_script(&packet, state == REQUESTING ? "bound" : "renew");
+
+ state = BOUND;
+ change_listen_mode(LISTEN_NONE);
+ if (opt & OPT_q) { /* quit after lease */
+ goto ret0;
+ }
+ /* future renew failures should not exit (JM) */
+ opt &= ~OPT_n;
+#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 */
+ }
+ continue;
+ /* case BOUND: - ignore all packets */
+ /* case RELEASED: - ignore all packets */
+ }
+ /* back to main loop */
+ } /* for (;;) - main loop ends */
+
+ ret0:
+ if (opt & OPT_R) /* release on quit */
+ perform_d6_release(&srv6_buf, requested_ipv6);
+ retval = 0;
+ ret:
+ /*if (client_config.pidfile) - remove_pidfile has its own check */
+ remove_pidfile(client_config.pidfile);
+ return retval;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/d6_packet.c b/ap/app/busybox/src/networking/udhcp/d6_packet.c
new file mode 100644
index 0000000..79b2946
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/d6_packet.c
@@ -0,0 +1,172 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2011 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "common.h"
+#include "d6_common.h"
+#include "dhcpd.h"
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netpacket/packet.h>
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+void FAST_FUNC d6_dump_packet(struct d6_packet *packet)
+{
+ if (dhcp_verbose < 2)
+ return;
+
+ bb_info_msg(
+ " xid %x"
+ , packet->d6_xid32
+ );
+ //*bin2hex(buf, (void *) packet->chaddr, sizeof(packet->chaddr)) = '\0';
+ //bb_info_msg(" chaddr %s", buf);
+}
+#endif
+
+int FAST_FUNC d6_recv_kernel_packet(struct in6_addr *peer_ipv6
+ UNUSED_PARAM
+ , struct d6_packet *packet, int fd)
+{
+ int bytes;
+
+ memset(packet, 0, sizeof(*packet));
+ bytes = safe_read(fd, packet, sizeof(*packet));
+ if (bytes < 0) {
+ log1("Packet read error, ignoring");
+ return bytes; /* returns -1 */
+ }
+
+ if (bytes < offsetof(struct d6_packet, d6_options)) {
+ bb_info_msg("Packet with bad magic, ignoring");
+ return -2;
+ }
+ log1("Received a packet");
+ d6_dump_packet(packet);
+
+ return bytes;
+}
+
+/* Construct a ipv6+udp header for a packet, send packet */
+int FAST_FUNC d6_send_raw_packet(
+ struct d6_packet *d6_pkt, unsigned d6_pkt_size,
+ struct in6_addr *src_ipv6, int source_port,
+ struct in6_addr *dst_ipv6, int dest_port, const uint8_t *dest_arp,
+ int ifindex)
+{
+ struct sockaddr_ll dest_sll;
+ struct ip6_udp_d6_packet packet;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+
+ memset(&dest_sll, 0, sizeof(dest_sll));
+ memset(&packet, 0, offsetof(struct ip6_udp_d6_packet, data));
+ packet.data = *d6_pkt; /* struct copy */
+
+ dest_sll.sll_family = AF_PACKET;
+ dest_sll.sll_protocol = htons(ETH_P_IPV6);
+ dest_sll.sll_ifindex = ifindex;
+ dest_sll.sll_halen = 6;
+ memcpy(dest_sll.sll_addr, dest_arp, 6);
+
+ if (bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)) < 0) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ packet.ip6.ip6_vfc = (6 << 4); /* 4 bits version, top 4 bits of tclass */
+ if (src_ipv6)
+ packet.ip6.ip6_src = *src_ipv6; /* struct copy */
+ packet.ip6.ip6_dst = *dst_ipv6; /* struct copy */
+ packet.udp.source = htons(source_port);
+ packet.udp.dest = htons(dest_port);
+ /* size, excluding IP header: */
+ packet.udp.len = htons(sizeof(struct udphdr) + d6_pkt_size);
+ packet.ip6.ip6_plen = packet.udp.len;
+ /*
+ * Someone was smoking weed (at least) while inventing UDP checksumming:
+ * UDP checksum skips first four bytes of IPv6 header.
+ * 'next header' field should be summed as if it is one more byte
+ * to the right, therefore we write its value (IPPROTO_UDP)
+ * into ip6_hlim, and its 'real' location remains zero-filled for now.
+ */
+ packet.ip6.ip6_hlim = IPPROTO_UDP;
+ packet.udp.check = inet_cksum(
+ (uint16_t *)&packet + 2,
+ offsetof(struct ip6_udp_d6_packet, data) - 4 + d6_pkt_size
+ );
+ /* fix 'hop limit' and 'next header' after UDP checksumming */
+ packet.ip6.ip6_hlim = 1; /* observed Windows machines to use hlim=1 */
+ packet.ip6.ip6_nxt = IPPROTO_UDP;
+
+ d6_dump_packet(d6_pkt);
+ result = sendto(fd, &packet, offsetof(struct ip6_udp_d6_packet, data) + d6_pkt_size,
+ /*flags:*/ 0,
+ (struct sockaddr *) &dest_sll, sizeof(dest_sll)
+ );
+ 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 d6_send_kernel_packet(
+ struct d6_packet *d6_pkt, unsigned d6_pkt_size,
+ struct in6_addr *src_ipv6, int source_port,
+ struct in6_addr *dst_ipv6, int dest_port)
+{
+ struct sockaddr_in6 sa;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+ setsockopt_reuseaddr(fd);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(source_port);
+ sa.sin6_addr = *src_ipv6; /* struct copy */
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(dest_port);
+ sa.sin6_addr = *dst_ipv6; /* struct copy */
+ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ msg = "connect";
+ goto ret_close;
+ }
+
+ d6_dump_packet(d6_pkt);
+ result = safe_write(fd, d6_pkt, d6_pkt_size);
+ msg = "write";
+ ret_close:
+ close(fd);
+ if (result < 0) {
+ ret_msg:
+ bb_perror_msg(msg, "UDP");
+ }
+ return result;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/d6_socket.c b/ap/app/busybox/src/networking/udhcp/d6_socket.c
new file mode 100644
index 0000000..56f69f6
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/d6_socket.c
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2011 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "common.h"
+#include "d6_common.h"
+#include <net/if.h>
+
+int FAST_FUNC d6_listen_socket(int port, const char *inf)
+{
+ int fd;
+ struct sockaddr_in6 addr;
+
+ log1("Opening listen socket on *:%d %s", port, inf);
+ fd = xsocket(PF_INET6, 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.sin6_family = AF_INET6;
+ addr.sin6_port = htons(port);
+ /* addr.sin6_addr is all-zeros */
+ xbind(fd, (struct sockaddr *)&addr, sizeof(addr));
+
+ return fd;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/dhcpc.c b/ap/app/busybox/src/networking/udhcp/dhcpc.c
new file mode 100755
index 0000000..c33097a
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dhcpc.c
@@ -0,0 +1,1819 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 <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 <netinet/if_ether.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+
+/* "struct client_config_t client_config" is in bb_common_bufsiz1 */
+
+
+#if ENABLE_LONG_OPTS
+static const char udhcpc_longopts[] ALIGN1 =
+ "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"
+ "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"
+ "broadcast\0" No_argument "B"
+ IF_FEATURE_UDHCPC_ARPING("arping\0" No_argument "a")
+ IF_FEATURE_UDHCP_PORT("client-port\0" Required_argument "P")
+ ;
+#endif
+/* Must match getopt32 option string order */
+enum {
+ OPT_C = 1 << 0,
+ OPT_V = 1 << 1,
+ OPT_H = 1 << 2,
+ OPT_h = 1 << 3,
+ OPT_F = 1 << 4,
+ OPT_i = 1 << 5,
+ OPT_n = 1 << 6,
+ OPT_p = 1 << 7,
+ OPT_q = 1 << 8,
+ OPT_R = 1 << 9,
+ OPT_r = 1 << 10,
+ OPT_s = 1 << 11,
+ OPT_T = 1 << 12,
+ OPT_t = 1 << 13,
+ OPT_S = 1 << 14,
+ OPT_A = 1 << 15,
+ OPT_O = 1 << 16,
+ OPT_o = 1 << 17,
+ OPT_x = 1 << 18,
+ OPT_f = 1 << 19,
+ OPT_B = 1 << 20,
+/* The rest has variable bit positions, need to be clever */
+ OPTBIT_B = 20,
+ USE_FOR_MMU( OPTBIT_b,)
+ IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
+ IF_FEATURE_UDHCP_PORT( OPTBIT_P,)
+ USE_FOR_MMU( OPT_b = 1 << OPTBIT_b,)
+ IF_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,)
+ IF_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,)
+};
+
+
+/*** Script execution code ***/
+
+/* get a rough idea of how long an option will be (rounding up...) */
+static const uint8_t len_of_option_as_string[] = {
+ [OPTION_IP ] = sizeof("255.255.255.255 "),
+ [OPTION_IP_PAIR ] = sizeof("255.255.255.255 ") * 2,
+ [OPTION_STATIC_ROUTES ] = sizeof("255.255.255.255/32 255.255.255.255 "),
+ [OPTION_6RD ] = sizeof("132 128 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 255.255.255.255 "),//hub CVE-2016-2148
+ [OPTION_STRING ] = 1,
+ [OPTION_STRING_HOST ] = 1,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ [OPTION_DNS_STRING ] = 1, /* unused */
+ /* Hmmm, this severely overestimates size if SIP_SERVERS option
+ * is in domain name form: N-byte option in binary form
+ * mallocs ~16*N bytes. But it is freed almost at once.
+ */
+ [OPTION_SIP_SERVERS ] = sizeof("255.255.255.255 "),
+#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 "),
+};
+
+/* note: ip is a pointer to an IP in network order, possibly misaliged */
+static int sprint_nip(char *dest, const char *pre, const uint8_t *ip)
+{
+ return sprintf(dest, "%s%u.%u.%u.%u", 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;
+}
+
+/* Check if a given label represents a valid DNS label
+ * Return pointer to the first character after the label upon success,
+ * NULL otherwise.
+ * See RFC1035, 2.3.1
+ */
+/* We don't need to be particularly anal. For example, allowing _, hyphen
+ * at the end, or leading and trailing dots would be ok, since it
+ * can't be used for attacks. (Leading hyphen can be, if someone uses
+ * cmd "$hostname"
+ * in the script: then hostname may be treated as an option)
+ */
+static const char *valid_domain_label(const char *label)
+{
+ unsigned char ch;
+ unsigned pos = 0;
+
+ for (;;) {
+ ch = *label;
+ if ((ch|0x20) < 'a' || (ch|0x20) > 'z') {
+ if (pos == 0) {
+ /* label must begin with letter */
+ return NULL;
+ }
+ if (ch < '0' || ch > '9') {
+ if (ch == '\0' || ch == '.')
+ return label;
+ /* DNS allows only '-', but we are more permissive */
+ if (ch != '-' && ch != '_')
+ return NULL;
+ }
+ }
+ label++;
+ pos++;
+ //Do we want this?
+ //if (pos > 63) /* NS_MAXLABEL; labels must be 63 chars or less */
+ // return NULL;
+ }
+}
+
+/* Check if a given name represents a valid DNS name */
+/* See RFC1035, 2.3.1 */
+static int good_hostname(const char *name)
+{
+ //const char *start = name;
+
+ for (;;) {
+ name = valid_domain_label(name);
+ if (!name)
+ return 0;
+ if (!name[0])
+ return 1;
+ //Do we want this?
+ //return ((name - start) < 1025); /* NS_MAXDNAME */
+ name++;
+ }
+}
+
+/* Create "opt_name=opt_value" string */
+static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name)
+{
+ unsigned upper_length;
+ int len, type, optlen;
+ char *dest, *ret;
+
+ /* option points to OPT_DATA, need to go back to get OPT_LEN */
+ len = option[-OPT_DATA + OPT_LEN];
+
+ type = optflag->flags & OPTION_TYPE_MASK;
+ optlen = dhcp_option_lengths[type];
+ upper_length = len_of_option_as_string[type]
+ * ((unsigned)(len + optlen) / (unsigned)optlen);//hub CVE-2016-2148
+
+ dest = ret = xmalloc(upper_length + strlen(opt_name) + 2);
+ dest += sprintf(ret, "%s=", opt_name);
+
+ while (len >= optlen) {
+ switch (type) {
+ case OPTION_IP:
+ case OPTION_IP_PAIR:
+ dest += sprint_nip(dest, "", option);
+ if (type == OPTION_IP)
+ break;
+ dest += sprint_nip(dest, "/", option + 4);
+ break;
+// case OPTION_BOOLEAN:
+// dest += sprintf(dest, *option ? "yes" : "no");
+// break;
+ case OPTION_U8:
+ dest += sprintf(dest, "%u", *option);
+ break;
+// case OPTION_S16:
+ case OPTION_U16: {
+ uint16_t val_u16;
+ move_from_unaligned16(val_u16, option);
+ dest += sprintf(dest, "%u", ntohs(val_u16));
+ break;
+ }
+ case OPTION_S32:
+ case OPTION_U32: {
+ uint32_t val_u32;
+ move_from_unaligned32(val_u32, option);
+ dest += sprintf(dest, type == OPTION_U32 ? "%lu" : "%ld", (unsigned long) ntohl(val_u32));
+ break;
+ }
+ /* Note: options which use 'return' instead of 'break'
+ * (for example, OPTION_STRING) skip the code which handles
+ * the case of list of options.
+ */
+ case OPTION_STRING:
+ case OPTION_STRING_HOST:
+ memcpy(dest, option, len);
+ dest[len] = '\0';
+ if (type == OPTION_STRING_HOST && !good_hostname(dest))
+ safe_strncpy(dest, "bad", len);
+ return ret;
+ case OPTION_STATIC_ROUTES: {
+ /* Option binary format:
+ * mask [one byte, 0..32]
+ * ip [big endian, 0..4 bytes depending on mask]
+ * router [big endian, 4 bytes]
+ * may be repeated
+ *
+ * We convert it to a string "IP/MASK ROUTER IP2/MASK2 ROUTER2"
+ */
+ const char *pfx = "";
+
+ while (len >= 1 + 4) { /* mask + 0-byte ip + router */
+ uint32_t nip;
+ uint8_t *p;
+ unsigned mask;
+ int bytes;
+
+ mask = *option++;
+ if (mask > 32)
+ break;
+ len--;
+
+ nip = 0;
+ p = (void*) &nip;
+ bytes = (mask + 7) / 8; /* 0 -> 0, 1..8 -> 1, 9..16 -> 2 etc */
+ while (--bytes >= 0) {
+ *p++ = *option++;
+ len--;
+ }
+ if (len < 4)
+ break;
+
+ /* print ip/mask */
+ dest += sprint_nip(dest, pfx, (void*) &nip);
+ pfx = " ";
+ dest += sprintf(dest, "/%u ", mask);
+ /* print router */
+ dest += sprint_nip(dest, "", option);
+ option += 4;
+ len -= 4;
+ }
+
+ return ret;
+ }
+ case OPTION_6RD:
+ /* Option binary format (see RFC 5969):
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_6RD | option-length | IPv4MaskLen | 6rdPrefixLen |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | 6rdPrefix |
+ * ... (16 octets) ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * ... 6rdBRIPv4Address(es) ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * We convert it to a string
+ * "IPv4MaskLen 6rdPrefixLen 6rdPrefix 6rdBRIPv4Address..."
+ *
+ * Sanity check: ensure that our length is at least 22 bytes, that
+ * IPv4MaskLen <= 32,
+ * 6rdPrefixLen <= 128,
+ * 6rdPrefixLen + (32 - IPv4MaskLen) <= 128
+ * (2nd condition need no check - it follows from 1st and 3rd).
+ * Else, return envvar with empty value ("optname=")
+ */
+ if (len >= (1 + 1 + 16 + 4)
+ && option[0] <= 32
+ && (option[1] + 32 - option[0]) <= 128
+ ) {
+ /* IPv4MaskLen */
+ dest += sprintf(dest, "%u ", *option++);
+ /* 6rdPrefixLen */
+ dest += sprintf(dest, "%u ", *option++);
+ /* 6rdPrefix */
+ dest += sprint_nip6(dest, /* "", */ option);
+ option += 16;
+ len -= 1 + 1 + 16 + 4;
+ /* "+ 4" above corresponds to the length of IPv4 addr
+ * we consume in the loop below */
+ while (1) {
+ /* 6rdBRIPv4Address(es) */
+ dest += sprint_nip(dest, " ", option);
+ option += 4;
+ len -= 4; /* do we have yet another 4+ bytes? */
+ if (len < 0)
+ break; /* no */
+ }
+ }
+
+ return ret;
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ case OPTION_DNS_STRING:
+ /* unpack option into dest; use ret for prefix (i.e., "optname=") */
+ dest = dname_dec(option, len, ret);
+ if (dest) {
+ free(ret);
+ return dest;
+ }
+ /* error. return "optname=" string */
+ return ret;
+ case OPTION_SIP_SERVERS:
+ /* Option binary format:
+ * type: byte
+ * type=0: domain names, dns-compressed
+ * type=1: IP addrs
+ */
+ option++;
+ len--;
+ if (option[-1] == 0) {
+ dest = dname_dec(option, len, ret);
+ if (dest) {
+ free(ret);
+ return dest;
+ }
+ } else
+ if (option[-1] == 1) {
+ const char *pfx = "";
+ while (1) {
+ len -= 4;
+ if (len < 0)
+ break;
+ dest += sprint_nip(dest, pfx, option);
+ pfx = " ";
+ option += 4;
+ }
+ }
+ return ret;
+#endif
+ } /* switch */
+
+ /* If we are here, try to format any remaining data
+ * in the option as another, similarly-formatted option
+ */
+ option += optlen;
+ len -= optlen;
+// TODO: it can be a list only if (optflag->flags & OPTION_LIST).
+// Should we bail out/warn if we see multi-ip option which is
+// not allowed to be such (for example, DHCP_BROADCAST)? -
+ if (len < optlen /* || !(optflag->flags & OPTION_LIST) */)
+ break;
+ *dest++ = ' ';
+ *dest = '\0';
+ } /* while */
+
+ return ret;
+}
+
+/* put all the parameters into the environment */
+static char **fill_envp(struct dhcp_packet *packet)
+{
+ int envc;
+ int i;
+ char **envp, **curr;
+ const char *opt_name;
+ uint8_t *temp;
+ uint8_t overload = 0;
+
+#define BITMAP unsigned
+#define BBITS (sizeof(BITMAP) * 8)
+#define BMASK(i) (1 << (i & (sizeof(BITMAP) * 8 - 1)))
+#define FOUND_OPTS(i) (found_opts[(unsigned)i / BBITS])
+ BITMAP found_opts[256 / BBITS];
+
+ memset(found_opts, 0, sizeof(found_opts));
+
+ /* We need 6 elements for:
+ * "interface=IFACE"
+ * "ip=N.N.N.N" from packet->yiaddr
+ * "siaddr=IP" from packet->siaddr_nip (unless 0)
+ * "boot_file=FILE" from packet->file (unless overloaded)
+ * "sname=SERVER_HOSTNAME" from packet->sname (unless overloaded)
+ * terminating NULL
+ */
+ envc = 6;
+ /* +1 element for each option, +2 for subnet option: */
+ if (packet) {
+ /* note: do not search for "pad" (0) and "end" (255) options */
+//TODO: change logic to scan packet _once_
+ for (i = 1; i < 255; i++) {
+ temp = udhcp_get_option(packet, i);
+ if (temp) {
+ if (i == DHCP_OPTION_OVERLOAD)
+ overload = *temp;
+ else if (i == DHCP_SUBNET)
+ envc++; /* for $mask */
+ envc++;
+ /*if (i != DHCP_MESSAGE_TYPE)*/
+ FOUND_OPTS(i) |= BMASK(i);
+ }
+ }
+ }
+ curr = envp = xzalloc(sizeof(envp[0]) * envc);
+
+ *curr = xasprintf("interface=%s", client_config.interface);
+ putenv(*curr++);
+
+ if (!packet)
+ return envp;
+
+ /* Export BOOTP fields. Fields we don't (yet?) export:
+ * uint8_t op; // always 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;
+ * uint16_t secs; // elapsed since client began acquisition/renewal
+ * uint16_t flags; // only one flag so far: bcast. Never set by server
+ * uint32_t ciaddr; // client IP (usually == yiaddr. can it be different
+ * // if during renew server wants to give us differn IP?)
+ * uint32_t gateway_nip; // relay agent IP address
+ * uint8_t chaddr[16]; // link-layer client hardware address (MAC)
+ * TODO: export gateway_nip as $giaddr?
+ */
+ /* Most important one: yiaddr as $ip */
+ *curr = xmalloc(sizeof("ip=255.255.255.255"));
+ sprint_nip(*curr, "ip=", (uint8_t *) &packet->yiaddr);
+ putenv(*curr++);
+ if (packet->siaddr_nip) {
+ /* IP address of next server to use in bootstrap */
+ *curr = xmalloc(sizeof("siaddr=255.255.255.255"));
+ sprint_nip(*curr, "siaddr=", (uint8_t *) &packet->siaddr_nip);
+ putenv(*curr++);
+ }
+ if (!(overload & FILE_FIELD) && packet->file[0]) {
+ /* watch out for invalid packets */
+ *curr = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file);
+ putenv(*curr++);
+ }
+ if (!(overload & SNAME_FIELD) && packet->sname[0]) {
+ /* watch out for invalid packets */
+ *curr = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname);
+ putenv(*curr++);
+ }
+
+ /* Export known DHCP options */
+ opt_name = dhcp_option_strings;
+ i = 0;
+ while (*opt_name) {
+ uint8_t code = dhcp_optflags[i].code;
+ BITMAP *found_ptr = &FOUND_OPTS(code);
+ BITMAP found_mask = BMASK(code);
+ if (!(*found_ptr & found_mask))
+ goto next;
+ *found_ptr &= ~found_mask; /* leave only unknown options */
+ temp = udhcp_get_option(packet, code);
+ *curr = xmalloc_optname_optval(temp, &dhcp_optflags[i], opt_name);
+ putenv(*curr++);
+ //if (code == DHCP_SUBNET) {CVE-2019-5747
+ if (code == DHCP_SUBNET && temp[-OPT_DATA + OPT_LEN] == 4) {
+ /* Subnet option: make things like "$ip/$mask" possible */
+ uint32_t subnet;
+ move_from_unaligned32(subnet, temp);
+ *curr = xasprintf("mask=%u", mton(subnet));
+ putenv(*curr++);
+ }
+ next:
+ opt_name += strlen(opt_name) + 1;
+ i++;
+ }
+ /* Export unknown options */
+ for (i = 0; i < 256;) {
+ BITMAP bitmap = FOUND_OPTS(i);
+ if (!bitmap) {
+ i += BBITS;
+ continue;
+ }
+ if (bitmap & BMASK(i)) {
+ unsigned len, ofs;
+
+ temp = udhcp_get_option(packet, i);
+ /* udhcp_get_option returns ptr to data portion,
+ * need to go back to get len
+ */
+ len = temp[-OPT_DATA + OPT_LEN];
+ *curr = xmalloc(sizeof("optNNN=") + 1 + len*2);
+ ofs = sprintf(*curr, "opt%u=", i);
+ *bin2hex(*curr + ofs, (void*) temp, len) = '\0';
+ putenv(*curr++);
+ }
+ i++;
+ }
+
+ return envp;
+}
+
+/* Call a script with a par file and env vars */
+static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
+{
+ char **envp, **curr;
+ char *argv[3];
+
+ envp = fill_envp(packet);
+
+ /* call script */
+ log1("Executing %s %s", client_config.script, name);
+ argv[0] = (char*) client_config.script;
+ argv[1] = (char*) name;
+ argv[2] = NULL;
+ spawn_and_wait(argv);
+
+ for (curr = envp; *curr; curr++) {
+ log2(" %s", *curr);
+ bb_unsetenv_and_free(*curr);
+ }
+ free(envp);
+}
+
+
+/*** Sending/receiving packets ***/
+
+static ALWAYS_INLINE uint32_t random_xid(void)
+{
+ return rand();
+}
+
+/* Initialize the packet with the proper defaults */
+static void init_packet(struct dhcp_packet *packet, char type)
+{
+ uint16_t secs;
+
+ /* Fill in: op, htype, hlen, cookie fields; message type option: */
+ udhcp_init_header(packet, type);
+
+ packet->xid = random_xid();
+
+ client_config.last_secs = monotonic_sec();
+ if (client_config.first_secs == 0)
+ client_config.first_secs = client_config.last_secs;
+ secs = client_config.last_secs - client_config.first_secs;
+ packet->secs = htons(secs);
+
+ memcpy(packet->chaddr, client_config.client_mac, 6);
+ if (client_config.clientid)
+ udhcp_add_binary_option(packet, client_config.clientid);
+}
+
+static void add_client_options(struct dhcp_packet *packet)
+{
+ int i, end, len;
+
+ udhcp_add_simple_option(packet, DHCP_MAX_SIZE, htons(IP_UDP_DHCP_SIZE));
+
+ /* Add a "param req" option with the list of options we'd like to have
+ * from stubborn DHCP servers. Pull the data from the struct in common.c.
+ * No bounds checking because it goes towards the head of the packet. */
+ end = udhcp_end_option(packet->options);
+ len = 0;
+ for (i = 1; i < DHCP_END; i++) {
+ if (client_config.opt_mask[i >> 3] & (1 << (i & 7))) {
+ packet->options[end + OPT_DATA + len] = i;
+ 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;
+ }
+
+ if (client_config.vendorclass)
+ udhcp_add_binary_option(packet, client_config.vendorclass);
+ if (client_config.hostname)
+ udhcp_add_binary_option(packet, client_config.hostname);
+ if (client_config.fqdn)
+ udhcp_add_binary_option(packet, client_config.fqdn);
+
+ /* Request broadcast replies if we have no IP addr */
+ if ((option_mask32 & OPT_B) && packet->ciaddr == 0)
+ packet->flags |= htons(BROADCAST_FLAG);
+
+ /* Add -x options if any */
+ {
+ struct option_set *curr = client_config.options;
+ while (curr) {
+ udhcp_add_binary_option(packet, curr->data);
+ curr = curr->next;
+ }
+// if (client_config.sname)
+// strncpy((char*)packet->sname, client_config.sname, sizeof(packet->sname) - 1);
+// if (client_config.boot_file)
+// strncpy((char*)packet->file, client_config.boot_file, sizeof(packet->file) - 1);
+ }
+
+ // This will be needed if we remove -V VENDOR_STR in favor of
+ // -x vendor:VENDOR_STR
+ //if (!udhcp_find_option(packet.options, DHCP_VENDOR))
+ // /* not set, set the default vendor ID */
+ // ...add (DHCP_VENDOR, "udhcp "BB_VER) opt...
+}
+
+/* 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 dhcp_packet *packet)
+{
+ return udhcp_send_raw_packet(packet,
+ /*src*/ INADDR_ANY, CLIENT_PORT,
+ /*dst*/ INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR,
+ client_config.ifindex);
+}
+
+/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_discover(uint32_t xid, uint32_t requested)
+{
+ struct dhcp_packet packet;
+
+ /* Fill in: op, htype, hlen, cookie, chaddr fields,
+ * random xid field (we override it below),
+ * client-id option (unless -C), message type option:
+ */
+ init_packet(&packet, DHCPDISCOVER);
+
+ packet.xid = xid;
+ if (requested)
+ udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
+
+ /* Add options: maxsize,
+ * optionally: hostname, fqdn, vendorclass,
+ * "param req" option according to -O, options specified with -x
+ */
+ add_client_options(&packet);
+
+ bb_info_msg("Sending discover...");
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+/* Broadcast a DHCP request message */
+/* RFC 2131 3.1 paragraph 3:
+ * "The client _broadcasts_ a DHCPREQUEST message..."
+ */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requested)
+{
+ struct dhcp_packet packet;
+ struct in_addr addr;
+
+/*
+ * RFC 2131 4.3.2 DHCPREQUEST message
+ * ...
+ * If the DHCPREQUEST message contains a 'server identifier'
+ * option, the message is in response to a DHCPOFFER message.
+ * Otherwise, the message is a request to verify or extend an
+ * existing lease. If the client uses a 'client identifier'
+ * in a DHCPREQUEST message, it MUST use that same 'client identifier'
+ * in all subsequent messages. If the client included a list
+ * of requested parameters in a DHCPDISCOVER message, it MUST
+ * include that list in all subsequent messages.
+ */
+ /* Fill in: op, htype, hlen, cookie, chaddr fields,
+ * random xid field (we override it below),
+ * client-id option (unless -C), message type option:
+ */
+ init_packet(&packet, DHCPREQUEST);
+
+ packet.xid = xid;
+ udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
+
+ udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
+
+ /* Add options: maxsize,
+ * optionally: hostname, fqdn, vendorclass,
+ * "param req" option according to -O, and options specified with -x
+ */
+ add_client_options(&packet);
+
+ addr.s_addr = requested;
+ bb_info_msg("Sending select for %s...", inet_ntoa(addr));
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+/* Unicast or broadcast a DHCP renew message */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
+{
+ struct dhcp_packet packet;
+
+/*
+ * RFC 2131 4.3.2 DHCPREQUEST message
+ * ...
+ * DHCPREQUEST generated during RENEWING state:
+ *
+ * 'server identifier' MUST NOT be filled in, 'requested IP address'
+ * option MUST NOT be filled in, 'ciaddr' MUST be filled in with
+ * client's IP address. In this situation, the client is completely
+ * configured, and is trying to extend its lease. This message will
+ * be unicast, so no relay agents will be involved in its
+ * transmission. Because 'giaddr' is therefore not filled in, the
+ * DHCP server will trust the value in 'ciaddr', and use it when
+ * replying to the client.
+ */
+ /* Fill in: op, htype, hlen, cookie, chaddr fields,
+ * random xid field (we override it below),
+ * client-id option (unless -C), message type option:
+ */
+ init_packet(&packet, DHCPREQUEST);
+
+ packet.xid = xid;
+ packet.ciaddr = ciaddr;
+
+ /* Add options: maxsize,
+ * optionally: hostname, fqdn, vendorclass,
+ * "param req" option according to -O, and options specified with -x
+ */
+ add_client_options(&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);
+}
+
+#if ENABLE_FEATURE_UDHCPC_ARPING
+/* Broadcast a DHCP decline message */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int send_decline(/*uint32_t xid,*/ uint32_t server, uint32_t requested)
+{
+ struct dhcp_packet packet;
+
+ /* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
+ * client-id option (unless -C), message type option:
+ */
+ init_packet(&packet, DHCPDECLINE);
+
+#if 0
+ /* RFC 2131 says DHCPDECLINE's xid is randomly selected by client,
+ * but in case the server is buggy and wants DHCPDECLINE's xid
+ * to match the xid which started entire handshake,
+ * we use the same xid we used in initial DHCPDISCOVER:
+ */
+ packet.xid = xid;
+#endif
+ /* DHCPDECLINE uses "requested ip", not ciaddr, to store offered IP */
+ udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
+
+ udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
+
+ bb_info_msg("Sending decline...");
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+#endif
+
+/* Unicast a DHCP release message */
+static int send_release(uint32_t server, uint32_t ciaddr)
+{
+ struct dhcp_packet packet;
+
+ /* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
+ * client-id option (unless -C), message type option:
+ */
+ init_packet(&packet, DHCPRELEASE);
+
+ /* DHCPRELEASE uses ciaddr, not "requested ip", to store IP being released */
+ packet.ciaddr = ciaddr;
+
+ udhcp_add_simple_option(&packet, 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 */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE int udhcp_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd)
+{
+ int bytes;
+ struct ip_udp_dhcp_packet packet;
+ uint16_t check;
+ unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))];
+ struct iovec iov;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+
+ /* used to use just safe_read(fd, &packet, sizeof(packet))
+ * but we need to check for TP_STATUS_CSUMNOTREADY :(
+ */
+ iov.iov_base = &packet;
+ iov.iov_len = sizeof(packet);
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ for (;;) {
+ bytes = recvmsg(fd, &msg, 0);
+ if (bytes < 0) {
+ if (errno == EINTR)
+ continue;
+ log1("Packet read error, ignoring");
+ /* NB: possible down interface, etc. Caller should pause. */
+ return bytes; /* returns -1 */
+ }
+ break;
+ }
+
+ if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) {
+ log1("Packet is too short, ignoring");
+ return -2;
+ }
+
+ if (bytes < ntohs(packet.ip.tot_len)) {
+ /* packet is bigger than sizeof(packet), we did partial read */
+ log1("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))
+ ) {
+ log1("Unrelated/bogus packet, ignoring");
+ return -2;
+ }
+
+ /* verify IP checksum */
+ check = packet.ip.check;
+ packet.ip.check = 0;
+ if (check != inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip))) {
+ log1("Bad IP header checksum, ignoring");
+ return -2;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_PACKET
+ && cmsg->cmsg_type == PACKET_AUXDATA
+ ) {
+ /* some VMs don't checksum UDP and TCP data
+ * they send to the same physical machine,
+ * here we detect this case:
+ */
+ struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg);
+ if (aux->tp_status & TP_STATUS_CSUMNOTREADY)
+ goto skip_udp_sum_check;
+ }
+ }
+
+ /* 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 != inet_cksum((uint16_t *)&packet, bytes)) {
+ log1("Packet with bad UDP checksum received, ignoring");
+ return -2;
+ }
+ skip_udp_sum_check:
+
+ if (packet.data.cookie != htonl(DHCP_MAGIC)) {
+ bb_info_msg("Packet with bad magic, ignoring");
+ return -2;
+ }
+
+ log1("Received a packet");
+ udhcp_dump_packet(&packet.data);
+
+ bytes -= sizeof(packet.ip) + sizeof(packet.udp);
+ memcpy(dhcp_pkt, &packet.data, bytes);
+ return bytes;
+}
+
+
+/*** Main ***/
+
+static int sockfd = -1;
+
+#define LISTEN_NONE 0
+#define LISTEN_KERNEL 1
+#define LISTEN_RAW 2
+static smallint listen_mode;
+
+/* initial state: (re)start DHCP negotiation */
+#define INIT_SELECTING 0
+/* discover was sent, DHCPOFFER reply received */
+#define REQUESTING 1
+/* select/renew was sent, DHCPACK reply received */
+#define BOUND 2
+/* half of lease passed, want to renew it by sending unicast renew requests */
+#define RENEWING 3
+/* renew requests were not answered, lease is almost over, send broadcast renew */
+#define REBINDING 4
+/* manually requested renew (SIGUSR1) */
+#define RENEW_REQUESTED 5
+/* release, possibly manually requested (SIGUSR2) */
+#define RELEASED 6
+static smallint state;
+
+static int 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?
+ */
+ static const struct sock_filter filter_instr[] = {
+ /* load 9th byte (protocol) */
+ BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9),
+ /* jump to L1 if it is IPPROTO_UDP, else to L4 */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 0, 6),
+ /* L1: load halfword from offset 6 (flags and frag offset) */
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6),
+ /* jump to L4 if any bits in frag offset field are set, else to L2 */
+ BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x1fff, 4, 0),
+ /* L2: skip IP header (load index reg with header len) */
+ BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0),
+ /* load udp destination port from halfword[header_len + 2] */
+ BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2),
+ /* jump to L3 if udp dport is CLIENT_PORT, else to L4 */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1),
+ /* L3: accept packet */
+ BPF_STMT(BPF_RET|BPF_K, 0xffffffff),
+ /* L4: discard packet */
+ BPF_STMT(BPF_RET|BPF_K, 0),
+ };
+ static const struct sock_fprog filter_prog = {
+ .len = sizeof(filter_instr) / sizeof(filter_instr[0]),
+ /* casting const away: */
+ .filter = (struct sock_filter *) filter_instr,
+ };
+
+ log1("Opening raw socket on ifindex %d", ifindex); //log2?
+
+ fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+ log1("Got raw socket fd"); //log2?
+
+ sock.sll_family = AF_PACKET;
+ sock.sll_protocol = htons(ETH_P_IP);
+ sock.sll_ifindex = ifindex;
+ xbind(fd, (struct sockaddr *) &sock, sizeof(sock));
+
+ if (CLIENT_PORT == 68) {
+ /* Use only if standard port is in use */
+ /* Ignoring error (kernel may lack support for this) */
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
+ sizeof(filter_prog)) >= 0)
+ log1("Attached filter to raw socket fd"); // log?
+ }
+
+ if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA,
+ &const_int_1, sizeof(int)) < 0
+ ) {
+ if (errno != ENOPROTOOPT)
+ log1("Can't set PACKET_AUXDATA on raw socket");
+ }
+
+ log1("Created raw socket");
+
+ return fd;
+}
+
+static void change_listen_mode(int new_mode)
+{
+ log1("Entering listen mode: %s",
+ new_mode != LISTEN_NONE
+ ? (new_mode == LISTEN_KERNEL ? "kernel" : "raw")
+ : "none"
+ );
+
+ listen_mode = new_mode;
+ if (sockfd >= 0) {
+ close(sockfd);
+ sockfd = -1;
+ }
+ if (new_mode == LISTEN_KERNEL)
+ sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+ else if (new_mode != LISTEN_NONE)
+ sockfd = udhcp_raw_socket(client_config.ifindex);
+ /* else LISTEN_NONE: sockfd stays closed */
+}
+
+/* Called only on SIGUSR1 */
+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;
+ }
+}
+
+static void perform_release(uint32_t server_addr, uint32_t requested_ip)
+{
+ 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;
+}
+
+static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
+{
+ uint8_t *storage;
+ int len = strnlen(str, 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;
+}
+
+#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
+
+//usage:#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+//usage:# define IF_UDHCP_VERBOSE(...) __VA_ARGS__
+//usage:#else
+//usage:# define IF_UDHCP_VERBOSE(...)
+//usage:#endif
+//usage:#define udhcpc_trivial_usage
+//usage: "[-fbnq"IF_UDHCP_VERBOSE("v")"oCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n"
+//usage: " [-V VENDOR] [-x OPT:VAL]... [-O OPT]..." IF_FEATURE_UDHCP_PORT(" [-P N]")
+//usage:#define udhcpc_full_usage "\n"
+//usage: IF_LONG_OPTS(
+//usage: "\n -i,--interface IFACE Interface to use (default eth0)"
+//usage: "\n -p,--pidfile FILE Create pidfile"
+//usage: "\n -s,--script PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")"
+//usage: "\n -B,--broadcast Request broadcast replies"
+//usage: "\n -t,--retries N Send up to N discover packets"
+//usage: "\n -T,--timeout N Pause between packets (default 3 seconds)"
+//usage: "\n -A,--tryagain N Wait N seconds after failure (default 20)"
+//usage: "\n -f,--foreground Run in foreground"
+//usage: USE_FOR_MMU(
+//usage: "\n -b,--background Background if lease is not obtained"
+//usage: )
+//usage: "\n -n,--now Exit if lease is not obtained"
+//usage: "\n -q,--quit Exit after obtaining lease"
+//usage: "\n -R,--release Release IP on exit"
+//usage: "\n -S,--syslog Log to syslog too"
+//usage: IF_FEATURE_UDHCP_PORT(
+//usage: "\n -P,--client-port N Use port N (default 68)"
+//usage: )
+//usage: IF_FEATURE_UDHCPC_ARPING(
+//usage: "\n -a,--arping Use arping to validate offered address"
+//usage: )
+//usage: "\n -O,--request-option OPT Request option OPT from server (cumulative)"
+//usage: "\n -o,--no-default-options Don't request any options (unless -O is given)"
+//usage: "\n -r,--request IP Request this IP address"
+//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)"
+//usage: "\n Examples of string, numeric, and hex byte opts:"
+//usage: "\n -x hostname:bbox - option 12"
+//usage: "\n -x lease:3600 - option 51 (lease time)"
+//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)"
+//usage: "\n -F,--fqdn NAME Ask server to update DNS mapping for NAME"
+//usage: "\n -V,--vendorclass VENDOR Vendor identifier (default 'udhcp VERSION')"
+//usage: "\n -C,--clientid-none Don't send MAC as client identifier"
+//usage: IF_UDHCP_VERBOSE(
+//usage: "\n -v Verbose"
+//usage: )
+//usage: )
+//usage: IF_NOT_LONG_OPTS(
+//usage: "\n -i IFACE Interface to use (default eth0)"
+//usage: "\n -p FILE Create pidfile"
+//usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")"
+//usage: "\n -B Request broadcast replies"
+//usage: "\n -t N Send up to N discover packets"
+//usage: "\n -T N Pause between packets (default 3 seconds)"
+//usage: "\n -A N Wait N seconds (default 20) after failure"
+//usage: "\n -f Run in foreground"
+//usage: USE_FOR_MMU(
+//usage: "\n -b Background if lease is not obtained"
+//usage: )
+//usage: "\n -n Exit if lease is not obtained"
+//usage: "\n -q Exit after obtaining lease"
+//usage: "\n -R Release IP on exit"
+//usage: "\n -S Log to syslog too"
+//usage: IF_FEATURE_UDHCP_PORT(
+//usage: "\n -P N Use port N (default 68)"
+//usage: )
+//usage: IF_FEATURE_UDHCPC_ARPING(
+//usage: "\n -a Use arping to validate offered address"
+//usage: )
+//usage: "\n -O OPT Request option OPT from server (cumulative)"
+//usage: "\n -o Don't request any options (unless -O is given)"
+//usage: "\n -r IP Request this IP address"
+//usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)"
+//usage: "\n Examples of string, numeric, and hex byte opts:"
+//usage: "\n -x hostname:bbox - option 12"
+//usage: "\n -x lease:3600 - option 51 (lease time)"
+//usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)"
+//usage: "\n -F NAME Ask server to update DNS mapping for NAME"
+//usage: "\n -V VENDOR Vendor identifier (default 'udhcp VERSION')"
+//usage: "\n -C Don't send MAC as client identifier"
+//usage: IF_UDHCP_VERBOSE(
+//usage: "\n -v Verbose"
+//usage: )
+//usage: )
+//usage: "\nSignals:"
+//usage: "\n USR1 Renew lease"
+//usage: "\n USR2 Release lease"
+
+
+int udhcpc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpc_main(int argc UNUSED_PARAM, char **argv)
+{
+ uint8_t *temp, *message;
+ const char *str_V, *str_h, *str_F, *str_r;
+ IF_FEATURE_UDHCP_PORT(char *str_P;)
+ void *clientid_mac_ptr;
+ llist_t *list_O = NULL;
+ llist_t *list_x = 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 = xid; /* for compiler */
+ int packet_num;
+ int timeout; /* must be signed */
+ unsigned already_waited_sec;
+ unsigned opt;
+ int max_fd;
+ int retval;
+ fd_set rfds;
+
+ /* Default options */
+ IF_FEATURE_UDHCP_PORT(SERVER_PORT = 67;)
+ IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)
+ client_config.interface = "eth0";
+ client_config.script = CONFIG_UDHCPC_DEFAULT_SCRIPT;
+ str_V = "udhcp "BB_VER;
+
+ /* Parse command line */
+ /* O,x: list; -T,-t,-A take numeric param */
+ opt_complementary = "O::x::T+:t+:A+" IF_UDHCP_VERBOSE(":vv") ;
+ IF_LONG_OPTS(applet_long_options = udhcpc_longopts;)
+ opt = getopt32(argv, "CV:H:h:F:i:np:qRr:s:T:t:SA:O:ox:fB"
+ USE_FOR_MMU("b")
+ IF_FEATURE_UDHCPC_ARPING("a")
+ IF_FEATURE_UDHCP_PORT("P:")
+ "v"
+ , &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
+ , &list_x
+ IF_FEATURE_UDHCP_PORT(, &str_P)
+ IF_UDHCP_VERBOSE(, &dhcp_verbose)
+ );
+ if (opt & (OPT_h|OPT_H)) {
+ //msg added 2011-11
+ bb_error_msg("option -h NAME is deprecated, use -x hostname:NAME");
+ client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0);
+ }
+ if (opt & OPT_F) {
+ /* FQDN option format: [0x51][len][flags][0][0]<fqdn> */
+ client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3);
+ /* Flag bits: 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 is in DNS format, i.e. <4>host<6>domain<3>com<0>,
+ * not "host.domain.com". Format 0 is obsolete.
+ * N: 1 = Client requests server to not update DNS (S must be 0 then)
+ * Two [0] bytes which follow are deprecated and must be 0.
+ */
+ client_config.fqdn[OPT_DATA + 0] = 0x1;
+ /*client_config.fqdn[OPT_DATA + 1] = 0; - xzalloc did it */
+ /*client_config.fqdn[OPT_DATA + 2] = 0; */
+ }
+ if (opt & OPT_r)
+ requested_ip = inet_addr(str_r);
+#if ENABLE_FEATURE_UDHCP_PORT
+ if (opt & OPT_P) {
+ CLIENT_PORT = xatou16(str_P);
+ SERVER_PORT = CLIENT_PORT - 1;
+ }
+#endif
+ while (list_O) {
+ char *optstr = llist_pop(&list_O);
+ unsigned n = bb_strtou(optstr, NULL, 0);
+ if (errno || n > 254) {
+ n = udhcp_option_idx(optstr);
+ n = dhcp_optflags[n].code;
+ }
+ client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+ }
+ if (!(opt & OPT_o)) {
+ unsigned i, n;
+ for (i = 0; (n = dhcp_optflags[i].code) != 0; i++) {
+ if (dhcp_optflags[i].flags & OPTION_REQ) {
+ client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+ }
+ }
+ }
+ while (list_x) {
+ char *optstr = llist_pop(&list_x);
+ char *colon = strchr(optstr, ':');
+ if (colon)
+ *colon = ' ';
+ /* now it looks similar to udhcpd's config file line:
+ * "optname optval", using the common routine: */
+ udhcp_str2optset(optstr, &client_config.options);
+ }
+
+ if (udhcp_read_interface(client_config.interface,
+ &client_config.ifindex,
+ NULL,
+ client_config.client_mac)
+ ) {
+ return 1;
+ }
+
+ clientid_mac_ptr = NULL;
+ if (!(opt & OPT_C) && !udhcp_find_option(client_config.options, DHCP_CLIENT_ID)) {
+ /* not suppressed and not set, set the default client ID */
+ client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7);
+ client_config.clientid[OPT_DATA] = 1; /* type: ethernet */
+ clientid_mac_ptr = client_config.clientid + OPT_DATA+1;
+ memcpy(clientid_mac_ptr, client_config.client_mac, 6);
+ }
+ if (str_V[0] != '\0') {
+ // can drop -V, str_V, client_config.vendorclass,
+ // but need to add "vendor" to the list of recognized
+ // string opts for this to work;
+ // and need to tweak add_client_options() too...
+ // ...so the question is, should we?
+ //bb_error_msg("option -V VENDOR is deprecated, use -x vendor:VENDOR");
+ client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0);
+ }
+
+#if !BB_MMU
+ /* on NOMMU reexec (i.e., background) early */
+ if (!(opt & OPT_f)) {
+ bb_daemonize_or_rexec(0 /* flags */, argv);
+ logmode = LOGMODE_NONE;
+ }
+#endif
+ if (opt & OPT_S) {
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ 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);
+ /* Set up the signal pipe */
+ udhcp_sp_setup();
+ /* We want random_xid to be random... */
+ srand(monotonic_us());
+
+ 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 (;;) {
+ struct timeval tv;
+ struct dhcp_packet packet;
+ /* silence "uninitialized!" warning */
+ unsigned timestamp_before_wait = timestamp_before_wait;
+
+ //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode);
+
+ /* Was opening raw or udp socket here
+ * if (listen_mode != LISTEN_NONE && sockfd < 0),
+ * but on fast network renew responses return faster
+ * than we open sockets. Thus this code is moved
+ * to change_listen_mode(). Thus we open listen socket
+ * BEFORE we send renew request (see "case BOUND:"). */
+
+ 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 with retval = 0, else... */
+ if ((int)tv.tv_sec > 0) {
+ log1("Waiting on select %u seconds", (int)tv.tv_sec);
+ timestamp_before_wait = (unsigned)monotonic_sec();
+ retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
+ if (retval < 0) {
+ /* EINTR? A signal was caught, don't panic */
+ if (errno == EINTR) {
+ already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
+ 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) {
+ /* When running on a bridge, the ifindex may have changed
+ * (e.g. if member interfaces were added/removed
+ * or if the status of the bridge changed).
+ * Refresh ifindex and client_mac:
+ */
+ if (udhcp_read_interface(client_config.interface,
+ &client_config.ifindex,
+ NULL,
+ client_config.client_mac)
+ ) {
+ goto ret0; /* iface is gone? */
+ }
+ if (clientid_mac_ptr)
+ memcpy(clientid_mac_ptr, client_config.client_mac, 6);
+
+ /* We will restart the wait in any case */
+ already_waited_sec = 0;
+
+ switch (state) {
+ case INIT_SELECTING:
+ if (!discover_retries || packet_num < discover_retries) {
+ if (packet_num == 0)
+ xid = random_xid();
+ /* broadcast */
+ send_discover(xid, requested_ip);
+ 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 REQUESTING:
+ if (!discover_retries || packet_num < discover_retries) {
+ /* send broadcast select packet */
+ send_select(xid, server_addr, requested_ip);
+ timeout = discover_timeout;
+ packet_num++;
+ continue;
+ }
+ /* Timed out, go back to init state.
+ * "discover...select...discover..." loops
+ * were seen in the wild. Treat them similarly
+ * to "no response to discover" case */
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ goto leasefail;
+ case BOUND:
+ /* 1/2 lease passed, enter renewing state */
+ state = RENEWING;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ change_listen_mode(LISTEN_KERNEL);
+ log1("Entering renew state");
+ /* fall right through */
+ case RENEW_REQUESTED: /* manual (SIGUSR1) renew */
+ case_RENEW_REQUESTED:
+ case RENEWING:
+ if (timeout > 60) {
+ /* send an unicast renew request */
+ /* Sometimes observed to fail (EADDRNOTAVAIL) to bind
+ * a new UDP socket for sending inside send_renew.
+ * I hazard to guess existing listening socket
+ * is somehow conflicting with it, but why is it
+ * not deterministic then?! Strange.
+ * Anyway, it does recover by eventually failing through
+ * into INIT_SELECTING state.
+ */
+ send_renew(xid, server_addr, requested_ip);
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter rebinding state */
+ log1("Entering rebinding state");
+ state = REBINDING;
+ /* fall right through */
+ case REBINDING:
+ /* Switch to bcast receive */
+ change_listen_mode(LISTEN_RAW);
+ /* Lease is *really* about to run out,
+ * try to find DHCP server using broadcast */
+ if (timeout > 0) {
+ /* send a broadcast renew request */
+ send_renew(xid, 0 /*INADDR_ANY*/, requested_ip);
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter init state */
+ bb_info_msg("Lease lost, entering init state");
+ udhcp_run_script(NULL, "deconfig");
+ state = INIT_SELECTING;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ /*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 */
+ } /* if select timed out */
+
+ /* select() didn't timeout, something happened */
+
+ /* Is it a signal? */
+ /* note: udhcp_sp_read checks FD_ISSET before reading */
+ switch (udhcp_sp_read(&rfds)) {
+ case SIGUSR1:
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ already_waited_sec = 0;
+ perform_renew();
+ if (state == RENEW_REQUESTED) {
+ /* We might be either on the same network
+ * (in which case renew might work),
+ * or we might be on a completely different one
+ * (in which case renew won't ever succeed).
+ * For the second case, must make sure timeout
+ * is not too big, or else we can send
+ * futile renew requests for hours.
+ * (Ab)use -A TIMEOUT value (usually 20 sec)
+ * as a cap on the timeout.
+ */
+ if (timeout > tryagain_timeout)
+ timeout = tryagain_timeout;
+ goto case_RENEW_REQUESTED;
+ }
+ /* Start things over */
+ packet_num = 0;
+ /* Kill any timeouts, user wants this to hurry along */
+ timeout = 0;
+ continue;
+ case SIGUSR2:
+ perform_release(server_addr, requested_ip);
+ timeout = INT_MAX;
+ continue;
+ case SIGTERM:
+ bb_info_msg("Received SIGTERM");
+ goto ret0;
+ }
+
+ /* Is it a packet? */
+ if (listen_mode == LISTEN_NONE || !FD_ISSET(sockfd, &rfds))
+ continue; /* no */
+
+ {
+ 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 */
+ bb_info_msg("Read error: %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) {
+ log1("xid %x (our is %x), ignoring packet",
+ (unsigned)packet.xid, (unsigned)xid);
+ continue;
+ }
+
+ /* Ignore packets that aren't for us */
+ if (packet.hlen != 6
+ || memcmp(packet.chaddr, client_config.client_mac, 6) != 0
+ ) {
+//FIXME: need to also check that last 10 bytes are zero
+ log1("chaddr does not match, ignoring packet"); // log2?
+ continue;
+ }
+
+ message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (message == NULL) {
+ bb_error_msg("no message type option, ignoring packet");
+ continue;
+ }
+
+ switch (state) {
+ case INIT_SELECTING:
+ /* Must be a DHCPOFFER */
+ if (*message == DHCPOFFER) {
+/* What exactly is server's IP? There are several values.
+ * Example DHCP offer captured with tchdump:
+ *
+ * 10.34.25.254:67 > 10.34.25.202:68 // IP header's src
+ * BOOTP fields:
+ * Your-IP 10.34.25.202
+ * Server-IP 10.34.32.125 // "next server" IP
+ * Gateway-IP 10.34.25.254 // relay's address (if DHCP relays are in use)
+ * DHCP options:
+ * DHCP-Message Option 53, length 1: Offer
+ * Server-ID Option 54, length 4: 10.34.255.7 // "server ID"
+ * Default-Gateway Option 3, length 4: 10.34.25.254 // router
+ *
+ * We think that real server IP (one to use in renew/release)
+ * is one in Server-ID option. But I am not 100% sure.
+ * IP header's src and Gateway-IP (same in this example)
+ * might work too.
+ * "Next server" and router are definitely wrong ones to use, though...
+ */
+#if 0//CVE-2018-20679
+ temp = udhcp_get_option(&packet, DHCP_SERVER_ID);
+ if (!temp) {
+ bb_error_msg("no server ID, ignoring packet");
+ continue;
+ /* still selecting - this server looks bad */
+ }
+ /* it IS unaligned sometimes, don't "optimize" */
+ move_from_unaligned32(server_addr, temp);
+#else
+/* We used to ignore pcakets without DHCP_SERVER_ID.
+ * I've got user reports from people who run "address-less" servers.
+ * They either supply DHCP_SERVER_ID of 0.0.0.0 or don't supply it at all.
+ * They say ISC DHCP client supports this case.
+ */
+ server_addr = 0;
+ temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
+ if (!temp) {
+ bb_error_msg("no server ID, using 0.0.0.0");
+ } else {
+ /* it IS unaligned sometimes, don't "optimize" */
+ move_from_unaligned32(server_addr, temp);
+ }
+#endif
+ /*xid = packet.xid; - already is */
+ requested_ip = packet.yiaddr;
+
+ /* enter requesting state */
+ state = REQUESTING;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ }
+ continue;
+ case REQUESTING:
+ case RENEWING:
+ case RENEW_REQUESTED:
+ case REBINDING:
+ if (*message == DHCPACK) {
+ uint32_t lease_seconds;
+ struct in_addr temp_addr;
+
+ temp = udhcp_get_option32(&packet, DHCP_LEASE_TIME);//CVE-2018-20679
+ 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" */
+ move_from_unaligned32(lease_seconds, temp);
+ lease_seconds = ntohl(lease_seconds);
+ /* paranoia: must not be too small and not prone to overflows */
+ if (lease_seconds < 0x10)
+ lease_seconds = 0x10;
+ if (lease_seconds >= 0x10000000)
+ lease_seconds = 0x0fffffff;
+ }
+#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,
+ NULL,
+ (uint32_t) 0,
+ client_config.client_mac,
+ 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;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ 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;
+ 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 == REQUESTING ? "bound" : "renew");
+
+ state = BOUND;
+ change_listen_mode(LISTEN_NONE);
+ if (opt & OPT_q) { /* quit after lease */
+ goto ret0;
+ }
+ /* future renew failures should not exit (JM) */
+ opt &= ~OPT_n;
+#if BB_MMU /* NOMMU case backgrounded earlier */
+ if (!(opt & OPT_f)) {
+ client_background();
+ /* do not background again! */
+ opt = ((opt & ~OPT_b) | OPT_f);
+ }
+#endif
+ /* make future renew packets use different xid */
+ /* xid = random_xid(); ...but why bother? */
+ already_waited_sec = 0;
+ continue; /* back to main loop */
+ }
+ if (*message == DHCPNAK) {
+#if 1//CVE-2018-20679
+ /* If network has more than one DHCP server,
+ * "wrong" server can reply first, with a NAK.
+ * Do not interpret it as a NAK from "our" server.
+ */
+ if (server_addr != 0) {
+ uint32_t svid;
+ uint8_t *temp;
+
+ temp = udhcp_get_option32(&packet, DHCP_SERVER_ID);
+ if (!temp) {
+non_matching_svid:
+ log1("received DHCP NAK with wrong"
+ " server ID, ignoring packet");
+ continue;
+ }
+ move_from_unaligned32(svid, temp);
+ if (svid != server_addr)
+ goto non_matching_svid;
+ }
+#endif
+ /* 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;
+ client_config.first_secs = 0; /* make secs field count from 0 */
+ requested_ip = 0;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ }
+ continue;
+ /* case BOUND: - ignore all packets */
+ /* case RELEASED: - ignore all packets */
+ }
+ /* back to main loop */
+ } /* for (;;) - main loop ends */
+
+ ret0:
+ if (opt & OPT_R) /* release on quit */
+ perform_release(server_addr, requested_ip);
+ retval = 0;
+ ret:
+ /*if (client_config.pidfile) - remove_pidfile has its own check */
+ remove_pidfile(client_config.pidfile);
+ return retval;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/dhcpc.h b/ap/app/busybox/src/networking/udhcp/dhcpc.h
new file mode 100644
index 0000000..2859a07
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dhcpc.h
@@ -0,0 +1,39 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#ifndef UDHCP_DHCPC_H
+#define UDHCP_DHCPC_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+struct client_config_t {
+ uint8_t client_mac[6]; /* Our mac address */
+ IF_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 */
+ struct option_set *options; /* list of DHCP options to send to server */
+ 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 */
+
+ uint16_t first_secs;
+ uint16_t last_secs;
+} FIX_ALIASING;
+
+/* 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
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/dhcpd.c b/ap/app/busybox/src/networking/udhcp/dhcpd.c
new file mode 100755
index 0000000..279a059
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dhcpd.c
@@ -0,0 +1,716 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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
+ *
+ * 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.
+ */
+
+//usage:#define udhcpd_trivial_usage
+//usage: "[-fS]" IF_FEATURE_UDHCP_PORT(" [-P N]") " [CONFFILE]"
+//usage:#define udhcpd_full_usage "\n\n"
+//usage: "DHCP server\n"
+//usage: "\n -f Run in foreground"
+//usage: "\n -S Log to syslog too"
+//usage: IF_FEATURE_UDHCP_PORT(
+//usage: "\n -P N Use port N (default 67)"
+//usage: )
+
+#include <syslog.h>
+#include "common.h"
+#include "dhcpc.h"
+#include "dhcpd.h"
+
+
+/* Send a packet to a specific mac address and ip address by creating our own ip packet */
+static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadcast)
+{
+ const uint8_t *chaddr;
+ uint32_t ciaddr;
+
+ // Was:
+ //if (force_broadcast) { /* broadcast */ }
+ //else if (dhcp_pkt->ciaddr) { /* unicast to dhcp_pkt->ciaddr */ }
+ //else if (dhcp_pkt->flags & htons(BROADCAST_FLAG)) { /* broadcast */ }
+ //else { /* unicast to dhcp_pkt->yiaddr */ }
+ // But this is wrong: yiaddr is _our_ idea what client's IP is
+ // (for example, from lease file). Client may not know that,
+ // and may not have UDP socket listening on that IP!
+ // We should never unicast to dhcp_pkt->yiaddr!
+ // dhcp_pkt->ciaddr, OTOH, comes from client's request packet,
+ // and can be used.
+
+ if (force_broadcast
+ || (dhcp_pkt->flags & htons(BROADCAST_FLAG))
+ || dhcp_pkt->ciaddr == 0
+ ) {
+ log1("Broadcasting packet to client");
+ ciaddr = INADDR_BROADCAST;
+ chaddr = MAC_BCAST_ADDR;
+ } else {
+ log1("Unicasting packet to client ciaddr");
+ ciaddr = dhcp_pkt->ciaddr;
+ chaddr = dhcp_pkt->chaddr;
+ }
+
+ udhcp_send_raw_packet(dhcp_pkt,
+ /*src*/ server_config.server_nip, SERVER_PORT,
+ /*dst*/ ciaddr, CLIENT_PORT, chaddr,
+ server_config.ifindex);
+}
+
+/* Send a packet to gateway_nip using the kernel ip stack */
+static void send_packet_to_relay(struct dhcp_packet *dhcp_pkt)
+{
+ log1("Forwarding packet to relay");
+
+ udhcp_send_kernel_packet(dhcp_pkt,
+ server_config.server_nip, SERVER_PORT,
+ dhcp_pkt->gateway_nip, SERVER_PORT);
+}
+
+static void send_packet(struct dhcp_packet *dhcp_pkt, int force_broadcast)
+{
+ if (dhcp_pkt->gateway_nip)
+ send_packet_to_relay(dhcp_pkt);
+ else
+ send_packet_to_client(dhcp_pkt, force_broadcast);
+}
+
+static void init_packet(struct dhcp_packet *packet, struct dhcp_packet *oldpacket, char type)
+{
+ /* Sets op, htype, hlen, cookie fields
+ * and adds DHCP_MESSAGE_TYPE option */
+ udhcp_init_header(packet, type);
+
+ packet->xid = oldpacket->xid;
+ memcpy(packet->chaddr, oldpacket->chaddr, sizeof(oldpacket->chaddr));
+ packet->flags = oldpacket->flags;
+ packet->gateway_nip = oldpacket->gateway_nip;
+ packet->ciaddr = oldpacket->ciaddr;
+ udhcp_add_simple_option(packet, DHCP_SERVER_ID, server_config.server_nip);
+}
+
+/* Fill options field, siaddr_nip, and sname and boot_file fields.
+ * TODO: teach this code to use overload option.
+ */
+static void add_server_options(struct dhcp_packet *packet)
+{
+ struct option_set *curr = server_config.options;
+
+ while (curr) {
+ if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+ udhcp_add_binary_option(packet, curr->data);
+ curr = curr->next;
+ }
+
+ packet->siaddr_nip = server_config.siaddr_nip;
+
+ 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);
+}
+
+static uint32_t select_lease_time(struct dhcp_packet *packet)
+{
+ uint32_t lease_time_sec = server_config.max_lease_sec;
+ uint8_t *lease_time_opt = udhcp_get_option32(packet, DHCP_LEASE_TIME);//CVE-2018-20679
+ if (lease_time_opt) {
+ move_from_unaligned32(lease_time_sec, lease_time_opt);
+ lease_time_sec = ntohl(lease_time_sec);
+ if (lease_time_sec > server_config.max_lease_sec)
+ lease_time_sec = server_config.max_lease_sec;
+ if (lease_time_sec < server_config.min_lease_sec)
+ lease_time_sec = server_config.min_lease_sec;
+ }
+ return lease_time_sec;
+}
+
+/* We got a DHCP DISCOVER. Send an OFFER. */
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE void send_offer(struct dhcp_packet *oldpacket,
+ uint32_t static_lease_nip,
+ struct dyn_lease *lease,
+ uint8_t *requested_ip_opt)
+{
+ struct dhcp_packet packet;
+ uint32_t lease_time_sec;
+ struct in_addr addr;
+
+ init_packet(&packet, oldpacket, DHCPOFFER);
+
+ /* If it is a static lease, use its IP */
+ packet.yiaddr = static_lease_nip;
+ /* Else: */
+ if (!static_lease_nip) {
+ /* We have no static lease for client's chaddr */
+ uint32_t req_nip;
+ const char *p_host_name;
+
+ if (lease) {
+ /* We have a dynamic lease for client's chaddr.
+ * Reuse its IP (even if lease is expired).
+ * Note that we ignore requested IP in this case.
+ */
+ packet.yiaddr = lease->lease_nip;
+ }
+ /* Or: if client has requested an IP */
+ else if (requested_ip_opt != NULL
+ /* (read IP) */
+ && (move_from_unaligned32(req_nip, requested_ip_opt), 1)
+ /* and the IP is in the lease range */
+ && ntohl(req_nip) >= server_config.start_ip
+ && ntohl(req_nip) <= server_config.end_ip
+ /* and */
+ && ( !(lease = find_lease_by_nip(req_nip)) /* is not already taken */
+ || is_expired_lease(lease) /* or is taken, but expired */
+ )
+ ) {
+ packet.yiaddr = req_nip;
+ }
+ else {
+ /* Otherwise, find a free IP */
+ packet.yiaddr = find_free_or_expired_nip(oldpacket->chaddr);
+ }
+
+ if (!packet.yiaddr) {
+ bb_error_msg("no free IP addresses. OFFER abandoned");
+ return;
+ }
+ /* Reserve the IP for a short time hoping to get DHCPREQUEST soon */
+ p_host_name = (const char*) udhcp_get_option(oldpacket, DHCP_HOST_NAME);
+ lease = add_lease(packet.chaddr, packet.yiaddr,
+ server_config.offer_time,
+ p_host_name,
+ p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
+ );
+ if (!lease) {
+ bb_error_msg("no free IP addresses. OFFER abandoned");
+ return;
+ }
+ }
+
+ lease_time_sec = select_lease_time(oldpacket);
+ udhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
+ add_server_options(&packet);
+
+ addr.s_addr = packet.yiaddr;
+ bb_info_msg("Sending OFFER of %s", inet_ntoa(addr));
+ /* send_packet emits error message itself if it detects failure */
+ send_packet(&packet, /*force_bcast:*/ 0);
+}
+
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE void send_NAK(struct dhcp_packet *oldpacket)
+{
+ struct dhcp_packet packet;
+
+ init_packet(&packet, oldpacket, DHCPNAK);
+
+ log1("Sending NAK");
+ send_packet(&packet, /*force_bcast:*/ 1);
+}
+
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE void send_ACK(struct dhcp_packet *oldpacket, uint32_t yiaddr)
+{
+ struct dhcp_packet packet;
+ uint32_t lease_time_sec;
+ struct in_addr addr;
+ const char *p_host_name;
+
+ init_packet(&packet, oldpacket, DHCPACK);
+ packet.yiaddr = yiaddr;
+
+ lease_time_sec = select_lease_time(oldpacket);
+ udhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
+
+ add_server_options(&packet);
+
+ addr.s_addr = yiaddr;
+ bb_info_msg("Sending ACK to %s", inet_ntoa(addr));
+ send_packet(&packet, /*force_bcast:*/ 0);
+
+ p_host_name = (const char*) udhcp_get_option(oldpacket, DHCP_HOST_NAME);
+ add_lease(packet.chaddr, packet.yiaddr,
+ lease_time_sec,
+ p_host_name,
+ p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
+ );
+ if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) {
+ /* rewrite the file with leases at every new acceptance */
+ write_leases();
+ }
+}
+
+/* NOINLINE: limit stack usage in caller */
+static NOINLINE void send_inform(struct dhcp_packet *oldpacket)
+{
+ struct dhcp_packet packet;
+
+ /* "If a client has obtained a network address through some other means
+ * (e.g., manual configuration), it may use a DHCPINFORM request message
+ * to obtain other local configuration parameters. Servers receiving a
+ * DHCPINFORM message construct a DHCPACK message with any local
+ * configuration parameters appropriate for the client without:
+ * allocating a new address, checking for an existing binding, filling
+ * in 'yiaddr' or including lease time parameters. The servers SHOULD
+ * unicast the DHCPACK reply to the address given in the 'ciaddr' field
+ * of the DHCPINFORM message.
+ * ...
+ * The server responds to a DHCPINFORM message by sending a DHCPACK
+ * message directly to the address given in the 'ciaddr' field
+ * of the DHCPINFORM message. The server MUST NOT send a lease
+ * expiration time to the client and SHOULD NOT fill in 'yiaddr'."
+ */
+//TODO: do a few sanity checks: is ciaddr set?
+//Better yet: is ciaddr == IP source addr?
+ init_packet(&packet, oldpacket, DHCPACK);
+ add_server_options(&packet);
+
+ send_packet(&packet, /*force_bcast:*/ 0);
+}
+
+
+/* globals */
+struct dyn_lease *g_leases;
+/* struct server_config_t server_config is in bb_common_bufsiz1 */
+extern int base_ip_on_mac = 0;
+int execute_cmd(const char *cmd, char *out_data, int out_len)
+{
+ int ret = -1;
+
+#if 1
+ FILE *fp;
+
+ fp = popen(cmd, "r");
+ if(NULL == fp) {
+ printf("popen execute[%s] fail, error:%s \n", cmd, strerror(errno));
+ return -1;
+ }
+
+ if(NULL != out_data){
+ while(fgets(out_data, out_len, fp) != NULL) {
+ if('\n' == out_data[strlen(out_data)-1]) {
+ out_data[strlen(out_data)-1] = '\0';
+ }
+ printf("command:[%s] out_data:[%s]\r\n", cmd, out_data);
+ }
+ }
+
+ ret = pclose(fp);
+ if(0 != ret) {
+ printf("Close [%s] out put point fail, ret = %d, error:%s \n", cmd, ret, strerror(errno));
+ return -1;
+ } else {
+ printf("command:[%s], child hork end status:[%d]\n", cmd, ret);
+ }
+
+ return 0;
+
+#else
+ ret = system(cmd);
+
+ if(-1 == ret) {
+ upi_log("System [%s] fail, error:%s \n", cmd, strerror(errno));
+ return -1;
+ }else{
+ printf("System [%s] fail, return:%d \n", cmd, ret);
+ }
+
+ return 0;
+#endif
+
+}
+
+int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpd_main(int argc UNUSED_PARAM, char **argv)
+{
+ int server_socket = -1, retval, max_sock;
+ uint8_t *state;
+ unsigned timeout_end;
+ unsigned num_ips;
+ unsigned opt;
+ struct option_set *option;
+ IF_FEATURE_UDHCP_PORT(char *str_P;)
+ char value[8] = {0};
+
+#if ENABLE_FEATURE_UDHCP_PORT
+ SERVER_PORT = 67;
+ CLIENT_PORT = 68;
+#endif
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+ opt_complementary = "vv";
+#endif
+ if(execute_cmd("nv get base_ip_on_mac", value, sizeof(value)) == 0)
+ {
+ if(strcmp(value, "1") == 0)
+ {
+ base_ip_on_mac = 1;
+ }
+ }
+ opt = getopt32(argv, "fSv"
+ IF_FEATURE_UDHCP_PORT("P:", &str_P)
+ IF_UDHCP_VERBOSE(, &dhcp_verbose)
+ );
+ if (!(opt & 1)) { /* no -f */
+ bb_daemonize_or_rexec(0, argv);
+ logmode = LOGMODE_NONE;
+ }
+ /* update argv after the possible vfork+exec in daemonize */
+ argv += optind;
+ if (opt & 2) { /* -S */
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ logmode |= LOGMODE_SYSLOG;
+ }
+#if ENABLE_FEATURE_UDHCP_PORT
+ if (opt & 8) { /* -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("can't create pidfile %s", pidfile); */
+
+ bb_info_msg("%s (v"BB_VER") started", applet_name);
+
+ option = udhcp_find_option(server_config.options, DHCP_LEASE_TIME);
+ server_config.max_lease_sec = DEFAULT_LEASE_TIME;
+ if (option) {
+ move_from_unaligned32(server_config.max_lease_sec, option->data + OPT_DATA);
+ server_config.max_lease_sec = ntohl(server_config.max_lease_sec);
+ }
+
+ /* 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;
+ }
+
+ g_leases = xzalloc(server_config.max_leases * sizeof(g_leases[0]));
+ read_leases(server_config.lease_file);
+
+ if (udhcp_read_interface(server_config.interface,
+ &server_config.ifindex,
+ &server_config.server_nip,
+ server_config.server_mac)
+ ) {
+ retval = 1;
+ goto ret;
+ }
+
+ /* Setup the signal pipe */
+ udhcp_sp_setup();
+
+ continue_with_autotime:
+ timeout_end = monotonic_sec() + server_config.auto_time;
+ while (1) { /* loop until universe collapses */
+ fd_set rfds;
+ struct dhcp_packet packet;
+ int bytes;
+ struct timeval tv;
+ uint8_t *server_id_opt;
+ uint8_t *requested_ip_opt;
+ uint32_t requested_nip = requested_nip; /* for compiler */
+ uint32_t static_lease_nip;
+ struct dyn_lease *lease, fake_lease;
+
+ 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();
+ goto continue_with_autotime;
+ }
+ if (retval < 0 && errno != EINTR) {
+ log1("Error on select");
+ continue;
+ }
+
+ switch (udhcp_sp_read(&rfds)) {
+ case SIGUSR1:
+ bb_info_msg("Received SIGUSR1");
+ write_leases();
+ /* why not just reset the timeout, eh */
+ goto continue_with_autotime;
+ case SIGTERM:
+ bb_info_msg("Received SIGTERM");
+ write_leases();
+ goto ret0;
+ case 0: /* no signal: read a packet */
+ break;
+ default: /* signal or error (probably EINTR): back to select */
+ continue;
+ }
+
+ bytes = udhcp_recv_kernel_packet(&packet, server_socket);
+ if (bytes < 0) {
+ /* bytes can also be -2 ("bad packet data") */
+ if (bytes == -1 && errno != EINTR) {
+ log1("Read error: %s, reopening socket", strerror(errno));
+ close(server_socket);
+ server_socket = -1;
+ }
+ continue;
+ }
+ if (packet.hlen != 6) {
+ bb_error_msg("MAC length != 6, ignoring packet");
+ continue;
+ }
+ if (packet.op != BOOTREQUEST) {
+ bb_error_msg("not a REQUEST, ignoring packet");
+ continue;
+ }
+ state = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (state == NULL || state[0] < DHCP_MINTYPE || state[0] > DHCP_MAXTYPE) {
+ bb_error_msg("no or bad message type option, ignoring packet");
+ continue;
+ }
+
+ /* Get SERVER_ID if present */
+ server_id_opt = udhcp_get_option32(&packet, DHCP_SERVER_ID);//CVE-2018-20679
+ if (server_id_opt) {
+ uint32_t server_id_network_order;
+ move_from_unaligned32(server_id_network_order, server_id_opt);
+ if (server_id_network_order != server_config.server_nip) {
+ /* client talks to somebody else */
+ log1("server ID doesn't match, ignoring");
+ continue;
+ }
+ }
+
+ /* Look for a static/dynamic lease */
+ static_lease_nip = get_static_nip_by_mac(server_config.static_leases, &packet.chaddr);
+ if (static_lease_nip) {
+ bb_info_msg("Found static lease: %x", static_lease_nip);
+ memcpy(&fake_lease.lease_mac, &packet.chaddr, 6);
+ fake_lease.lease_nip = static_lease_nip;
+ fake_lease.expires = 0;
+ lease = &fake_lease;
+ } else {
+ lease = find_lease_by_mac(packet.chaddr);
+ }
+
+ /* Get REQUESTED_IP if present */
+ requested_ip_opt = udhcp_get_option32(&packet, DHCP_REQUESTED_IP);//CVE-2018-20679
+ if (requested_ip_opt) {
+ move_from_unaligned32(requested_nip, requested_ip_opt);
+ }
+
+ switch (state[0]) {
+
+ case DHCPDISCOVER:
+ log1("Received DISCOVER");
+
+ send_offer(&packet, static_lease_nip, lease, requested_ip_opt);
+ break;
+
+ case DHCPREQUEST:
+ log1("Received REQUEST");
+/* RFC 2131:
+
+o DHCPREQUEST generated during SELECTING state:
+
+ Client inserts the address of the selected server in 'server
+ identifier', 'ciaddr' MUST be zero, 'requested IP address' MUST be
+ filled in with the yiaddr value from the chosen DHCPOFFER.
+
+ Note that the client may choose to collect several DHCPOFFER
+ messages and select the "best" offer. The client indicates its
+ selection by identifying the offering server in the DHCPREQUEST
+ message. If the client receives no acceptable offers, the client
+ may choose to try another DHCPDISCOVER message. Therefore, the
+ servers may not receive a specific DHCPREQUEST from which they can
+ decide whether or not the client has accepted the offer.
+
+o DHCPREQUEST generated during INIT-REBOOT state:
+
+ 'server identifier' MUST NOT be filled in, 'requested IP address'
+ option MUST be filled in with client's notion of its previously
+ assigned address. 'ciaddr' MUST be zero. The client is seeking to
+ verify a previously allocated, cached configuration. Server SHOULD
+ send a DHCPNAK message to the client if the 'requested IP address'
+ is incorrect, or is on the wrong network.
+
+ Determining whether a client in the INIT-REBOOT state is on the
+ correct network is done by examining the contents of 'giaddr', the
+ 'requested IP address' option, and a database lookup. If the DHCP
+ server detects that the client is on the wrong net (i.e., the
+ result of applying the local subnet mask or remote subnet mask (if
+ 'giaddr' is not zero) to 'requested IP address' option value
+ doesn't match reality), then the server SHOULD send a DHCPNAK
+ message to the client.
+
+ If the network is correct, then the DHCP server should check if
+ the client's notion of its IP address is correct. If not, then the
+ server SHOULD send a DHCPNAK message to the client. If the DHCP
+ server has no record of this client, then it MUST remain silent,
+ and MAY output a warning to the network administrator. This
+ behavior is necessary for peaceful coexistence of non-
+ communicating DHCP servers on the same wire.
+
+ If 'giaddr' is 0x0 in the DHCPREQUEST message, the client is on
+ the same subnet as the server. The server MUST broadcast the
+ DHCPNAK message to the 0xffffffff broadcast address because the
+ client may not have a correct network address or subnet mask, and
+ the client may not be answering ARP requests.
+
+ If 'giaddr' is set in the DHCPREQUEST message, the client is on a
+ different subnet. The server MUST set the broadcast bit in the
+ DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
+ client, because the client may not have a correct network address
+ or subnet mask, and the client may not be answering ARP requests.
+
+o DHCPREQUEST generated during RENEWING state:
+
+ 'server identifier' MUST NOT be filled in, 'requested IP address'
+ option MUST NOT be filled in, 'ciaddr' MUST be filled in with
+ client's IP address. In this situation, the client is completely
+ configured, and is trying to extend its lease. This message will
+ be unicast, so no relay agents will be involved in its
+ transmission. Because 'giaddr' is therefore not filled in, the
+ DHCP server will trust the value in 'ciaddr', and use it when
+ replying to the client.
+
+ A client MAY choose to renew or extend its lease prior to T1. The
+ server may choose not to extend the lease (as a policy decision by
+ the network administrator), but should return a DHCPACK message
+ regardless.
+
+o DHCPREQUEST generated during REBINDING state:
+
+ 'server identifier' MUST NOT be filled in, 'requested IP address'
+ option MUST NOT be filled in, 'ciaddr' MUST be filled in with
+ client's IP address. In this situation, the client is completely
+ configured, and is trying to extend its lease. This message MUST
+ be broadcast to the 0xffffffff IP broadcast address. The DHCP
+ server SHOULD check 'ciaddr' for correctness before replying to
+ the DHCPREQUEST.
+
+ The DHCPREQUEST from a REBINDING client is intended to accommodate
+ sites that have multiple DHCP servers and a mechanism for
+ maintaining consistency among leases managed by multiple servers.
+ A DHCP server MAY extend a client's lease only if it has local
+ administrative authority to do so.
+*/
+ if (!requested_ip_opt) {
+ requested_nip = packet.ciaddr;
+ if (requested_nip == 0) {
+ log1("no requested IP and no ciaddr, ignoring");
+ break;
+ }
+ }
+ if (lease && requested_nip == lease->lease_nip) {
+ /* client requested or configured IP matches the lease.
+ * ACK it, and bump lease expiration time. */
+ send_ACK(&packet, lease->lease_nip);
+ break;
+ }
+ /* No lease for this MAC, or lease IP != requested IP */
+
+ if (server_id_opt /* client is in SELECTING state */
+ || requested_ip_opt /* client is in INIT-REBOOT state */
+ ) {
+ /* "No, we don't have this IP for you" */
+ send_NAK(&packet);
+ } /* else: client is in RENEWING or REBINDING, do not answer */
+
+ break;
+
+ case DHCPDECLINE:
+ /* RFC 2131:
+ * "If the server receives a DHCPDECLINE message,
+ * the client has discovered through some other means
+ * that the suggested network address is already
+ * in use. The server MUST mark the network address
+ * as not available and SHOULD notify the local
+ * sysadmin of a possible configuration problem."
+ *
+ * SERVER_ID must be present,
+ * REQUESTED_IP must be present,
+ * chaddr must be filled in,
+ * ciaddr must be 0 (we do not check this)
+ */
+ log1("Received DECLINE");
+ if (server_id_opt
+ && requested_ip_opt
+ && lease /* chaddr matches this lease */
+ && requested_nip == lease->lease_nip
+ ) {
+ memset(lease->lease_mac, 0, sizeof(lease->lease_mac));
+ lease->expires = time(NULL) + server_config.decline_time;
+ }
+ break;
+
+ case DHCPRELEASE:
+ /* "Upon receipt of a DHCPRELEASE message, the server
+ * marks the network address as not allocated."
+ *
+ * SERVER_ID must be present,
+ * REQUESTED_IP must not be present (we do not check this),
+ * chaddr must be filled in,
+ * ciaddr must be filled in
+ */
+ log1("Received RELEASE");
+ if (server_id_opt
+ && lease /* chaddr matches this lease */
+ && packet.ciaddr == lease->lease_nip
+ ) {
+ lease->expires = time(NULL);
+ }
+ break;
+
+ case DHCPINFORM:
+ log1("Received INFORM");
+ send_inform(&packet);
+ break;
+ }
+ }
+ ret0:
+ retval = 0;
+ ret:
+ /*if (server_config.pidfile) - server_config.pidfile is never NULL */
+ remove_pidfile(server_config.pidfile);
+ return retval;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/dhcpd.h b/ap/app/busybox/src/networking/udhcp/dhcpd.h
new file mode 100644
index 0000000..7c801bf
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dhcpd.h
@@ -0,0 +1,126 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#ifndef UDHCP_DHCPD_H
+#define UDHCP_DHCPD_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* Defaults you may want to tweak */
+/* Default max_lease_sec */
+#define DEFAULT_LEASE_TIME (60*60*24 * 10)
+#define LEASES_FILE CONFIG_DHCPD_LEASES_FILE
+/* Where to find the DHCP server configuration file */
+#define DHCPD_CONF_FILE "/etc/udhcpd.conf"
+
+
+struct static_lease {
+ struct static_lease *next;
+ uint32_t nip;
+ uint8_t mac[6];
+};
+
+struct server_config_t {
+ char *interface; /* interface to use */
+//TODO: ifindex, server_nip, server_mac
+// are obtained from interface name.
+// Instead of querying them *once*, create update_server_network_data_cache()
+// and call it before any usage of these fields.
+// update_server_network_data_cache() must re-query data
+// if more than N seconds have passed after last use.
+ int ifindex;
+ uint32_t server_nip;
+#if ENABLE_FEATURE_UDHCP_PORT
+ uint16_t port;
+#endif
+ uint8_t server_mac[6]; /* our MAC address (used only for ARP probing) */
+ struct option_set *options; /* list of DHCP options loaded from the config file */
+ /* 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 */
+ uint32_t max_lease_sec; /* maximum lease time (host order) */
+ uint32_t min_lease_sec; /* minimum lease time a client can request */
+ uint32_t max_leases; /* maximum number of leases (including reserved addresses) */
+ 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 siaddr_nip; /* "next server" bootp option */
+ char *lease_file;
+ char *pidfile;
+ char *notify_file; /* what to run whenever leases are written */
+ 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 */
+} FIX_ALIASING;
+
+#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
+
+
+typedef uint32_t leasetime_t;
+typedef int32_t signed_leasetime_t;
+
+struct dyn_lease {
+ /* Unix time when lease expires. Kept in memory in host order.
+ * When written to file, converted to network order
+ * and adjusted (current time subtracted) */
+ leasetime_t expires;
+ /* "nip": IP in network order */
+ uint32_t lease_nip;
+ /* We use lease_mac[6], since e.g. ARP probing uses
+ * only 6 first bytes anyway. We check received dhcp packets
+ * that their hlen == 6 and thus chaddr has only 6 significant bytes
+ * (dhcp packet has chaddr[16], not [6])
+ */
+ uint8_t lease_mac[6];
+ char hostname[20];
+ uint8_t pad[2];
+ /* total size is a multiply of 4 */
+} PACKED;
+
+extern struct dyn_lease *g_leases;
+
+struct dyn_lease *add_lease(
+ const uint8_t *chaddr, uint32_t yiaddr,
+ leasetime_t leasetime,
+ const char *hostname, int hostname_len
+ ) FAST_FUNC;
+int is_expired_lease(struct dyn_lease *lease) FAST_FUNC;
+struct dyn_lease *find_lease_by_mac(const uint8_t *mac) FAST_FUNC;
+struct dyn_lease *find_lease_by_nip(uint32_t nip) FAST_FUNC;
+uint32_t find_free_or_expired_nip(const uint8_t *safe_mac) FAST_FUNC;
+
+
+/* Config file parser will pass static lease info to this function
+ * which will add it to a data structure that can be searched later */
+void add_static_lease(struct static_lease **st_lease_pp, uint8_t *mac, uint32_t nip) FAST_FUNC;
+/* Find static lease IP by mac */
+uint32_t get_static_nip_by_mac(struct static_lease *st_lease, void *arg) FAST_FUNC;
+/* Check to see if an IP is reserved as a static IP */
+int is_nip_reserved(struct static_lease *st_lease, uint32_t nip) FAST_FUNC;
+/* Print out static leases just to check what's going on (debug code) */
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+void log_static_leases(struct static_lease **st_lease_pp) FAST_FUNC;
+#else
+# define log_static_leases(st_lease_pp) ((void)0)
+#endif
+
+
+void read_config(const char *file) FAST_FUNC;
+void write_leases(void) FAST_FUNC;
+void read_leases(const char *file) FAST_FUNC;
+
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/dhcprelay.c b/ap/app/busybox/src/networking/udhcp/dhcprelay.c
new file mode 100644
index 0000000..f82ac05
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dhcprelay.c
@@ -0,0 +1,373 @@
+/* vi: set sw=4 ts=4: */
+/* Port to Busybox Copyright (C) 2006 Jesse Dutton <jessedutton@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ *
+ * 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
+ */
+
+//usage:#define dhcprelay_trivial_usage
+//usage: "CLIENT_IFACE[,CLIENT_IFACE2]... SERVER_IFACE [SERVER_IP]"
+//usage:#define dhcprelay_full_usage "\n\n"
+//usage: "Relay DHCP requests between clients and server"
+
+#include "common.h"
+
+#define SERVER_PORT 67
+
+/* lifetime of an xid entry in sec. */
+#define MAX_LIFETIME 2*60
+/* select timeout in sec. */
+#define SELECT_TIMEOUT (MAX_LIFETIME / 8)
+
+/* 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;
+} FIX_ALIASING;
+
+#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) {
+ break;
+ }
+ item = item->next;
+ }
+ return item;
+}
+
+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 dhcp_packet *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 = udhcp_get_option(p, DHCP_MESSAGE_TYPE);
+ if (op != NULL)
+ return op[0];
+ return -1;
+}
+
+/**
+ * make_iface_list - parses client/server interface names
+ * returns array
+ */
+static char **make_iface_list(char **client_and_server_ifaces, int *client_number)
+{
+ char *s, **iface_list;
+ int i, cn;
+
+ /* get number of items */
+ cn = 2; /* 1 server iface + at least 1 client one */
+ s = client_and_server_ifaces[0]; /* list of client ifaces */
+ while (*s) {
+ if (*s == ',')
+ cn++;
+ s++;
+ }
+ *client_number = cn;
+
+ /* create vector of pointers */
+ iface_list = xzalloc(cn * sizeof(iface_list[0]));
+
+ iface_list[0] = client_and_server_ifaces[1]; /* server iface */
+
+ i = 1;
+ s = xstrdup(client_and_server_ifaces[0]); /* list of client ifaces */
+ goto store_client_iface_name;
+
+ while (i < cn) {
+ if (*s++ == ',') {
+ s[-1] = '\0';
+ store_client_iface_name:
+ iface_list[i++] = s;
+ }
+ }
+
+ return iface_list;
+}
+
+/* Creates listen sockets (in fds) bound to client and server ifaces,
+ * and returns numerically max fd.
+ */
+static int init_sockets(char **iface_list, int num_clients, int *fds)
+{
+ int i, n;
+
+ n = 0;
+ for (i = 0; i < num_clients; i++) {
+ fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, iface_list[i]);
+ if (n < fds[i])
+ n = fds[i];
+ }
+ return n;
+}
+
+static int sendto_ip4(int sock, const void *msg, int msg_len, struct sockaddr_in *to)
+{
+ int err;
+
+ errno = 0;
+ err = sendto(sock, msg, msg_len, 0, (struct sockaddr*) to, sizeof(*to));
+ err -= msg_len;
+ if (err)
+ bb_perror_msg("sendto");
+ return err;
+}
+
+/**
+ * pass_to_server() - forwards dhcp packets from client to server
+ * p - packet to send
+ * client - number of the client
+ */
+static void pass_to_server(struct dhcp_packet *p, int packet_len, int client, int *fds,
+ struct sockaddr_in *client_addr, struct sockaddr_in *server_addr)
+{
+ int type;
+
+ /* 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 */
+ xid_add(p->xid, client_addr, client);
+
+ /* forward request to server */
+ /* note that we send from fds[0] which is bound to SERVER_PORT (67).
+ * IOW: we send _from_ SERVER_PORT! Although this may look strange,
+ * RFC 1542 not only allows, but prescribes this for BOOTP relays.
+ */
+ sendto_ip4(fds[0], p, packet_len, server_addr);
+}
+
+/**
+ * pass_to_client() - forwards dhcp packets from server to client
+ * p - packet to send
+ */
+static void pass_to_client(struct dhcp_packet *p, int packet_len, int *fds)
+{
+ int 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;
+ }
+
+//TODO: also do it if (p->flags & htons(BROADCAST_FLAG)) is set!
+ if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY))
+ item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+
+ if (sendto_ip4(fds[item->client], p, packet_len, &item->ip) != 0) {
+ return; /* send error occurred */
+ }
+
+ /* remove xid entry */
+ xid_del(p->xid);
+}
+
+int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dhcprelay_main(int argc, char **argv)
+{
+ struct sockaddr_in server_addr;
+ char **iface_list;
+ int *fds;
+ int num_sockets, max_socket;
+ uint32_t our_nip;
+
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+ server_addr.sin_port = htons(SERVER_PORT);
+
+ /* dhcprelay CLIENT_IFACE1[,CLIENT_IFACE2...] SERVER_IFACE [SERVER_IP] */
+ if (argc == 4) {
+ if (!inet_aton(argv[3], &server_addr.sin_addr))
+ bb_perror_msg_and_die("bad server IP");
+ } else if (argc != 3) {
+ bb_show_usage();
+ }
+
+ iface_list = make_iface_list(argv + 1, &num_sockets);
+
+ fds = xmalloc(num_sockets * sizeof(fds[0]));
+
+ /* Create sockets and bind one to every iface */
+ max_socket = init_sockets(iface_list, num_sockets, fds);
+
+ /* Get our IP on server_iface */
+ if (udhcp_read_interface(argv[2], NULL, &our_nip, NULL))
+ return 1;
+
+ /* Main loop */
+ while (1) {
+// reinit stuff from time to time? go back to make_iface_list
+// every N minutes?
+ fd_set rfds;
+ struct timeval tv;
+ int i;
+
+ 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) {
+ int packlen;
+ struct dhcp_packet dhcp_msg;
+
+ /* server */
+ if (FD_ISSET(fds[0], &rfds)) {
+ packlen = udhcp_recv_kernel_packet(&dhcp_msg, fds[0]);
+ if (packlen > 0) {
+ pass_to_client(&dhcp_msg, packlen, fds);
+ }
+ }
+
+ /* clients */
+ for (i = 1; i < num_sockets; i++) {
+ struct sockaddr_in client_addr;
+ socklen_t addr_size;
+
+ if (!FD_ISSET(fds[i], &rfds))
+ continue;
+
+ addr_size = sizeof(client_addr);
+ packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0,
+ (struct sockaddr *)(&client_addr), &addr_size);
+ if (packlen <= 0)
+ continue;
+
+ /* Get our IP on corresponding client_iface */
+// RFC 1542
+// 4.1 General BOOTP Processing for Relay Agents
+// 4.1.1 BOOTREQUEST Messages
+// If the relay agent does decide to relay the request, it MUST examine
+// the 'giaddr' ("gateway" IP address) field. If this field is zero,
+// the relay agent MUST fill this field with the IP address of the
+// interface on which the request was received. If the interface has
+// more than one IP address logically associated with it, the relay
+// agent SHOULD choose one IP address associated with that interface and
+// use it consistently for all BOOTP messages it relays. If the
+// 'giaddr' field contains some non-zero value, the 'giaddr' field MUST
+// NOT be modified. The relay agent MUST NOT, under any circumstances,
+// fill the 'giaddr' field with a broadcast address as is suggested in
+// [1] (Section 8, sixth paragraph).
+
+// but why? what if server can't route such IP? Client ifaces may be, say, NATed!
+
+// 4.1.2 BOOTREPLY Messages
+// BOOTP relay agents relay BOOTREPLY messages only to BOOTP clients.
+// It is the responsibility of BOOTP servers to send BOOTREPLY messages
+// directly to the relay agent identified in the 'giaddr' field.
+// (yeah right, unless it is impossible... see comment above)
+// Therefore, a relay agent may assume that all BOOTREPLY messages it
+// receives are intended for BOOTP clients on its directly-connected
+// networks.
+//
+// When a relay agent receives a BOOTREPLY message, it should examine
+// the BOOTP 'giaddr', 'yiaddr', 'chaddr', 'htype', and 'hlen' fields.
+// These fields should provide adequate information for the relay agent
+// to deliver the BOOTREPLY message to the client.
+//
+// The 'giaddr' field can be used to identify the logical interface from
+// which the reply must be sent (i.e., the host or router interface
+// connected to the same network as the BOOTP client). If the content
+// of the 'giaddr' field does not match one of the relay agent's
+// directly-connected logical interfaces, the BOOTREPLY messsage MUST be
+// silently discarded.
+ if (udhcp_read_interface(iface_list[i], NULL, &dhcp_msg.gateway_nip, NULL)) {
+ /* Fall back to our IP on server iface */
+// this makes more sense!
+ dhcp_msg.gateway_nip = our_nip;
+ }
+// maybe dhcp_msg.hops++? drop packets with too many hops (RFC 1542 says 4 or 16)?
+ pass_to_server(&dhcp_msg, packlen, i, fds, &client_addr, &server_addr);
+ }
+ }
+ xid_expire();
+ } /* while (1) */
+
+ /* return 0; - not reached */
+}
diff --git a/ap/app/busybox/src/networking/udhcp/domain_codec.c b/ap/app/busybox/src/networking/udhcp/domain_codec.c
new file mode 100755
index 0000000..ce7d203
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/domain_codec.c
@@ -0,0 +1,253 @@
+/* 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 source tree.
+ */
+#ifdef DNS_COMPR_TESTING
+# define FAST_FUNC /* nothing */
+# define xmalloc malloc
+# include <stdlib.h>
+# include <stdint.h>
+# include <string.h>
+# include <stdio.h>
+#else
+# include "common.h"
+#endif
+
+#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)
+{
+ char *ret = ret; /* for compiler */
+ char *dst = NULL;
+
+ /* 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 (1) {
+ /* note: "return NULL" below are leak-safe since
+ * dst isn't yet allocated */
+ const uint8_t *c;
+ unsigned crtpos, retpos, depth, len;
+
+ crtpos = retpos = depth = len = 0;
+ while (crtpos < clen) {
+ c = cstr + crtpos;
+
+ if ((*c & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
+ /* 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[0] & 0x3f) << 8) | c[1]; /* jump */
+ } else if (*c) {
+ /* label */
+ if (crtpos + *c + 1 > clen) /* label too long? abort */
+ return NULL;
+ if (dst)
+ /* \3com ---> "com." */
+ ((char*)mempcpy(dst + len, c + 1, *c))[0] = '.';//hub CVE-2016-2147
+ len += *c + 1;
+ crtpos += *c + 1;
+ } else {
+ /* NUL: end of current domain name */
+ if (retpos == 0) {
+ /* toplevel? keep going */
+ crtpos++;
+ } else {
+ /* return to toplevel saved spot */
+ crtpos = retpos;
+ retpos = depth = 0;
+ }
+ //hub CVE-2016-2147
+ if (dst && len != 0)
+ /* \4host\3com\0\4host and we are at \0:
+ * \3com was converted to "com.", change dot to space.
+ */
+ dst[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) { /* first pass? */
+ /* allocate dst buffer and copy pre */
+ unsigned plen = strlen(pre);
+ ret = dst = xmalloc(plen + len);
+ memcpy(dst, pre, plen);
+ dst += plen;
+ } else {
+ dst[len - 1] = '\0';
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/* 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, *lenptr, *dst;
+ int len;
+
+ res = xmalloc(strlen(src) + 2);
+ dst = lenptr = res;
+ dst++;
+
+ for (;;) {
+ c = (uint8_t)*src++;
+ if (c == '.' || c == '\0') { /* end of label */
+ len = dst - lenptr - 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;
+ }
+ *lenptr = len;
+ if (c == '\0' || *src == '\0') /* "" or ".": end of src */
+ break;
+ lenptr = dst++;
+ continue;
+ }
+ if (c >= 'A' && c <= 'Z') /* uppercase? convert to lower */
+ c += ('a' - 'A');
+ *dst++ = c;
+ }
+
+ if (dst - res >= NS_MAXCDNAME) { /* dname too long? abort */
+ free(res);
+ return NULL;
+ }
+
+ *dst = 0;
+ 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;
+
+ /* find all labels in cstr */
+ off = 0;
+ while (off < clen) {
+ c = cstr + off;
+
+ if ((*c & NS_CMPRSFLGS) == NS_CMPRSFLGS) { /* pointer, skip */
+ off += 2;
+ continue;
+ }
+ if (*c) { /* label, try matching dname */
+ d = dname;
+ while (1) {
+ unsigned len1 = *c + 1;
+ if (memcmp(c, d, len1) != 0)
+ break;
+ if (len1 == 1) /* at terminating NUL - match, return offset */
+ return off;
+ d += len1;
+ c += len1;
+ if ((*c & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* pointer, jump */
+ c = cstr + (((c[0] & 0x3f) << 8) | c[1]);
+ }
+ off += cstr[off] + 1;
+ continue;
+ }
+ /* NUL, 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 <4>host<3>com<0> 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;
+ }
+
+ d = dname;
+ while (*d) {
+ if (cstr) {
+ off = find_offset(cstr, clen, d);
+ if (off >= 0) { /* found a match, add pointer and return */
+ *d++ = NS_CMPRSFLGS | (off >> 8);
+ *d = off;
+ break;
+ }
+ }
+ d += *d + 1;
+ }
+
+ *retlen = d - dname + 1;
+ return dname;
+}
+
+#ifdef DNS_COMPR_TESTING
+/* gcc -Wall -DDNS_COMPR_TESTING domain_codec.c -o domain_codec && ./domain_codec */
+int main(int argc, char **argv)
+{
+ int len;
+ uint8_t *encoded;
+ //hub CVE-2016-2147
+ uint8_t str[6] = { 0x00, 0x00, 0x02, 0x65, 0x65, 0x00 };
+ printf("NUL:'%s'\n", dname_dec(str, 6, ""));
+
+#define DNAME_DEC(encoded,pre) dname_dec((uint8_t*)(encoded), sizeof(encoded), (pre))
+ printf("'%s'\n", DNAME_DEC("\4host\3com\0", "test1:"));
+ printf("test2:'%s'\n", DNAME_DEC("\4host\3com\0\4host\3com\0", ""));
+ printf("test3:'%s'\n", DNAME_DEC("\4host\3com\0\xC0\0", ""));
+ printf("test4:'%s'\n", DNAME_DEC("\4host\3com\0\xC0\5", ""));
+ printf("test5:'%s'\n", DNAME_DEC("\4host\3com\0\xC0\5\1z\xC0\xA", ""));
+
+#define DNAME_ENC(cache,source,lenp) dname_enc((uint8_t*)(cache), sizeof(cache), (source), (lenp))
+ encoded = dname_enc(NULL, 0, "test.net", &len);
+ printf("test6:'%s' len:%d\n", dname_dec(encoded, len, ""), len);
+ encoded = DNAME_ENC("\3net\0", "test.net", &len);
+ printf("test7:'%s' len:%d\n", dname_dec(encoded, len, ""), len);
+ encoded = DNAME_ENC("\4test\3net\0", "test.net", &len);
+ printf("test8:'%s' len:%d\n", dname_dec(encoded, len, ""), len);
+ return 0;
+}
+#endif
diff --git a/ap/app/busybox/src/networking/udhcp/dumpleases.c b/ap/app/busybox/src/networking/udhcp/dumpleases.c
new file mode 100644
index 0000000..64cd73e
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/dumpleases.c
@@ -0,0 +1,107 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//usage:#define dumpleases_trivial_usage
+//usage: "[-r|-a] [-f LEASEFILE]"
+//usage:#define dumpleases_full_usage "\n\n"
+//usage: "Display DHCP leases granted by udhcpd\n"
+//usage: IF_LONG_OPTS(
+//usage: "\n -f,--file=FILE Lease file"
+//usage: "\n -r,--remaining Show remaining time"
+//usage: "\n -a,--absolute Show expiration time"
+//usage: )
+//usage: IF_NOT_LONG_OPTS(
+//usage: "\n -f FILE Lease file"
+//usage: "\n -r Show remaining time"
+//usage: "\n -a Show expiration time"
+//usage: )
+
+#include "common.h"
+#include "dhcpd.h"
+#include "unicode.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;
+ int64_t written_at, curr, expires_abs;
+ const char *file = LEASES_FILE;
+ struct dyn_lease lease;
+ struct in_addr addr;
+
+ enum {
+ OPT_a = 0x1, // -a
+ OPT_r = 0x2, // -r
+ OPT_f = 0x4, // -f
+ };
+#if ENABLE_LONG_OPTS
+ 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
+ init_unicode();
+
+ opt_complementary = "=0:a--r:r--a";
+ opt = getopt32(argv, "arf:", &file);
+
+ fd = xopen(file, O_RDONLY);
+
+ printf("Mac Address IP Address Host Name Expires %s\n", (opt & OPT_a) ? "at" : "in");
+ /* "00:00:00:00:00:00 255.255.255.255 ABCDEFGHIJKLMNOPQRS Wed Jun 30 21:49:08 1993" */
+ /* "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 */
+
+ xread(fd, &written_at, sizeof(written_at));
+ written_at = SWAP_BE64(written_at);
+ curr = time(NULL);
+ if (curr < written_at)
+ written_at = curr; /* lease file from future! :) */
+
+ while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+ const char *fmt = ":%02x" + 1;
+ for (i = 0; i < 6; i++) {
+ printf(fmt, lease.lease_mac[i]);
+ fmt = ":%02x";
+ }
+ addr.s_addr = lease.lease_nip;
+#if ENABLE_UNICODE_SUPPORT
+ {
+ char *uni_name = unicode_conv_to_printable_fixedwidth(/*NULL,*/ lease.hostname, 19);
+ printf(" %-16s%s ", inet_ntoa(addr), uni_name);
+ free(uni_name);
+ }
+#else
+ /* actually, 15+1 and 19+1, +1 is a space between columns */
+ /* lease.hostname is char[20] and is always NUL terminated */
+ printf(" %-16s%-20s", inet_ntoa(addr), lease.hostname);
+#endif
+ expires_abs = ntohl(lease.expires) + written_at;
+ if (expires_abs <= curr) {
+ puts("expired");
+ continue;
+ }
+ if (!(opt & OPT_a)) { /* no -a */
+ unsigned d, h, m;
+ unsigned expires = expires_abs - curr;
+ 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 */
+ time_t t = expires_abs;
+ fputs(ctime(&t), stdout);
+ }
+ }
+ /* close(fd); */
+
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/files.c b/ap/app/busybox/src/networking/udhcp/files.c
new file mode 100644
index 0000000..6840f3c
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/files.c
@@ -0,0 +1,216 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * DHCP server config and lease file manipulation
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include <netinet/ether.h>
+
+#include "common.h"
+#include "dhcpd.h"
+
+/* on these functions, make sure your datatype matches */
+static int FAST_FUNC read_str(const char *line, void *arg)
+{
+ char **dest = arg;
+
+ free(*dest);
+ *dest = xstrdup(line);
+ return 1;
+}
+
+static int FAST_FUNC read_u32(const char *line, void *arg)
+{
+ *(uint32_t*)arg = bb_strtou32(line, NULL, 10);
+ return errno == 0;
+}
+
+static int FAST_FUNC read_staticlease(const char *const_line, void *arg)
+{
+ char *line;
+ char *mac_string;
+ char *ip_string;
+ struct ether_addr mac_bytes; /* it's "struct { uint8_t mac[6]; }" */
+ uint32_t nip;
+
+ /* Read mac */
+ line = (char *) const_line;
+ mac_string = strtok_r(line, " \t", &line);
+ if (!mac_string || !ether_aton_r(mac_string, &mac_bytes))
+ return 0;
+
+ /* Read ip */
+ ip_string = strtok_r(NULL, " \t", &line);
+ if (!ip_string || !udhcp_str2nip(ip_string, &nip))
+ return 0;
+
+ add_static_lease(arg, (uint8_t*) &mac_bytes, nip);
+
+ log_static_leases(arg);
+
+ return 1;
+}
+
+
+struct config_keyword {
+ const char *keyword;
+ int (*handler)(const char *line, void *var) FAST_FUNC;
+ void *var;
+ const char *def;
+};
+
+static const struct config_keyword keywords[] = {
+ /* keyword handler variable address default */
+ {"start" , udhcp_str2nip , &server_config.start_ip , "192.168.0.20"},
+ {"end" , udhcp_str2nip , &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"},
+ {"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_sec, "60"},
+ {"lease_file" , read_str , &server_config.lease_file , LEASES_FILE},
+ {"pidfile" , read_str , &server_config.pidfile , "/var/run/udhcpd.pid"},
+ {"siaddr" , udhcp_str2nip , &server_config.siaddr_nip , "0.0.0.0"},
+ /* keywords with no defaults must be last! */
+ {"option" , udhcp_str2optset, &server_config.options , ""},
+ {"opt" , udhcp_str2optset, &server_config.options , ""},
+ {"notify_file" , read_str , &server_config.notify_file , NULL},
+ {"sname" , read_str , &server_config.sname , NULL},
+ {"boot_file" , read_str , &server_config.boot_file , NULL},
+ {"static_lease" , read_staticlease, &server_config.static_leases, ""},
+};
+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) == 0) {
+ 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 fd;
+ unsigned i;
+ leasetime_t curr;
+ int64_t written_at;
+
+ fd = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC);
+ if (fd < 0)
+ return;
+
+ curr = written_at = time(NULL);
+
+ written_at = SWAP_BE64(written_at);
+ full_write(fd, &written_at, sizeof(written_at));
+
+ for (i = 0; i < server_config.max_leases; i++) {
+ leasetime_t tmp_time;
+
+ if (g_leases[i].lease_nip == 0)
+ continue;
+
+ /* Screw with the time in the struct, for easier writing */
+ tmp_time = g_leases[i].expires;
+
+ g_leases[i].expires -= curr;
+ if ((signed_leasetime_t) g_leases[i].expires < 0)
+ g_leases[i].expires = 0;
+ g_leases[i].expires = htonl(g_leases[i].expires);
+
+ /* No error check. If the file gets truncated,
+ * we lose some leases on restart. Oh well. */
+ full_write(fd, &g_leases[i], sizeof(g_leases[i]));
+
+ /* Then restore it when done */
+ g_leases[i].expires = tmp_time;
+ }
+ close(fd);
+
+ if (server_config.notify_file) {
+ char *argv[3];
+ argv[0] = server_config.notify_file;
+ argv[1] = server_config.lease_file;
+ argv[2] = NULL;
+ spawn_and_wait(argv);
+ }
+}
+
+void FAST_FUNC read_leases(const char *file)
+{
+ struct dyn_lease lease;
+ int64_t written_at, time_passed;
+ int fd;
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+ unsigned i = 0;
+#endif
+
+ fd = open_or_warn(file, O_RDONLY);
+ if (fd < 0)
+ return;
+
+ if (full_read(fd, &written_at, sizeof(written_at)) != sizeof(written_at))
+ goto ret;
+ written_at = SWAP_BE64(written_at);
+
+ time_passed = time(NULL) - written_at;
+ /* Strange written_at, or lease file from old version of udhcpd
+ * which had no "written_at" field? */
+ if ((uint64_t)time_passed > 12 * 60 * 60)
+ goto ret;
+
+ while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+//FIXME: what if it matches some static lease?
+ uint32_t y = ntohl(lease.lease_nip);
+ if (y >= server_config.start_ip && y <= server_config.end_ip) {
+ signed_leasetime_t expires = ntohl(lease.expires) - (signed_leasetime_t)time_passed;
+ if (expires <= 0)
+ continue;
+ /* NB: add_lease takes "relative time", IOW,
+ * lease duration, not lease deadline. */
+ if (add_lease(lease.lease_mac, lease.lease_nip,
+ expires,
+ lease.hostname, sizeof(lease.hostname)
+ ) == 0
+ ) {
+ bb_error_msg("too many leases while loading %s", file);
+ break;
+ }
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
+ i++;
+#endif
+ }
+ }
+ log1("Read %d leases", i);
+ ret:
+ close(fd);
+}
diff --git a/ap/app/busybox/src/networking/udhcp/leases.c b/ap/app/busybox/src/networking/udhcp/leases.c
new file mode 100755
index 0000000..bf0db7f
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/leases.c
@@ -0,0 +1,215 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "common.h"
+#include "dhcpd.h"
+
+/* Find the oldest expired lease, NULL if there are no expired leases */
+static struct dyn_lease *oldest_expired_lease(void)
+{
+ struct dyn_lease *oldest_lease = NULL;
+ leasetime_t oldest_time = time(NULL);
+ unsigned i;
+
+ /* Unexpired leases have g_leases[i].expires >= current time
+ * and therefore can't ever match */
+ for (i = 0; i < server_config.max_leases; i++) {
+ if (g_leases[i].expires < oldest_time) {
+ oldest_time = g_leases[i].expires;
+ oldest_lease = &g_leases[i];
+ }
+ }
+ return oldest_lease;
+}
+
+/* Clear out all leases with matching nonzero chaddr OR yiaddr.
+ * If chaddr == NULL, this is a conflict lease.
+ */
+static void clear_leases(const uint8_t *chaddr, uint32_t yiaddr)
+{
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++) {
+ if ((chaddr && memcmp(g_leases[i].lease_mac, chaddr, 6) == 0)
+ || (yiaddr && g_leases[i].lease_nip == yiaddr)
+ ) {
+ memset(&g_leases[i], 0, sizeof(g_leases[i]));
+ }
+ }
+}
+
+/* Add a lease into the table, clearing out any old ones.
+ * If chaddr == NULL, this is a conflict lease.
+ */
+struct dyn_lease* FAST_FUNC add_lease(
+ const uint8_t *chaddr, uint32_t yiaddr,
+ leasetime_t leasetime,
+ const char *hostname, int hostname_len)
+{
+ struct dyn_lease *oldest;
+
+ /* clean out any old ones */
+ clear_leases(chaddr, yiaddr);
+
+ oldest = oldest_expired_lease();
+
+ if (oldest) {
+ memset(oldest, 0, sizeof(*oldest));
+ if (hostname) {
+ char *p;
+
+ hostname_len++; /* include NUL */
+ if (hostname_len > sizeof(oldest->hostname))
+ hostname_len = sizeof(oldest->hostname);
+ p = safe_strncpy(oldest->hostname, hostname, hostname_len);
+ /* sanitization (s/non-ASCII/^/g) */
+ while (*p) {
+ if (*p < ' ' || *p > 126)
+ *p = '^';
+ p++;
+ }
+ }
+ if (chaddr)
+ memcpy(oldest->lease_mac, chaddr, 6);
+ oldest->lease_nip = yiaddr;
+ oldest->expires = time(NULL) + leasetime;
+ }
+
+ return oldest;
+}
+
+/* True if a lease has expired */
+int FAST_FUNC is_expired_lease(struct dyn_lease *lease)
+{
+ return (lease->expires < (leasetime_t) time(NULL));
+}
+
+/* Find the first lease that matches MAC, NULL if no match */
+struct dyn_lease* FAST_FUNC find_lease_by_mac(const uint8_t *mac)
+{
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if (memcmp(g_leases[i].lease_mac, mac, 6) == 0)
+ return &g_leases[i];
+
+ return NULL;
+}
+
+/* Find the first lease that matches IP, NULL is no match */
+struct dyn_lease* FAST_FUNC find_lease_by_nip(uint32_t nip)
+{
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if (g_leases[i].lease_nip == nip)
+ return &g_leases[i];
+
+ return NULL;
+}
+
+/* Check if the IP is taken; if it is, add it to the lease table */
+static int nobody_responds_to_arp(uint32_t nip, const uint8_t *safe_mac)
+{
+ struct in_addr temp;
+ int r;
+
+ r = arpping(nip, safe_mac,
+ server_config.server_nip,
+ server_config.server_mac,
+ server_config.interface);
+ if (r)
+ return r;
+
+ temp.s_addr = nip;
+ bb_info_msg("%s belongs to someone, reserving it for %u seconds",
+ inet_ntoa(temp), (unsigned)server_config.conflict_time);
+ add_lease(NULL, nip, server_config.conflict_time, NULL, 0);
+ return 0;
+}
+extern int base_ip_on_mac;
+
+/* Find a new usable (we think) address */
+uint32_t FAST_FUNC find_free_or_expired_nip(const uint8_t *safe_mac)
+{
+ uint32_t addr;
+ struct dyn_lease *oldest_lease = NULL;
+ uint32_t stop;
+
+//#if ENABLE_FEATURE_UDHCPD_BASE_IP_ON_MAC
+ if(base_ip_on_mac == 1)
+ {
+
+ unsigned i, hash;
+
+ /* hash hwaddr: use the SDBM hashing algorithm. Seems to give good
+ * dispersal even with similarly-valued "strings".
+ */
+ hash = 0;
+ for (i = 0; i < 6; i++)
+ hash += safe_mac[i] + (hash << 6) + (hash << 16) - hash;
+
+ /* pick a seed based on hwaddr then iterate until we find a free address. */
+ addr = server_config.start_ip
+ + (hash % (1 + server_config.end_ip - server_config.start_ip));
+ stop = addr;
+ }
+ else
+ {
+ //#else
+ addr = server_config.start_ip;
+ stop = server_config.end_ip + 1;
+ //#define stop (server_config.end_ip + 1)
+ //#endif
+ }
+ do {
+ uint32_t nip;
+ struct dyn_lease *lease;
+
+ /* ie, 192.168.55.0 */
+ //if ((addr & 0xff) == 0)
+ // goto next_addr;
+ /* ie, 192.168.55.255 */
+ //if ((addr & 0xff) == 0xff)
+ // goto next_addr;
+ nip = htonl(addr);
+ /* skip our own address */
+ if (nip == server_config.server_nip)
+ goto next_addr;
+ /* is this a static lease addr? */
+ if (is_nip_reserved(server_config.static_leases, nip))
+ goto next_addr;
+
+ lease = find_lease_by_nip(nip);
+ if (!lease) {
+//TODO: DHCP servers do not always sit on the same subnet as clients: should *ping*, not arp-ping!
+ if (server_config.start_ip == server_config.end_ip || nobody_responds_to_arp(nip, safe_mac))
+ return nip;
+ } else {
+ if (!oldest_lease || lease->expires < oldest_lease->expires)
+ oldest_lease = lease;
+ }
+
+ next_addr:
+ addr++;
+//#if ENABLE_FEATURE_UDHCPD_BASE_IP_ON_MAC
+ if(base_ip_on_mac == 1)
+ {
+ if (addr > server_config.end_ip)
+ addr = server_config.start_ip;
+ }
+//#endif
+ } while (addr != stop);
+
+ if (oldest_lease
+ && is_expired_lease(oldest_lease)
+ && nobody_responds_to_arp(oldest_lease->lease_nip, safe_mac)
+ ) {
+ return oldest_lease->lease_nip;
+ }
+
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/packet.c b/ap/app/busybox/src/networking/udhcp/packet.c
new file mode 100644
index 0000000..33c9585
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/packet.c
@@ -0,0 +1,227 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Packet ops
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "common.h"
+#include "dhcpd.h"
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netpacket/packet.h>
+
+void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type)
+{
+ memset(packet, 0, sizeof(*packet));
+ 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 = 1; /* ethernet */
+ packet->hlen = 6;
+ packet->cookie = htonl(DHCP_MAGIC);
+ if (DHCP_END != 0)
+ packet->options[0] = DHCP_END;
+ udhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type);
+}
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+void FAST_FUNC udhcp_dump_packet(struct dhcp_packet *packet)
+{
+ char buf[sizeof(packet->chaddr)*2 + 1];
+
+ if (dhcp_verbose < 2)
+ return;
+
+ bb_info_msg(
+ //" op %x"
+ //" htype %x"
+ " hlen %x"
+ //" hops %x"
+ " xid %x"
+ //" secs %x"
+ //" flags %x"
+ " ciaddr %x"
+ " yiaddr %x"
+ " siaddr %x"
+ " giaddr %x"
+ //" chaddr %s"
+ //" sname %s"
+ //" file %s"
+ //" cookie %x"
+ //" options %s"
+ //, packet->op
+ //, packet->htype
+ , packet->hlen
+ //, packet->hops
+ , packet->xid
+ //, packet->secs
+ //, packet->flags
+ , packet->ciaddr
+ , packet->yiaddr
+ , packet->siaddr_nip
+ , packet->gateway_nip
+ //, packet->chaddr[16]
+ //, packet->sname[64]
+ //, packet->file[128]
+ //, packet->cookie
+ //, packet->options[]
+ );
+ *bin2hex(buf, (void *) packet->chaddr, sizeof(packet->chaddr)) = '\0';
+ bb_info_msg(" chaddr %s", buf);
+}
+#endif
+
+/* Read a packet from socket fd, return -1 on read error, -2 on packet error */
+int FAST_FUNC udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd)
+{
+ int bytes;
+
+ memset(packet, 0, sizeof(*packet));
+ bytes = safe_read(fd, packet, sizeof(*packet));
+ if (bytes < 0) {
+ log1("Packet read error, ignoring");
+ return bytes; /* returns -1 */
+ }
+
+ if (bytes < offsetof(struct dhcp_packet, options)
+ || packet->cookie != htonl(DHCP_MAGIC)
+ ) {
+ bb_info_msg("Packet with bad magic, ignoring");
+ return -2;
+ }
+ log1("Received a packet");
+ udhcp_dump_packet(packet);
+
+ return bytes;
+}
+
+/* Construct a ip/udp header for a packet, send packet */
+int FAST_FUNC udhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+ uint32_t source_nip, int source_port,
+ uint32_t dest_nip, int dest_port, const uint8_t *dest_arp,
+ int ifindex)
+{
+ struct sockaddr_ll dest_sll;
+ struct ip_udp_dhcp_packet packet;
+ unsigned padding;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+
+ memset(&dest_sll, 0, sizeof(dest_sll));
+ memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data));
+ packet.data = *dhcp_pkt; /* struct copy */
+
+ dest_sll.sll_family = AF_PACKET;
+ dest_sll.sll_protocol = htons(ETH_P_IP);
+ dest_sll.sll_ifindex = ifindex;
+ dest_sll.sll_halen = 6;
+ memcpy(dest_sll.sll_addr, dest_arp, 6);
+
+ if (bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)) < 0) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ /* We were sending full-sized DHCP packets (zero padded),
+ * but some badly configured servers were seen dropping them.
+ * Apparently they drop all DHCP packets >576 *ethernet* octets big,
+ * whereas they may only drop packets >576 *IP* octets big
+ * (which for typical Ethernet II means 590 octets: 6+6+2 + 576).
+ *
+ * In order to work with those buggy servers,
+ * we truncate packets after end option byte.
+ */
+ padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(packet.data.options);
+
+ packet.ip.protocol = IPPROTO_UDP;
+ packet.ip.saddr = source_nip;
+ packet.ip.daddr = dest_nip;
+ packet.udp.source = htons(source_port);
+ packet.udp.dest = htons(dest_port);
+ /* size, excluding IP header: */
+ packet.udp.len = htons(UDP_DHCP_SIZE - padding);
+ /* for UDP checksumming, ip.len is set to UDP packet len */
+ packet.ip.tot_len = packet.udp.len;
+ packet.udp.check = inet_cksum((uint16_t *)&packet,
+ IP_UDP_DHCP_SIZE - padding);
+ /* but for sending, it is set to IP packet len */
+ packet.ip.tot_len = htons(IP_UDP_DHCP_SIZE - padding);
+ packet.ip.ihl = sizeof(packet.ip) >> 2;
+ packet.ip.version = IPVERSION;
+ packet.ip.ttl = IPDEFTTL;
+ packet.ip.check = inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip));
+
+ udhcp_dump_packet(dhcp_pkt);
+ result = sendto(fd, &packet, IP_UDP_DHCP_SIZE - padding, /*flags:*/ 0,
+ (struct sockaddr *) &dest_sll, sizeof(dest_sll));
+ 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 dhcp_packet *dhcp_pkt,
+ uint32_t source_nip, int source_port,
+ uint32_t dest_nip, int dest_port)
+{
+ struct sockaddr_in sa;
+ unsigned padding;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+ setsockopt_reuseaddr(fd);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(source_port);
+ sa.sin_addr.s_addr = source_nip;
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(dest_port);
+ sa.sin_addr.s_addr = dest_nip;
+ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ msg = "connect";
+ goto ret_close;
+ }
+
+ udhcp_dump_packet(dhcp_pkt);
+ padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(dhcp_pkt->options);
+ result = safe_write(fd, dhcp_pkt, DHCP_SIZE - padding);
+ msg = "write";
+ ret_close:
+ close(fd);
+ if (result < 0) {
+ ret_msg:
+ bb_perror_msg(msg, "UDP");
+ }
+ return result;
+}
diff --git a/ap/app/busybox/src/networking/udhcp/signalpipe.c b/ap/app/busybox/src/networking/udhcp/signalpipe.c
new file mode 100644
index 0000000..6355c5e
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/signalpipe.c
@@ -0,0 +1,77 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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"
+
+/* Global variable: we access it from signal handler */
+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("can't 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/ap/app/busybox/src/networking/udhcp/socket.c b/ap/app/busybox/src/networking/udhcp/socket.c
new file mode 100644
index 0000000..a421069
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/socket.c
@@ -0,0 +1,110 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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 "common.h"
+#include <net/if.h>
+
+int FAST_FUNC udhcp_read_interface(const char *interface, int *ifindex, uint32_t *nip, uint8_t *mac)
+{
+ /* char buffer instead of bona-fide struct avoids aliasing warning */
+ char ifr_buf[sizeof(struct ifreq)];
+ struct ifreq *const ifr = (void *)ifr_buf;
+
+ int fd;
+ 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_IFNAMSIZ(ifr->ifr_name, interface);
+ if (nip) {
+ 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;
+ *nip = our_ip->sin_addr.s_addr;
+ log1("IP %s", inet_ntoa(our_ip->sin_addr));
+ }
+
+ if (ifindex) {
+ if (ioctl_or_warn(fd, SIOCGIFINDEX, ifr) != 0) {
+ close(fd);
+ return -1;
+ }
+ log1("Adapter index %d", ifr->ifr_ifindex);
+ *ifindex = ifr->ifr_ifindex;
+ }
+
+ if (mac) {
+ if (ioctl_or_warn(fd, SIOCGIFHWADDR, ifr) != 0) {
+ close(fd);
+ return -1;
+ }
+ memcpy(mac, ifr->ifr_hwaddr.sa_data, 6);
+ log1("MAC %02x:%02x:%02x:%02x:%02x:%02x",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[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;
+ char *colon;
+
+ log1("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");
+
+ /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */
+ colon = strrchr(inf, ':');
+ if (colon)
+ *colon = '\0';
+
+ if (setsockopt_bindtodevice(fd, inf))
+ xfunc_die(); /* warning is already printed */
+
+ if (colon)
+ *colon = ':';
+
+ 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/ap/app/busybox/src/networking/udhcp/static_leases.c b/ap/app/busybox/src/networking/udhcp/static_leases.c
new file mode 100644
index 0000000..f4a24ab
--- /dev/null
+++ b/ap/app/busybox/src/networking/udhcp/static_leases.c
@@ -0,0 +1,77 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Storing and retrieving data for static leases
+ *
+ * Wade Berrier <wberrier@myrealbox.com> September 2004
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#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,
+ * 4 byte IP address */
+void FAST_FUNC add_static_lease(struct static_lease **st_lease_pp,
+ uint8_t *mac,
+ uint32_t nip)
+{
+ struct static_lease *st_lease;
+
+ /* Find the tail of the list */
+ while ((st_lease = *st_lease_pp) != NULL) {
+ st_lease_pp = &st_lease->next;
+ }
+
+ /* Add new node */
+ *st_lease_pp = st_lease = xzalloc(sizeof(*st_lease));
+ memcpy(st_lease->mac, mac, 6);
+ st_lease->nip = nip;
+ /*st_lease->next = NULL;*/
+}
+
+/* Find static lease IP by mac */
+uint32_t FAST_FUNC get_static_nip_by_mac(struct static_lease *st_lease, void *mac)
+{
+ while (st_lease) {
+ if (memcmp(st_lease->mac, mac, 6) == 0)
+ return st_lease->nip;
+ st_lease = st_lease->next;
+ }
+
+ return 0;
+}
+
+/* Check to see if an IP is reserved as a static IP */
+int FAST_FUNC is_nip_reserved(struct static_lease *st_lease, uint32_t nip)
+{
+ while (st_lease) {
+ if (st_lease->nip == nip)
+ return 1;
+ st_lease = st_lease->next;
+ }
+
+ return 0;
+}
+
+#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
+/* 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 log_static_leases(struct static_lease **st_lease_pp)
+{
+ struct static_lease *cur;
+
+ if (dhcp_verbose < 2)
+ return;
+
+ cur = *st_lease_pp;
+ while (cur) {
+ bb_info_msg("static lease: mac:%02x:%02x:%02x:%02x:%02x:%02x nip:%x",
+ cur->mac[0], cur->mac[1], cur->mac[2],
+ cur->mac[3], cur->mac[4], cur->mac[5],
+ cur->nip
+ );
+ cur = cur->next;
+ }
+}
+#endif
diff --git a/ap/app/busybox/src/networking/vconfig.c b/ap/app/busybox/src/networking/vconfig.c
new file mode 100644
index 0000000..924b2f0
--- /dev/null
+++ b/ap/app/busybox/src/networking/vconfig.c
@@ -0,0 +1,153 @@
+/* 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 source tree.
+ */
+
+/* BB_AUDIT SUSv3 N/A */
+
+//usage:#define vconfig_trivial_usage
+//usage: "COMMAND [OPTIONS]"
+//usage:#define vconfig_full_usage "\n\n"
+//usage: "Create and remove virtual ethernet devices\n"
+//usage: "\n add IFACE VLAN_ID"
+//usage: "\n rem VLAN_NAME"
+//usage: "\n set_flag IFACE 0|1 VLAN_QOS"
+//usage: "\n set_egress_map VLAN_NAME SKB_PRIO VLAN_QOS"
+//usage: "\n set_ingress_map VLAN_NAME SKB_PRIO VLAN_QOS"
+//usage: "\n set_name_type NAME_TYPE"
+
+#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[0])
+ bb_show_usage();
+ table += table[0];
+ }
+ 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, 0,
+ '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, 0,
+ 'D','E','V','_','P','L','U','S','_','V','I','D','_','N','O','_','P','A','D',0,
+};
+
+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;
+
+ memset(&ifr, 0, sizeof(ifr));
+
+ ++argv;
+ if (!argv[0])
+ bb_show_usage();
+ p = xfind_str(cmds + 2, argv[0]);
+ 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_IFNAMSIZ(ifr.device1, argv[1]);
+ p = argv[2];
+
+ /* I suppose one could try to combine some of the function calls below,
+ * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized
+ * (unsigned) int members of a unions. But because of the range checking,
+ * doing so wouldn't save that much space and would also make maintainence
+ * more of a pain.
+ */
+ if (ifr.cmd == SET_VLAN_FLAG_CMD) {
+ /* set_flag */
+ ifr.u.flag = xatou_range(p, 0, 1);
+ /* DM: in order to set reorder header, qos must be set */
+ ifr.vlan_qos = xatou_range(argv[3], 0, 7);
+ } else if (ifr.cmd == ADD_VLAN_CMD) {
+ /* add */
+ ifr.u.VID = xatou_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 = xatou_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[0]);
+
+ return 0;
+}
diff --git a/ap/app/busybox/src/networking/wget.c b/ap/app/busybox/src/networking/wget.c
new file mode 100644
index 0000000..4eafebe
--- /dev/null
+++ b/ap/app/busybox/src/networking/wget.c
@@ -0,0 +1,991 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wget - retrieve a file using HTTP or FTP
+ *
+ * Chip Rosenthal Covad Communications <chip@laserlink.net>
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ *
+ * Copyright (C) 2010 Bradley M. Kuhn <bkuhn@ebb.org>
+ * Kuhn's copyrights are licensed GPLv2-or-later. File as a whole remains GPLv2.
+ */
+
+//usage:#define wget_trivial_usage
+//usage: IF_FEATURE_WGET_LONG_OPTIONS(
+//usage: "[-c|--continue] [-s|--spider] [-q|--quiet] [-O|--output-document FILE]\n"
+//usage: " [--header 'header: value'] [-Y|--proxy on/off] [-P DIR]\n"
+/* Since we ignore these opts, we don't show them in --help */
+/* //usage: " [--no-check-certificate] [--no-cache]" */
+//usage: " [-U|--user-agent AGENT]" IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
+//usage: )
+//usage: IF_NOT_FEATURE_WGET_LONG_OPTIONS(
+//usage: "[-csq] [-O FILE] [-Y on/off] [-P DIR] [-U AGENT]"
+//usage: IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
+//usage: )
+//usage:#define wget_full_usage "\n\n"
+//usage: "Retrieve files via HTTP or FTP\n"
+//usage: "\n -s Spider mode - only check file existence"
+//usage: "\n -c Continue retrieval of aborted transfer"
+//usage: "\n -q Quiet"
+//usage: "\n -P DIR Save to DIR (default .)"
+//usage: IF_FEATURE_WGET_TIMEOUT(
+//usage: "\n -T SEC Network read timeout is SEC seconds"
+//usage: )
+//usage: "\n -O FILE Save to FILE ('-' for stdout)"
+//usage: "\n -U STR Use STR for User-Agent header"
+//usage: "\n -Y Use proxy ('on' or 'off')"
+
+#include "libbb.h"
+
+#if 0
+# define log_io(...) bb_error_msg(__VA_ARGS__)
+#else
+# define log_io(...) ((void)0)
+#endif
+
+
+struct host_info {
+ char *allocated;
+ const char *path;
+ const char *user;
+ char *host;
+ int port;
+ smallint is_ftp;
+};
+
+
+/* Globals */
+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 transferred; /* Number of bytes transferred so far */
+ const char *curfile; /* Name of current file being transferred */
+ bb_progress_t pmt;
+#endif
+ char *dir_prefix;
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ char *post_data;
+ char *extra_headers;
+#endif
+ char *fname_out; /* where to direct output (-O) */
+ const char *proxy_flag; /* Use proxies if env vars are set */
+ const char *user_agent; /* "User-Agent" header field */
+#if ENABLE_FEATURE_WGET_TIMEOUT
+ unsigned timeout_seconds;
+#endif
+ int output_fd;
+ int o_flags;
+ smallint chunked; /* chunked transfer encoding */
+ smallint got_clen; /* got content-length: from server */
+ /* Local downloads do benefit from big buffer.
+ * With 512 byte buffer, it was measured to be
+ * an order of magnitude slower than with big one.
+ */
+ uint64_t just_to_align_next_member;
+ char wget_buf[CONFIG_FEATURE_COPYBUF_KB*1024];
+} FIX_ALIASING;
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ IF_FEATURE_WGET_TIMEOUT(G.timeout_seconds = 900;) \
+} while (0)
+
+
+/* Must match option string! */
+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_NETWORK_READ_TIMEOUT = (1 << 7),
+ WGET_OPT_RETRIES = (1 << 8),
+ WGET_OPT_PASSIVE = (1 << 9),
+ WGET_OPT_HEADER = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+ WGET_OPT_POST_DATA = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+};
+
+enum {
+ PROGRESS_START = -1,
+ PROGRESS_END = 0,
+ PROGRESS_BUMP = 1,
+};
+#if ENABLE_FEATURE_WGET_STATUSBAR
+static void progress_meter(int flag)
+{
+ if (option_mask32 & WGET_OPT_QUIET)
+ return;
+
+ if (flag == PROGRESS_START)
+ bb_progress_init(&G.pmt, G.curfile);
+
+ bb_progress_update(&G.pmt,
+ G.beg_range,
+ G.transferred,
+ (G.chunked || !G.got_clen) ? 0 : G.beg_range + G.transferred + G.content_len
+ );
+
+ if (flag == PROGRESS_END) {
+ bb_progress_free(&G.pmt);
+ bb_putchar_stderr('\n');
+ G.transferred = 0;
+ }
+}
+#else
+static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
+#endif
+
+
+/* IPv6 knows scoped address types i.e. link and site local addresses. Link
+ * local addresses can have a scope identifier to specify the
+ * interface/link an address is valid on (e.g. fe80::1%eth0). This scope
+ * identifier is only valid on a single node.
+ *
+ * RFC 4007 says that the scope identifier MUST NOT be sent across the wire,
+ * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers
+ * in the Host header as invalid requests, see
+ * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122
+ */
+static void strip_ipv6_scope_id(char *host)
+{
+ char *scope, *cp;
+
+ /* bbox wget actually handles IPv6 addresses without [], like
+ * wget "http://::1/xxx", but this is not standard.
+ * To save code, _here_ we do not support it. */
+
+ if (host[0] != '[')
+ return; /* not IPv6 */
+
+ scope = strchr(host, '%');
+ if (!scope)
+ return;
+
+ /* Remove the IPv6 zone identifier from the host address */
+ cp = strchr(host, ']');
+ if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
+ /* malformed address (not "[xx]:nn" or "[xx]") */
+ return;
+ }
+
+ /* cp points to "]...", scope points to "%eth0]..." */
+ overlapping_strcpy(scope, cp);
+}
+
+#if ENABLE_FEATURE_WGET_AUTHENTICATION
+/* Base64-encode character string. */
+static char *base64enc(const char *str)
+{
+ unsigned len = strlen(str);
+ if (len > sizeof(G.wget_buf)/4*3 - 10) /* paranoia */
+ len = sizeof(G.wget_buf)/4*3 - 10;
+ bb_uuencode(G.wget_buf, str, len, bb_uuenc_tbl_base64);
+ return G.wget_buf;
+}
+#endif
+
+static char* sanitize_string(char *s)
+{
+ unsigned char *p = (void *) s;
+ while (*p >= ' ')
+ p++;
+ *p = '\0';
+ return s;
+}
+
+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(bb_msg_memory_exhausted);
+
+ return fp;
+}
+
+/* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
+static char fgets_and_trim(FILE *fp)
+{
+ char c;
+ char *buf_ptr;
+
+ if (fgets(G.wget_buf, sizeof(G.wget_buf) - 1, fp) == NULL)
+ bb_perror_msg_and_die("error getting response");
+
+ buf_ptr = strchrnul(G.wget_buf, '\n');
+ c = *buf_ptr;
+ *buf_ptr = '\0';
+ buf_ptr = strchrnul(G.wget_buf, '\r');
+ *buf_ptr = '\0';
+
+ log_io("< %s", G.wget_buf);
+
+ return c;
+}
+
+static int ftpcmd(const char *s1, const char *s2, FILE *fp)
+{
+ int result;
+ if (s1) {
+ if (!s2)
+ s2 = "";
+ fprintf(fp, "%s%s\r\n", s1, s2);
+ fflush(fp);
+ log_io("> %s%s", s1, s2);
+ }
+
+ do {
+ fgets_and_trim(fp);
+ } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
+
+ G.wget_buf[3] = '\0';
+ result = xatoi_positive(G.wget_buf);
+ G.wget_buf[3] = ' ';
+ return result;
+}
+
+static void parse_url(const char *src_url, struct host_info *h)
+{
+ char *url, *p, *sp;
+
+ free(h->allocated);
+ 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", sanitize_string(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;
+ }
+
+ // We used to set h->user to NULL here, but this interferes
+ // with handling of code 302 ("object was moved")
+
+ sp = strrchr(h->host, '@');
+ if (sp != NULL) {
+ // URL-decode "user:password" string before base64-encoding:
+ // wget http://test:my%20pass@example.com should send
+ // Authorization: Basic dGVzdDpteSBwYXNz
+ // which decodes to "test:my pass".
+ // Standard wget and curl do this too.
+ *sp = '\0';
+ h->user = percent_decode_in_place(h->host, /*strict:*/ 0);
+ h->host = sp + 1;
+ }
+
+ sp = h->host;
+}
+
+static char *gethdr(FILE *fp)
+{
+ char *s, *hdrval;
+ int c;
+
+ /* retrieve header line */
+ c = fgets_and_trim(fp);
+
+ /* end of the headers? */
+ if (G.wget_buf[0] == '\0')
+ return NULL;
+
+ /* convert the header name to lower case */
+ for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.'; ++s) {
+ /* tolower for "A-Z", no-op for "0-9a-z-." */
+ *s |= 0x20;
+ }
+
+ /* verify we are at the end of the header name */
+ if (*s != ':')
+ bb_error_msg_and_die("bad header line: %s", sanitize_string(G.wget_buf));
+
+ /* locate the start of the header value */
+ *s++ = '\0';
+ hdrval = skip_whitespace(s);
+
+ if (c != '\n') {
+ /* Rats! The buffer isn't big enough to hold the entire header value */
+ while (c = getc(fp), c != EOF && c != '\n')
+ continue;
+ }
+
+ return hdrval;
+}
+
+static void reset_beg_range_to_zero(void)
+{
+ bb_error_msg("restart failed");
+ G.beg_range = 0;
+ xlseek(G.output_fd, 0, SEEK_SET);
+ /* Done at the end instead: */
+ /* ftruncate(G.output_fd, 0); */
+}
+
+static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
+{
+ FILE *sfp;
+ char *str;
+ int port;
+
+ if (!target->user)
+ target->user = xstrdup("anonymous:busybox@");
+
+ sfp = open_socket(lsa);
+ if (ftpcmd(NULL, NULL, sfp) != 220)
+ bb_error_msg_and_die("%s", sanitize_string(G.wget_buf + 4));
+
+ /*
+ * Splitting username:password pair,
+ * trying to log in
+ */
+ str = strchr(target->user, ':');
+ if (str)
+ *str++ = '\0';
+ switch (ftpcmd("USER ", target->user, sfp)) {
+ case 230:
+ break;
+ case 331:
+ if (ftpcmd("PASS ", str, sfp) == 230)
+ break;
+ /* fall through (failed login) */
+ default:
+ bb_error_msg_and_die("ftp login: %s", sanitize_string(G.wget_buf + 4));
+ }
+
+ ftpcmd("TYPE I", NULL, sfp);
+
+ /*
+ * Querying file size
+ */
+ if (ftpcmd("SIZE ", target->path, sfp) == 213) {
+ G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
+ if (G.content_len < 0 || errno) {
+ bb_error_msg_and_die("SIZE value is garbage");
+ }
+ G.got_clen = 1;
+ }
+
+ /*
+ * Entering passive mode
+ */
+ if (ftpcmd("PASV", NULL, sfp) != 227) {
+ pasv_error:
+ bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(G.wget_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(G.wget_buf, ')');
+ if (str) str[0] = '\0';
+ str = strrchr(G.wget_buf, ',');
+ if (!str) goto pasv_error;
+ port = xatou_range(str+1, 0, 255);
+ *str = '\0';
+ str = strrchr(G.wget_buf, ',');
+ if (!str) goto pasv_error;
+ port += xatou_range(str+1, 0, 255) * 256;
+ set_nport(&lsa->u.sa, htons(port));
+
+ *dfpp = open_socket(lsa);
+
+ if (G.beg_range != 0) {
+ sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
+ if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
+ G.content_len -= G.beg_range;
+ else
+ reset_beg_range_to_zero();
+ }
+
+ if (ftpcmd("RETR ", target->path, sfp) > 150)
+ bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(G.wget_buf));
+
+ return sfp;
+}
+
+static void NOINLINE retrieve_file_data(FILE *dfp)
+{
+#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
+# if ENABLE_FEATURE_WGET_TIMEOUT
+ unsigned second_cnt = G.timeout_seconds;
+# endif
+ struct pollfd polldata;
+
+ polldata.fd = fileno(dfp);
+ polldata.events = POLLIN | POLLPRI;
+#endif
+ progress_meter(PROGRESS_START);
+
+ if (G.chunked)
+ goto get_clen;
+
+ /* Loops only if chunked */
+ while (1) {
+
+#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
+ /* Must use nonblocking I/O, otherwise fread will loop
+ * and *block* until it reads full buffer,
+ * which messes up progress bar and/or timeout logic.
+ * Because of nonblocking I/O, we need to dance
+ * very carefully around EAGAIN. See explanation at
+ * clearerr() calls.
+ */
+ ndelay_on(polldata.fd);
+#endif
+ while (1) {
+ int n;
+ unsigned rdsz;
+
+#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
+ /* fread internally uses read loop, which in our case
+ * is usually exited when we get EAGAIN.
+ * In this case, libc sets error marker on the stream.
+ * Need to clear it before next fread to avoid possible
+ * rare false positive ferror below. Rare because usually
+ * fread gets more than zero bytes, and we don't fall
+ * into if (n <= 0) ...
+ */
+ clearerr(dfp);
+#endif
+ errno = 0;
+ rdsz = sizeof(G.wget_buf);
+ if (G.got_clen) {
+ if (G.content_len < (off_t)sizeof(G.wget_buf)) {
+ if ((int)G.content_len <= 0)
+ break;
+ rdsz = (unsigned)G.content_len;
+ }
+ }
+ n = fread(G.wget_buf, 1, rdsz, dfp);
+
+ if (n > 0) {
+ xwrite(G.output_fd, G.wget_buf, n);
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ G.transferred += n;
+#endif
+ if (G.got_clen) {
+ G.content_len -= n;
+ if (G.content_len == 0)
+ break;
+ }
+#if ENABLE_FEATURE_WGET_TIMEOUT
+ second_cnt = G.timeout_seconds;
+#endif
+ continue;
+ }
+
+ /* n <= 0.
+ * man fread:
+ * If error occurs, or EOF is reached, the return value
+ * is a short item count (or zero).
+ * fread does not distinguish between EOF and error.
+ */
+ if (errno != EAGAIN) {
+ if (ferror(dfp)) {
+ progress_meter(PROGRESS_END);
+ bb_perror_msg_and_die(bb_msg_read_error);
+ }
+ break; /* EOF, not error */
+ }
+
+#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
+ /* It was EAGAIN. There is no data. Wait up to one second
+ * then abort if timed out, or update the bar and try reading again.
+ */
+ if (safe_poll(&polldata, 1, 1000) == 0) {
+# if ENABLE_FEATURE_WGET_TIMEOUT
+ if (second_cnt != 0 && --second_cnt == 0) {
+ progress_meter(PROGRESS_END);
+ bb_error_msg_and_die("download timed out");
+ }
+# endif
+ /* We used to loop back to poll here,
+ * but there is no great harm in letting fread
+ * to try reading anyway.
+ */
+ }
+ /* Need to do it _every_ second for "stalled" indicator
+ * to be shown properly.
+ */
+ progress_meter(PROGRESS_BUMP);
+#endif
+ } /* while (reading data) */
+
+#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
+ clearerr(dfp);
+ ndelay_off(polldata.fd); /* else fgets can get very unhappy */
+#endif
+ if (!G.chunked)
+ break;
+
+ fgets_and_trim(dfp); /* Eat empty line */
+ get_clen:
+ fgets_and_trim(dfp);
+ G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
+ /* FIXME: error check? */
+ if (G.content_len == 0)
+ break; /* all done! */
+ G.got_clen = 1;
+ /*
+ * Note that fgets may result in some data being buffered in dfp.
+ * We loop back to fread, which will retrieve this data.
+ * Also note that code has to be arranged so that fread
+ * is done _before_ one-second poll wait - poll doesn't know
+ * about stdio buffering and can result in spurious one second waits!
+ */
+ }
+
+ /* If -c failed, we restart from the beginning,
+ * but we do not truncate file then, we do it only now, at the end.
+ * This lets user to ^C if his 99% complete 10 GB file download
+ * failed to restart *without* losing the almost complete file.
+ */
+ {
+ off_t pos = lseek(G.output_fd, 0, SEEK_CUR);
+ if (pos != (off_t)-1)
+ ftruncate(G.output_fd, pos);
+ }
+
+ /* Draw full bar and free its resources */
+ G.chunked = 0; /* makes it show 100% even for chunked download */
+ G.got_clen = 1; /* makes it show 100% even for download of (formerly) unknown size */
+ progress_meter(PROGRESS_END);
+}
+
+static void download_one_url(const char *url)
+{
+ bool use_proxy; /* Use proxies if env vars are set */
+ int redir_limit;
+ len_and_sockaddr *lsa;
+ FILE *sfp; /* socket to web/ftp server */
+ FILE *dfp; /* socket to ftp server (data) */
+ char *proxy = NULL;
+ char *fname_out_alloc;
+ char *redirected_path = NULL;
+ struct host_info server;
+ struct host_info target;
+
+ server.allocated = NULL;
+ target.allocated = NULL;
+ server.user = NULL;
+ target.user = NULL;
+
+ parse_url(url, &target);
+
+ /* Use the proxy if necessary */
+ use_proxy = (strcmp(G.proxy_flag, "off") != 0);
+ if (use_proxy) {
+ proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
+ use_proxy = (proxy && proxy[0]);
+ if (use_proxy)
+ parse_url(proxy, &server);
+ }
+ if (!use_proxy) {
+ server.port = target.port;
+ if (ENABLE_FEATURE_IPV6) {
+ //free(server.allocated); - can't be non-NULL
+ server.host = server.allocated = xstrdup(target.host);
+ } else {
+ server.host = target.host;
+ }
+ }
+
+ if (ENABLE_FEATURE_IPV6)
+ strip_ipv6_scope_id(target.host);
+
+ /* If there was no -O FILE, guess output filename */
+ fname_out_alloc = NULL;
+ if (!(option_mask32 & WGET_OPT_OUTNAME)) {
+ G.fname_out = bb_get_last_path_component_nostrip(target.path);
+ /* handle "wget http://kernel.org//" */
+ if (G.fname_out[0] == '/' || !G.fname_out[0])
+ G.fname_out = (char*)"index.html";
+ /* -P DIR is considered only if there was no -O FILE */
+ if (G.dir_prefix)
+ G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
+ else {
+ /* redirects may free target.path later, need to make a copy */
+ G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
+ }
+ }
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
+#endif
+
+ /* Determine where to start transfer */
+ G.beg_range = 0;
+ if (option_mask32 & WGET_OPT_CONTINUE) {
+ G.output_fd = open(G.fname_out, O_WRONLY);
+ if (G.output_fd >= 0) {
+ G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
+ }
+ /* File doesn't exist. We do not create file here yet.
+ * We are not sure it exists on remote side */
+ }
+
+ redir_limit = 5;
+ resolve_lsa:
+ lsa = xhost2sockaddr(server.host, server.port);
+ if (!(option_mask32 & WGET_OPT_QUIET)) {
+ char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
+ fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
+ free(s);
+ }
+ establish_session:
+ /*G.content_len = 0; - redundant, got_clen = 0 is enough */
+ G.got_clen = 0;
+ G.chunked = 0;
+ if (use_proxy || !target.is_ftp) {
+ /*
+ * HTTP session
+ */
+ char *str;
+ int status;
+
+
+ /* Open socket to http server */
+ 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 {
+ if (option_mask32 & WGET_OPT_POST_DATA)
+ fprintf(sfp, "POST /%s HTTP/1.1\r\n", 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, G.user_agent);
+
+ /* Ask server to close the connection as soon as we are done
+ * (IOW: we do not intend to send more requests)
+ */
+ fprintf(sfp, "Connection: close\r\n");
+
+#if ENABLE_FEATURE_WGET_AUTHENTICATION
+ if (target.user) {
+ fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
+ base64enc(target.user));
+ }
+ if (use_proxy && server.user) {
+ fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
+ base64enc(server.user));
+ }
+#endif
+
+ if (G.beg_range != 0)
+ fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ if (G.extra_headers)
+ fputs(G.extra_headers, sfp);
+
+ if (option_mask32 & WGET_OPT_POST_DATA) {
+ fprintf(sfp,
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %u\r\n"
+ "\r\n"
+ "%s",
+ (int) strlen(G.post_data), G.post_data
+ );
+ } else
+#endif
+ {
+ fprintf(sfp, "\r\n");
+ }
+
+ fflush(sfp);
+
+ /*
+ * Retrieve HTTP response line and check for "200" status code.
+ */
+ read_response:
+ fgets_and_trim(sfp);
+
+ str = G.wget_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(sfp) != 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:
+ if (G.beg_range != 0) {
+ /* "Range:..." was not honored by the server.
+ * Restart download from the beginning.
+ */
+ reset_beg_range_to_zero();
+ }
+ break;
+ case 300: /* redirection */
+ case 301:
+ case 302:
+ case 303:
+ break;
+ case 206: /* Partial Content */
+ if (G.beg_range != 0)
+ /* "Range:..." worked. Good. */
+ break;
+ /* Partial Content even though we did not ask for it??? */
+ /* fall through */
+ default:
+ bb_error_msg_and_die("server returned error: %s", sanitize_string(G.wget_buf));
+ }
+
+ /*
+ * Retrieve HTTP headers.
+ */
+ while ((str = gethdr(sfp)) != NULL) {
+ static const char keywords[] ALIGN1 =
+ "content-length\0""transfer-encoding\0""location\0";
+ enum {
+ KEY_content_length = 1, KEY_transfer_encoding, KEY_location
+ };
+ smalluint key;
+
+ /* gethdr converted "FOO:" string to lowercase */
+
+ /* strip trailing whitespace */
+ char *s = strchrnul(str, '\0') - 1;
+ while (s >= str && (*s == ' ' || *s == '\t')) {
+ *s = '\0';
+ s--;
+ }
+ key = index_in_strings(keywords, G.wget_buf) + 1;
+ if (key == KEY_content_length) {
+ G.content_len = BB_STRTOOFF(str, NULL, 10);
+ if (G.content_len < 0 || errno) {
+ bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
+ }
+ G.got_clen = 1;
+ continue;
+ }
+ if (key == KEY_transfer_encoding) {
+ if (strcmp(str_tolower(str), "chunked") != 0)
+ bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
+ G.chunked = 1;
+ }
+ if (key == KEY_location && status >= 300) {
+ if (--redir_limit == 0)
+ bb_error_msg_and_die("too many redirections");
+ fclose(sfp);
+ if (str[0] == '/') {
+ free(redirected_path);
+ target.path = redirected_path = xstrdup(str+1);
+ /* lsa stays the same: it's on the same server */
+ } else {
+ parse_url(str, &target);
+ if (!use_proxy) {
+ free(server.allocated);
+ server.allocated = NULL;
+ server.host = target.host;
+ /* strip_ipv6_scope_id(target.host); - no! */
+ /* we assume remote never gives us IPv6 addr with scope id */
+ server.port = target.port;
+ free(lsa);
+ goto resolve_lsa;
+ } /* else: lsa stays the same: we use proxy */
+ }
+ goto establish_session;
+ }
+ }
+// if (status >= 300)
+// bb_error_msg_and_die("bad redirection (no Location: header from server)");
+
+ /* For HTTP, data is pumped over the same connection */
+ dfp = sfp;
+
+ } else {
+ /*
+ * FTP session
+ */
+ sfp = prepare_ftp_session(&dfp, &target, lsa);
+ }
+
+ free(lsa);
+
+ if (!(option_mask32 & WGET_OPT_SPIDER)) {
+ if (G.output_fd < 0)
+ G.output_fd = xopen(G.fname_out, G.o_flags);
+ retrieve_file_data(dfp);
+ if (!(option_mask32 & WGET_OPT_OUTNAME)) {
+ xclose(G.output_fd);
+ G.output_fd = -1;
+ }
+ }
+
+ if (dfp != sfp) {
+ /* It's ftp. Close data connection properly */
+ fclose(dfp);
+ if (ftpcmd(NULL, NULL, sfp) != 226)
+ bb_error_msg_and_die("ftp error: %s", sanitize_string(G.wget_buf + 4));
+ /* ftpcmd("QUIT", NULL, sfp); - why bother? */
+ }
+ fclose(sfp);
+
+ free(server.allocated);
+ free(target.allocated);
+ free(fname_out_alloc);
+ free(redirected_path);
+}
+
+int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int wget_main(int argc UNUSED_PARAM, char **argv)
+{
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ static const char wget_longopts[] ALIGN1 =
+ /* name, has_arg, val */
+ "continue\0" No_argument "c"
+//FIXME: -s isn't --spider, it's --save-headers!
+ "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"
+#if ENABLE_FEATURE_WGET_TIMEOUT
+ "timeout\0" Required_argument "T"
+#endif
+ /* Ignored: */
+ // "tries\0" Required_argument "t"
+ /* Ignored (we always use PASV): */
+ "passive-ftp\0" No_argument "\xff"
+ "header\0" Required_argument "\xfe"
+ "post-data\0" Required_argument "\xfd"
+ /* Ignored (we don't do ssl) */
+ "no-check-certificate\0" No_argument "\xfc"
+ /* Ignored (we don't support caching) */
+ "no-cache\0" No_argument "\xfb"
+ ;
+#endif
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ llist_t *headers_llist = NULL;
+#endif
+
+ INIT_G();
+
+ IF_FEATURE_WGET_TIMEOUT(G.timeout_seconds = 900;)
+ G.proxy_flag = "on"; /* use proxies if env vars are set */
+ G.user_agent = "Wget"; /* "User-Agent" header field */
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ applet_long_options = wget_longopts;
+#endif
+ opt_complementary = "-1" IF_FEATURE_WGET_TIMEOUT(":T+") IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
+ getopt32(argv, "csqO:P:Y:U:T:" /*ignored:*/ "t:",
+ &G.fname_out, &G.dir_prefix,
+ &G.proxy_flag, &G.user_agent,
+ IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
+ NULL /* -t RETRIES */
+ IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
+ IF_FEATURE_WGET_LONG_OPTIONS(, &G.post_data)
+ );
+ argv += optind;
+
+#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;
+ }
+ G.extra_headers = cp = xmalloc(size);
+ while (headers_llist) {
+ cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
+ }
+ }
+#endif
+
+ G.output_fd = -1;
+ G.o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
+ if (G.fname_out) { /* -O FILE ? */
+ if (LONE_DASH(G.fname_out)) { /* -O - ? */
+ G.output_fd = 1;
+ option_mask32 &= ~WGET_OPT_CONTINUE;
+ }
+ /* compat with wget: -O FILE can overwrite */
+ G.o_flags = O_WRONLY | O_CREAT | O_TRUNC;
+ }
+
+ while (*argv)
+ download_one_url(*argv++);
+
+ if (G.output_fd >= 0)
+ xclose(G.output_fd);
+
+ return EXIT_SUCCESS;
+}
diff --git a/ap/app/busybox/src/networking/whois.c b/ap/app/busybox/src/networking/whois.c
new file mode 100644
index 0000000..bf33033
--- /dev/null
+++ b/ap/app/busybox/src/networking/whois.c
@@ -0,0 +1,65 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * whois - tiny client for the whois directory service
+ *
+ * Copyright (c) 2011 Pere Orga <gotrunks@gmail.com>
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+/* TODO
+ * Add ipv6 support
+ * Add proxy support
+ */
+
+//config:config WHOIS
+//config: bool "whois"
+//config: default y
+//config: help
+//config: whois is a client for the whois directory service
+
+//applet:IF_WHOIS(APPLET(whois, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_WHOIS) += whois.o
+
+//usage:#define whois_trivial_usage
+//usage: "[-h SERVER] [-p PORT] NAME..."
+//usage:#define whois_full_usage "\n\n"
+//usage: "Query WHOIS info about NAME\n"
+//usage: "\n -h,-p Server to query"
+
+#include "libbb.h"
+
+static void pipe_out(int fd)
+{
+ FILE *fp;
+ char buf[1024];
+
+ fp = xfdopen_for_read(fd);
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *p = strpbrk(buf, "\r\n");
+ if (p)
+ *p = '\0';
+ puts(buf);
+ }
+
+ fclose(fp); /* closes fd too */
+}
+
+int whois_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int whois_main(int argc UNUSED_PARAM, char **argv)
+{
+ int port = 43;
+ const char *host = "whois-servers.net";
+
+ opt_complementary = "-1:p+";
+ getopt32(argv, "h:p:", &host, &port);
+
+ argv += optind;
+ do {
+ int fd = create_and_connect_stream_or_die(host, port);
+ fdprintf(fd, "%s\r\n", *argv);
+ pipe_out(fd);
+ }
+ while (*++argv);
+
+ return EXIT_SUCCESS;
+}
diff --git a/ap/app/busybox/src/networking/zcip.c b/ap/app/busybox/src/networking/zcip.c
new file mode 100644
index 0000000..7314ff8
--- /dev/null
+++ b/ap/app/busybox/src/networking/zcip.c
@@ -0,0 +1,578 @@
+/* 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 GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+/*
+ * 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)
+
+//usage:#define zcip_trivial_usage
+//usage: "[OPTIONS] IFACE SCRIPT"
+//usage:#define zcip_full_usage "\n\n"
+//usage: "Manage a ZeroConf IPv4 link-local address\n"
+//usage: "\n -f Run in foreground"
+//usage: "\n -q Quit after obtaining address"
+//usage: "\n -r 169.254.x.x Request this address first"
+//usage: "\n -v Verbose"
+//usage: "\n"
+//usage: "\nWith no -q, runs continuously monitoring for ARP conflicts,"
+//usage: "\nexits only on I/O errors (link down etc)"
+
+#include "libbb.h"
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/sockios.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;
+} FIX_ALIASING;
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define saddr (G.saddr )
+#define eth_addr (G.eth_addr)
+#define INIT_G() do { } while (0)
+
+
+/**
+ * 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 = spawn_and_wait(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 & 0xff);
+ 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 UNUSED_PARAM, 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));
+ INIT_G();
+
+#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");
+ }
+ }
+ 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_IFNAMSIZ(ifr.ifr_name, argv_intf);
+ 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;
+ move_from_unaligned32(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
+}