[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
+	  "&#60Hello&#32World&#62".
+
+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 "&#60Hello&#32World&#62"
+ * 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("&lt;");
+		else if (c == '>')
+			fmt_str("&gt;");
+		else if (c == '&') {
+			fmt_str("&amp;");
+		} 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); /* &lt; 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,
+	&ether_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 = &wr;
+		}
+
+		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(&eth_settings, 0, sizeof(eth_settings));
+		eth_settings.cmd = ETHTOOL_GSET;
+		ifr.ifr_data = (caddr_t) &eth_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, &param->local_port,
+			rem_addr, &param->rem_port, &param->state,
+			&param->txq, &param->rxq,
+			&param->uid, &param->inode);
+	if (num < 9) {
+		return 1; /* error */
+	}
+
+	if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+		build_ipv6_addr(local_addr, &param->localaddr.sin6);
+		build_ipv6_addr(rem_addr, &param->remaddr.sin6);
+#endif
+	} else {
+		build_ipv4_addr(local_addr, &param->localaddr.sin);
+		build_ipv4_addr(rem_addr, &param->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(
+				&param->localaddr.sa, param->local_port,
+				proto, flags & NETSTAT_NUMERIC);
+		char *r = ip_port_str(
+				&param->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(&param, 0, sizeof(param));
+	if (scan_inet_proc_line(&param, line))
+		return 1;
+
+	print_inet_line(&param, 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(&param, 0, sizeof(param)); /* otherwise we display garbage IPv6 scope_ids */
+	if (scan_inet_proc_line(&param, 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(&param, 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(&param, line))
+		return 1;
+
+	have_remaddr = NOT_NULL_ADDR(param.remaddr);
+	print_inet_line(&param, 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 &eth_addr */
+					struct in_addr source_ip,
+	const struct ether_addr *target_eth, struct in_addr target_ip)
+{
+	enum { op = ARPOP_REQUEST };
+#define source_eth (&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(&eth_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 *)&eth_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, */
+							/* &eth_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, */
+							/* &eth_addr, */ ip,
+							&eth_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, */
+						/* &eth_addr, */ ip,
+						&eth_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, */
+							/* &eth_addr, */ ip,
+							&eth_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, &eth_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, &eth_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, */
+						/* &eth_addr, */ ip,
+						&eth_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
+}