вторник, 15 сентября 2009 г.

Скрипт управления скоростью скачивания/отдачи rTorrent на роутере ASUS wl-500gP в зависимости от трафика от других компьютеров за роутером

В общем-то, я планировал выкладывать сюда заметки о том, как я настраивал роутер с самого начала, но что-то нет времени или желания, или того и другого вместе. Поэтому описание, того, что сделано на настоящий момент оставлю на потом. А сейчас опишу последнее своё творение. К тому же это самый большой и сложный скрипт написанный мной на shell на данный момент времени (все таки, по долгу службы я занимаюсь 1С, а не роутерами, линуксом и программированием на shell ;-)).
Значит так. Предполагается, что роутер (ASUS wl-500gP или совместимый) уже прошит олеговской прошивкой и проведена начальная настройка до rTorrent. Кроме того, должны стоять следующие утилиты: tail, head, awk, wget. Если чего из этого нет, то отправляемся на wl500g.info и настраиваем все, что нужно. Кроме того, понадобится утилита sudo, для того, чтобы скрипт смог работать под пользователем p2p, от имени которого стартует rTorrent

Sudo
У меня rTorrent работает от имени пользователя p2p. Поскольку этот пользователь бесправный, то он не сможет запускать этот скрипт. Точнее скрипт он запустит, а вот обратиться к iptables - не сможет. Для того чтобы обойти эту неприятность и ставится sudo. Ставим:

ipkg install sudo

После установки, находим конфиг sudo (/opt/etc/sudoers) и правим его. Самый ПРАВИЛЬНЫЙ способ править конфиг sudo - это использовать утилиту visudo. Дело в том, что перед записью исправленного конфига visudo в начале проверяет его на корректность и если все нормально, то сохраняет его. А если находит ошибки - то не сохраняет. Итак, набираем в консоле:

visudo 

Вот мой конфиг sudo. Строки которые я исправлял, отмечены красным:

# sudoers file.
#
# This file MUST be edited with the 'visudo' command as root.
# Failure to use 'visudo' may result in syntax or file permission errors
# that prevent sudo from running.
#
# See the sudoers man page for the details on how to write a sudoers file.
#

# Host alias specification

# User alias specification

# Cmnd alias specification

# Defaults specification

# Runas alias specification

# User privilege specification
# root ALL=(ALL) ALL
admin ALL=(ALL) ALL

# Uncomment to allow people in group wheel to run all commands
# %wheel ALL=(ALL) ALL
# Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL

# Samples
# %users ALL=/sbin/mount /cdrom,/sbin/umount /cdrom
# %users localhost=/sbin/shutdown -h now

p2p IGORROUTER = (admin) NOPASSWD: /usr/sbin/iptables

Defaults:p2p runas_default = admin
Defaults:admin runas_default = admin

Пользователя root я закомментарил, и добавил пользователя admin, аналогичного root.
Указал, что пользователь p2p может запускать на моем роутере (IGORROUTER) утилиту iptables
без пароля.
Пользователем по умолчанию для p2p назначен admin. Также и для пользователя admin, назначен пользователь admin, иначе sudo пытается использовать пользователя root, которого нет. Узнать имя роутера можно при помощи hostname, например:


hostname
IGORROUTER

Кратко, команды, которые я использовал при правке конфига в visudo:
- клавиша <Insert> - перейти в режим редактирования;
 - клавиша <Esc> - выйти из режима редактирования;
- ":w" - сохранить конфиг;
- ":q" - выйти из visudo;
- ":wq" - сохранить конфиг и выйти из visudo.


Скрипт rt-shaper.sh

Скрипт rt-shaper.sh предназначен для автоматической регилировки скачки/отдачи bittorrent-клиентом rTorrent в зависимости от наличия и количества трафика проходящего через роутер. Другими словами, если форвардного(проходящего через роутер на клиентов в вашей сети) трафика через роутер нет, то rTorrent, установленный на роутер, не ограничивается. Если же форвардный трафик есть, то скорость скачки/отдачи ограничивается. Более того, и скорость скачки, и скорость отдачи изменяется в заданном интервале в зависимости от количества форвардного трафика.

Я рекомендую скрипт ставить в отдельный каталог. У меня он стоит в /opt/etc/rt-shaper

На данный каталог нужно дать права пользователю p2p. У меня в качестве группы для каталога указана p2p и группе даны права на чтение(r), запись(w) и выполнение(x).

Далее, нужно место, где будут храниться значения, которые должны сохраняться между запусками скрипта. Поскольку, при каждом запуске скрипта, туда что-то пишется, лучше это место завести не на флешке, а на винте. У меня винт смонтирован в /home, а на нём я создал подкаталог var и ещё подкаталог rtorrent. Таким образом, путь к временным файлам имеет такой вид: /home/var/rtorrent

На него тоже нужно дать права на запись пользователю p2p.

Скрипт /opt/etc/rt-shaper/rt-shaper.sh

#!/bin/sh
# Скрипт предназначения для автоматического ограничения rTorrent работающего на роутере
# при обнаружении транзитного(форвардного)  трафика через роутер.
# Скорость(скорости upload'а и download'а) может варьироваться в заданном пределе в зависимости от 
# количества и поведения транзитного трафика.
# С точки зрения rTorrent'а скрипт действует писсимистично, т.е.:
#   при присутсвии транзитного трафика rTorrent ограничевается быстрее, 
#   чем это ограничение снимается при уменьшении транзитного трафика или его полном исчезновении
#
# Автор: Игорь (igor77777 on wl500g.info)
#
PATH=/opt/bin:/opt/sbin:/usr/bin:/sbin:/bin:/usr/sbin
RTSHAPER=/opt/etc/rt-shaper
VARCAT=/home/var/rtorrent
LOGFILE=/opt/var/log/rt-shaper.log
PIDUPWGET=$VARCAT/rtshaperupwget.pid
PIDDLWGET=$VARCAT/rtshaperdlwget.pid

# Если есть конфигурационный файл, то подключаем его
min_uprate=10;  # минимальный upload rate rTorrent в килобайтах. Скорость будет уменьшаться до него, но не ниже
min_dlrate=10;  # минимальный download rate rTorrent в килобайтах. Скорость будет уменьшаться до него, но не ниже 
step_rate=10;   # шаг изменения rate в килобайтах
max_uprate=50;  # максимальный upload rate rTorrent в килобайтах. При появлении форвардного трафика будет выставлен именно он. При дальнейшем росте форвардного трафика rate будет уменьшаться вплоть для min_uprate
max_dlrate=50;  # максимальный download rate rTorrent в килобайтах. При появлении форвардного трафика будет выставлен именно он. При дальнейшем росте форвардного трафика rate будет уменьшаться вплоть для min_dlrate
max_count_change_rate=5;  # максимально колличество пропущенных изменений rate. После этого колличества rate будет изменен по любому
interval_rate=5;    # частота точнее интервал, в секундах, с которой дергается скрипт. Должен совпадать с тем, что указано в конфиге rTorrent
enable_log=0;  # признак ведения лога 0-не вести лог. 1-вести лог, 2-вести подробный лог, 3-лог с промежуточными переменными
if [ -f $RTSHAPER/rt-shaper.conf ]; then
. $RTSHAPER/rt-shaper.conf
fi

# первым делом проверяем, есть ли процессы wget порожденные скриптом
# если есть, то скрипт дальше не выполняется
if [ -e $PIDUPWGET -a -e $PIDDLWGET ]; then
# файлы c pid'ами существуют
  hasexit=0
  kill -0 $(cat $PIDDLWGET) >/dev/null 2>&1
  res=$?
  [ $enable_log -gt 2 ] && echo "Search dlrate wget pid=$(cat $PIDDLWGET)  res=$res. At " `date` >> $LOGFILE
  if [ $res -eq 0 ]; then
    hasexit=`expr $hasexit + 1`
  fi
  kill -0 $(cat $PIDUPWGET) >/dev/null 2>&1
  res=$?
  [ $enable_log -gt 2 ] && echo "Search uprate wget pid=$(cat $PIDUPWGET)  res=$res. At " `date` >> $LOGFILE
  if [ $res -eq 0 ]; then
    hasexit=`expr $hasexit + 1`
  fi
  if [ $hasexit -gt 0 ]; then
    [ $enable_log -gt 0 ] && echo "Find child wget. Script exit. At " `date` >> $LOGFILE
    exit 0
  fi
fi


# установка  upload rate rTorrent
setuprate(){
  _new_rate=$curr_uprate;
  if [ $_new_rate -ne 0 ]; then 
    _new_rate="$_new_rate"k;
  fi
  
  _post_string="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
<methodCall>\
 <methodName>set_upload_rate</methodName>\
 <params>\
  <param>\
   <value>\
    <string>$_new_rate</string>\
   </value>\
  </param>\
 </params>\
</methodCall>\
";
  
  /opt/bin/wget -q --post-data="$_post_string" -O - http://127.0.0.1:80/RPC2 > /dev/null&
  pid=$!
  [ $enable_log -gt 2 ] && echo "Set uprate wget pid=$pid  _post_string=$_post_string. At " `date` >> $LOGFILE
  echo $pid > $PIDUPWGET
  [ $enable_log -gt 0 ] && echo "set_upload_rate  $_new_rate.  At " `date` >> $LOGFILE
}

# установка  download rate rTorrent
setdlrate(){
  _new_rate=$curr_dlrate;
  if [ $_new_rate -ne 0 ]; then 
    _new_rate="$_new_rate"k;
  fi
  
  _post_string="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
<methodCall>\
 <methodName>set_download_rate</methodName>\
 <params>\
  <param>\
   <value>\
    <string>$_new_rate</string>\
   </value>\
  </param>\
 </params>\
</methodCall>\
";

  /opt/bin/wget -q --post-data="$_post_string" -O - http://127.0.0.1:80/RPC2 > /dev/null&
  pid=$!
  [ $enable_log -gt 2 ] && echo "Set dlrate wget pid=$pid  _post_string=$_post_string. At " `date` >> $LOGFILE
  echo $pid > $PIDDLWGET
  [ $enable_log -gt 0 ] && echo "set_download_rate  $_new_rate.  At " `date` >> $LOGFILE
}

# Если файл с текущими значениями есть, то просто подключаем его
if [ -f $VARCAT/curr_state ]; then
. $VARCAT/curr_state
else
  last=0;     # последнее показание форвардного трафика
  last_delta=0; # последний объем трафика прошедший за измеряемый промежуток
  curr_uprate=0; # текущий upload rate
  curr_dlrate=0; # текущий download rate
  curr_state=0; # текущее состояние, если 0, то rtorrent не ограничивается
  count_change_rate_ups=0; # счетчик пропущенных изменений rate, по причине маленького отличия last_delta. После определенного колличества, все равно происходит изменение rate
fi

old_last=$last;
old_last_delta=$last_delta;
old_curr_uprate=$curr_uprate;
old_curr_dlrate=$curr_dlrate;
old_curr_state=$curr_state;
old_count_chahge_rate_ups=$count_change_rate_ups;

fwd=`sudo iptables -t filter -L FORWARD -v -x -n | tail -n 5 | head -n 1 | awk '{print($2)}'`
# обрабатываем переполнение
if [ $fwd -lt $last ]; then
  last=0;
  [ $enable_log -gt 1 ] && echo "Overflow counter forward byte. At " `date` >> $LOGFILE
fi

delta=0;
change_uprate=0;
change_dlrate=0;

[ $enable_log -gt 2 ] && echo "fwd=$fdw  last=$last. At " `date` >> $LOGFILE
if [ $fwd -eq $last ]; then
# форвардного трафика не было
  [ $enable_log -gt 2 ] && echo "curr_state=$curr_state. At " `date` >> $LOGFILE
  if [ $curr_state -gt 0 ]; then
    # если стоит ограничение, то снимаем его
    # но вначале определенное количество раз пропускаем
    [ $enable_log -gt 2 ] && echo "count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
    if [ $count_change_rate_ups -ge $max_count_change_rate ]; then
      curr_state=0;
      last_delta=0;
      curr_uprate=0;
      curr_dlrate=0;
      count_change_rate_ups=0;
      change_uprate=1;
      change_dlrate=1;
    else 
      [ $enable_log -gt 2 ] && echo "count_change_rate_ups++     =$count_change_rate_ups. At " `date` >> $LOGFILE
      count_change_rate_ups=`expr $count_change_rate_ups + 1`;
    fi
  fi
else
  delta=`expr $fwd - $last`;
  last=$fwd;
# если ограничения нет и появился форвардный трафик
  [ $enable_log -gt 2 ] && echo "curr_state=$curr_state delta=$delta. At " `date` >> $LOGFILE
  if [ $curr_state -eq 0 ]; then
    curr_state=1;
    curr_uprate=`expr $max_uprate - $step_rate`;
    curr_dlrate=`expr $max_dlrate - $step_rate`;
    count_change_rate_ups=0;
    change_uprate=1;
    change_dlrate=1;
  else
# а вот если форвардный трафик уже был, то смотрим, прибавляется он или уменьшается
# и в зависимости от этого дергаем скорость rtorrenta
# кроме того, проверяется размер этого изменения, и изменяем, только если он больше определенной велечины
    [ $enable_log -gt 2 ] && echo "delta=$delta  last_delta=$last_delta. At " `date` >> $LOGFILE
    if [ $delta -ge $last_delta ]; then
      # трафик прибавился
      uptrafic=`expr $delta - $last_delta`;
      # если прирост трафика больше половины шага уменьшения скорости для rTorrent
      [ $enable_log -gt 2 ] && echo "uptrafic=$uptrafic  count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
      if [ $uptrafic -ge `expr $step_rate '*' 1024 '*' $interval_rate / 2` -o $count_change_rate_ups -ge $max_count_change_rate ]; then
        # если текущая скорость uprate больше минимальной, то уменьшаем скорость ещё
        if [ $curr_uprate -gt $min_uprate ]; then
          curr_uprate=`expr $curr_uprate - $step_rate`;
          change_uprate=1;
        fi
        # если текущая скорость dlrate больше минимальной, то уменьшаем скорость ещё
        if [ $curr_dlrate -gt $min_dlrate ]; then
          curr_dlrate=`expr $curr_dlrate - $step_rate`;
          change_dlrate=1;
        fi
        if [ $change_uprate -eq 1 -o $change_dlrate -eq 1 ]; then
          curr_state=`expr $curr_state + 1`;
        fi
        count_change_rate_ups=0;
        [ $enable_log -gt 2 ] && echo "curr_uprate=$curr_uprate  curr_dlrate=$curr_dlrate  curr_state=$curr_state  count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
      else
        count_change_rate_ups=`expr $count_change_rate_ups + 1`;
        [ $enable_log -gt 2 ] && echo "count_change_rate_ups++     =$count_change_rate_ups. At " `date` >> $LOGFILE
      fi
    else
      # проходящий трафик уменьшился
      dltrafic=`expr $last_delta - $delta`;
      [ $enable_log -gt 2 ] && echo "dltrafic=$dltrafic  count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
      # если объем трафика уменьшился на щаг  скорости для rTorrent
      if [ $dltrafic -ge `expr $step_rate '*' 1024 '*' $interval_rate` -o $count_change_rate_ups -ge $max_count_change_rate ]; then
        # если текущая скорость uprate больше минимальной, то уменьшаем скорость ещё
        if [ $curr_uprate -lt $max_uprate ]; then
          curr_uprate=`expr $curr_uprate + $step_rate`;
          change_uprate=1;
        fi
        # если текущая скорость dlrate больше минимальной, то уменьшаем скорость ещё
        if [ $curr_dlrate -lt $max_dlrate ]; then
          curr_dlrate=`expr $curr_dlrate + $step_rate`;
          change_dlrate=1;
        fi
        if [ $change_uprate -eq 1 -o $change_dlrate -eq 1 ]; then
          curr_state=`expr $curr_state - 1`;
          # при уменьшении трафика, не даем curr_state уйти в 0
          if [ $curr_state -eq 0 ]; then
            curr_state=1;
          fi
        fi
        count_change_rate_ups=0;
        [ $enable_log -gt 2 ] && echo "curr_uprate=$curr_uprate  curr_dlrate=$curr_dlrate  curr_state=$curr_state  count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
      else
        count_change_rate_ups=`expr $count_change_rate_ups + 1`;
        [ $enable_log -gt 2 ] && echo "count_change_rate_ups++     =$count_change_rate_ups. At " `date` >> $LOGFILE
      fi
    fi
  fi
fi

[ $enable_log -gt 1 ] && echo "Previous forward trafic $last_delta, now forward trafic $delta, current state $curr_state, current upload rate $curr_uprate, current download rate $curr_dlrate. At " `date` >> $LOGFILE
# если необходимо изменение rate, то вызываем соответствующие функции
[ $change_uprate -eq 1 ] &&  setuprate
[ $change_dlrate -eq 1 ] && setdlrate

last=$fwd;
last_delta=$delta;

if [ $old_last -ne $last -o $old_last_delta -ne $last_delta -o $old_curr_uprate -ne $curr_uprate -o $old_curr_dlrate -ne $curr_dlrate -o $old_curr_state -ne $curr_state -o $old_count_chahge_rate_ups -ne $count_change_rate_ups ]; then
echo -e "last=$last\nlast_delta=$last_delta\ncurr_uprate=$curr_uprate\ncurr_dlrate=$curr_dlrate\ncurr_state=$curr_state\ncount_change_rate_ups=$count_change_rate_ups\n" > $VARCAT/curr_state;

[ $enable_log -gt 2 ] && echo "Exit script.   last=$last  last_delta=$last_delta  curr_uprate=$curr_uprate  curr_dlrate=$curr_dlrate  curr_state=$curr_state  count_change_rate_ups=$count_change_rate_ups. At " `date` >> $LOGFILE
else

[ $enable_log -gt 2 ] && echo "Exit script.   No change temperary varables. $VARCAT/curr_state not save. At " `date` >> $LOGFILE
fi
exit 0
 
Строчки, которые нужно поменять под себя я выделил красным. 
RTSHAPER= - каталог, в котором лежит сам скрипт
VARCAT= - каталог временных файлов
LOGFILE= - логфайл


В каталог /opt/etc/rt-shaper можно положить файл с настройками, которые будут использоваться в работе скрипта. Файл rt-shaper.conf



min_uprate=50; # минимальный upload rate rTorrent в килобайтах. Скорость будет уменьшаться до него, но не ниже
min_dlrate=10; # минимальный download rate rTorrent в килобайтах. Скорость будет уменьшаться до него, но не ниже
step_rate=10; # шаг изменения rate в килобайтах
max_uprate=70; # максимальный upload rate rTorrent в килобайтах. При появлении форвардного трафика будет выставлен именно он. При дальнейшем росте форвардного трафика rate будет уменьшаться вплоть для min_uprate
max_dlrate=50; # максимальный download rate rTorrent в килобайтах. При появлении форвардного трафика будет выставлен именно он. При дальнейшем росте форвардного трафика rate будет уменьшаться вплоть для min_dlrate
max_count_change_rate=5; # максимально количество пропущенных изменений rate. После этого количества rate будет изменен по любому
interval_rate=7; # частота точнее интервал, в секундах, с которой дергается скрипт. Должен совпадать с тем, что указано в конфиге rTorrent
enable_log=0; # признак ведения лога 0-не вести лог. 1-вести лог, 2-вести подробный лог, 3-лог с промежуточными переменными

Можно конфиг и не ложить, а задать все необходимые значения прямо в скрипте.

Параметры: min_uprate, min_dlrate, max_uprate, max_dlrate, step_rate - задаются в килобайтах. Т.е. сейчас, при наличии форвардного трафика скорость скачки будет гулять в интервале между 10 килобайтами и 50 килобайтами, а скорость отдачи между 50 килобайтами и 70 килобайтами с шагом в 10 килобайтами.

Параметр: interval_rate - должен совпадать с тем значением, которое будет указано для шедулера rTorrent, который будет запускать скрипт.

Подключение скрипта к rTorrent

Для подключения скрипта к rTorrent, нужно добавить одну строчку в конфиг rTorrent.

У меня конфиг rTorrent лежит в /opt/etc/rtorrent.conf

Добавляем туда строку:



schedule = rtshaper,15,7,execute=/opt/etc/rt-shaper/rt-shaper.sh


Здесь: 15 - задержка, в секундах, после которой rTorrent начнет запускать скрипт на выполнение. 7 - интервал, в секундах, с которым rTorrent будет запускать скрипт, должен совпадать с параметром interval_rate из rt-shaper.conf.

   
Вот собственно и все. Надеюсь, что я ни чего не пропустил.