Starting mysqld and httpd on demand with xinetd
My father gave me an awesome present besides life 🙂
He gave me a Raspberry Pi B model, its basically a low power consumption, low spec computer board, at a very low price intended for tinkerers, and electronic education.
I am using it to host my own PBX which is based of this project: Asterisk for raspberry
I am also using it for other purposes such as monitoring my home network as an active IDS, a dns server, a proxy, a vpn server, basically I want to use it for all the things you always wanted to have on a always-on server inside your house.
The problem I encountered was that the Pi model B comes with only 512 mb of RAM, and the Freepbx software uses Apache PHP5 and Mysql which consumes a good amount of RAM.
First step was easy, I just installed lighttpd and stopped apache. Second step I started looking into changing Freepbx to a SQLite engine, to get rid of MySQL, but Freepbx support for it was not 100%.
So after looking for a while I found a very nice solution for all my troubles, using xinetd and netcat (awesome tools) I start services on demand, when a connection comes in I start the required service and leave a background process to check on if the service is still required, if the port sees no further activity the service gets shutdown. So I can now have all the services that I want on the resource limited device.
I struggled to find how to do this. So I am sharing how I did it. I am also basing myself of this great post which does something similar but with another purpose in mind. Automatic Tunnels with xinetd and netcat.
Tools required:
- xinetd
- netcat
- service httpd, mysqld
- bash proxy script
This is my startup script I created here /etc/xinetd.init.d/lighttpd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | #!/bin/bash # inspired on this article # https://www.linuxnet.ch/automatic-tunnels-with-xinetd-and-netcat/ # script is called from xinetd # script starts and stops service using /etc/init.d scripts  SERVICE=/etc/init.d/lighttpd # script used to start/stop service PORT=81 # port where end service should listen PID_FILE=/var/run/lighttpd.pid # pid file generated by init script REAPER_PID_FILE="/var/run/lighttpd_reaper.pid" # pid file generated by this script REAPER_SLEEP=180 # The reaper sleeps in seconds and checks for idle conns LOG=/var/log/syslog # where to log messages  # this function checks if we already have reaper check_reaper(){ if [ -s $REAPER_PID_FILE ] then reaper_pid=`cat $REAPER_PID_FILE 2>/dev/null` ps -p $reaper_pid &> /dev/null if [ $? -ne 0 ] then start_reaper & # If we dont have a reaper we start one echo $! > $REAPER_PID_FILE fi else start_reaper & echo $! > $REAPER_PID_FILE fi  }  # this function starts a reaper, which is a background process that will kill the end service if its inactive start_reaper(){ while [ -f $PID_FILE ] do sleep $REAPER_SLEEP # We wait touched=`stat --printf %W $REAPER_PID_FILE 2>/dev/null` # We check when the reaper PID was last touched now=`date +%s 2>/dev/null` let idle=$now-$touched if [ $idle -gt $REAPER_SLEEP ] # If reaper pid has not been touched in more than a sleep cycle we stop the service then echo `date`" REAPER STOPPING SERVICE AFTER BEING $idle" >> $LOG $SERVICE stop >> $LOG # This is the stop service instruction rm $REAPER_PID_FILE exit 0 fi done }  # This is where we start our service start_service(){ sleep 1 # Added a delay to trouble shoot as browsers kickstart several connections, we need to allow the PID file to be created this can be improved. if [ -s $PID_FILE ] # We check if the PID file for the end service exist to avoid calling the start script when the service has already been started then return else echo `date`" STARTING $SERVICE" >> $LOG $SERVICE start &>> $LOG #this is the start service instruction return fi }  # We probe and wait for the service to come on line wait_for_service(){ nc -w30 -z 127.0.0.1 $PORT &>/dev/null # probe end port with a timeout of 30 seconds if [[ $? -ne 0 ]] then echo `date`" XINET SERVICE START ON $PORT TIMED OUT" >> $LOG fi }  # This is were all the magic happens netcat passes traffic back and forth transmit(){ nc -w30 127.0.0.1 $PORT 2>/dev/null # netcat is awesome, timeout flag of 30 seconds can be adjusted }  # this is the main program that is called every time main() { nc -z 127.0.0.1 $PORT &>/dev/null # We probe the end service if [[ $? -ne 0 ]] # If its not responding then start_service # We start service wait_for_service # We wait for service to became online fi check_reaper # We always check we have a reaper touch $REAPER_PID_FILE # We log activity by touching the reaper PID file transmit # We transmit data exit 0 }  main |
This is the configuration for xinet.d
1 2 3 4 5 6 7 8 9 10 | service http { disable = no socket_type = stream protocol = tcp port = 80 server = /etc/xinetd.init.d/lighttpd user = root wait = no } |
For mysqld I did something similar but I had to add a wait time as mysql takes longer to boot
mysql startup script I placed it in /etc/xinetd.init.d/mysqld
Another thing I encountered was that mysql clients tend to default to use the socket file, so I had to reconfigure so it would listen on the eth0 address, as a precaution I added a rule to only allow the same IP to access it, I can probably improve this too but I got it working like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | #!/bin/bash # inspired on this article # https://www.linuxnet.ch/automatic-tunnels-with-xinetd-and-netcat/  SERVICE=/etc/init.d/mysql PORT=3307 PID_FILE=/var/run/mysqld/mysqld.pid REAPER_PID_FILE=/var/run/mysqld/mysqld_reaper.pid REAPER_SLEEP=300 LOG=/var/log/syslog IP='192.168.1.50'  check_reaper(){ if [ -s $REAPER_PID_FILE ] then reaper_pid=`cat $REAPER_PID_FILE 2>/dev/null` ps -p $reaper_pid &> /dev/null if [ $? -ne 0 ] then start_reaper & echo $! > $REAPER_PID_FILE fi else start_reaper & echo $! > $REAPER_PID_FILE fi  }  start_reaper(){ while [ -f $PID_FILE ] do sleep $REAPER_SLEEP touched=`stat --printf %W $REAPER_PID_FILE 2>/dev/null` now=`date +%s 2>/dev/null` let idle=$now-$touched if [ $idle -gt $REAPER_SLEEP ] then echo `date`" REAPER STOPPING SERVICE AFTER BEING $idle" >> $LOG $SERVICE stop >> $LOG rm $REAPER_PID_FILE exit 0 fi done }  start_service(){ if [ -s $PID_FILE ] then return else echo `date`" STARTING $SERVICE" >> $LOG $SERVICE start &>> $LOG return fi }  wait_for_service(){ sleep 10 nc -w30 -z $IP $PORT &>/dev/null if [[ $? -ne 0 ]] then echo `date`" XINET SERVICE START ON $PORT TIMED OUT" >> $LOG fi }  transmit(){ nc -w30 $IP $PORT 2>/dev/null # netcat is awesome  }  main() { nc -z $IP $PORT &>/dev/null if [[ $? -ne 0 ]] then start_service & wait_for_service fi check_reaper &>/dev/null touch $REAPER_PID_FILE &>/dev/null transmit exit 0 }  main |
And its xinet config file looks like this:
/etc/xinetd.d/mysqld
1 2 3 4 5 6 7 8 9 10 11 12 | service mysql { disable = no type = UNLISTED socket_type = stream port = 3306 server = /etc/xinetd.init.d/mysqld user = root wait = no bind = 192.168.1.50 only_from = 192.168.1.50 } |
Leave a Reply