2014/10/24

install and config smarttools on Debian 7

install and config smarttools on Debian 7

#install
apt-get install smartmontools

#display smart information
smartctl -i /dev/sda

#fast selftest
smartctl -t short /dev/sda

#full test
smartctl -t long /dev/sda

#view the error list of selftest
smartctl -l error /dev/sda

#config smarttools
/etc/default/smartmontools
enable_smart="/dev/sda" #要檢查的硬碟
start_smartd=yes #開機後自動啟用
smartd_opts="--interval=1800" #檢查的間隔時間

/etc/smartd.conf
#只在有錯誤狀態時發 mail 通知
/dev/hdc -H -C 0 -U 0 -m admin@example.com

2014/10/23

install monitorix on debian 7 (wheezy)

install minotorix on Debian 7

apt-get install rrdtool perl libwww-perl libmailtools-perl libmime-lite-perl librrds-perl libdbi-perl libxml-simple-perl libhttp-server-simple-perl libconfig-general-perl

版本可能會變
wget http://www.monitorix.org/monitorix_3.7.0-izzy1_all.deb
dpkg -i monitorix_3.7.0-izzy1_all.deb


安裝完成後就已經啟動服務

設定檔 /etc/monitoric/monitorix.conf
可以把要監控的項目設定成 y,不想監控的設定成 n
<httpd_builtin> 底下是內建 httpd 的設定,安全起見不建議跟 apache 榜一起
  <auth> 可以設定要經過認證
      enabled = y
      htpasswd = /var/lib/monitorix/htpasswd  預設的密碼檔

產生密碼檔
cd /var/lib/monitorix
htpasswd -b -c -d htpasswd admin admin

產生第2~ 個人
htpasswd -b -d htpasswd aimwang iloveu

重新啟動服務,設定變更或者新增使用者都要重啟才生效
/etc/init.d/monitorix restart

2014/10/21

Sourceforge 的 igmpproxy 程式解析

官方位址

原始程式檔列表

lib.c 公用函式,都是用在網路位址處理
mcgroup.c 處理 join/leace Multicast group
ifvc.c 建立及提供搜尋 interface vector 功能
config.c 讀取 config 設定檔
udpsock.c 建立 udp sock
request.c 處理 igmp request
igmp.c 收/送 igmp 封包
confread.c config 子集,讀檔相關功能
igmpproxy.c igmpproxy 主流程
syslog.c 處理 log 訊息
mroute-api.c mroute api 相關程式
kern.c 不確定,看起來是打包用的
rttable.c igmpproxy 內部使用的 routing table 以及跟 kernel 增減 routing 的程式
callout.c 佇列處理
igmpproxy.h
os-linux.h
os-openbsd.h
os-freebsd.h
os-dragonfly.h
os-netbsd.h

igmpproxy 運作概說

    multicast 的訂閱是由 igmp 來進行,但是 multi 的特性類似於 broadcast,所以並不能透過 route 或 nat 轉發,所以需要在 router/gateway 上提供 igmp proxy 服務,在 downstream 收到 request 的時候,從 upstream 轉發出去,同時要去通知 kernel 收到這個 group 的 multicast 要轉發給 downstream。因為 downstream 有可能不只一個 host 要訂閱某個 group,igmpproxy 不可以在確定某 host leave 的時候就直接切斷這個 group 的 multicast routing,所以需要維護一套自己要看的 routing table,在確定 downstream 都沒有人有訂閱這個 group 的時候才能切斷。

    後面的程式解析只看 igmpproxy 的主要流程,也就是收到 igmp 封包之後開始解析,看看該程式是如何做出 igmpproxy 所需的功能。

acceptIgmp()

 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
// protocol == 0; kernel 發現新 source 的 group,所以要把他加入 routing 表
if (ip->ip_p == 0) {
    checkVIF = getIfByIx( upStreamVif );    // 根據 src 位址找出 upstream vif
    if(checkVIF == 0) {    // 不是 upstream vif,跳開
    else if(src == checkVIF->InAdr.s_addr) {    // 根本就是自己發的,跳開
    else if(!isAdressValidForIf(checkVIF, src)) { // 不在 upstream 的網段,跳開
    }
    activateRoute(dst, src);    // 啟用 routing,然後跳開
}

// 後面會根據收到的 igmp 封包決定後續動作
switch (igmp->igmp_type) {

    case IGMP_V1_MEMBERSHIP_REPORT:
    case IGMP_V2_MEMBERSHIP_REPORT:
        // 收到 igmp membership report
        acceptGroupReport(src, group, igmp->igmp_type);
        return;
    case IGMP_V2_LEAVE_GROUP:
        // 收到 igmp leave
        acceptLeaveMessage(src, group);
        return;
    case IGMP_MEMBERSHIP_QUERY:
        // 收到 igmp query,總覺得應該要回應這個 query 比較好,可是實際操作沒問題,所以就當作不需要就好
        return;
    default:
        // 其他當作不認識
        my_log(LOG_INFO, 0,
            "ignoring unknown IGMP message type %x from %s to %s",
            igmp->igmp_type, inetFmt(src, s1),
            inetFmt(dst, s2));
        return;
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if(!IN_MULTICAST( ntohl(group) )) {}    // 如果 group 不在 multicast 位址範圍內,跳開
sourceVif = getIfByAddress( src );    // 根據 src 位址找出 vif
if(sourceVif == NULL) {}    // 找不到 vif,跳開
if(sourceVif->InAdr.s_addr == src) {}    // src 就是自己,跳開
if(sourceVif->state == IF_STATE_DOWNSTREAM) {    // 是 downstream 的 vif 才處理
    if(sourceVif->allowedgroups == NULL) {    // 沒有設定白名單
        insertRoute(group, sourceVif->index);    // 加入路由後跳開
    }
    for(sn = sourceVif->allowedgroups; sn != NULL; sn = sn->next) {
        if((group & sn->subnet_mask) == sn->subnet_addr) {    // 在白名單裡面
            insertRoute(group, sourceVif->index);    // 加入路由
        }
    }
}
1
2
3
4
5
6
7
8
if(!IN_MULTICAST( ntohl(group) )) {}    // 如果 group 不在 multicast 位址範圍內,跳開
sourceVif = getIfByAddress( src );    // 根據 src 位址找出 vif
if(sourceVif == NULL) {}    // 找不到 vif,跳開
// 這次不檢查是不是自己發的!?
if(sourceVif->state == IF_STATE_DOWNSTREAM) {    // 是 downstream 的 vif 才處理
    setRouteLastMemberMode(group);    // 把 group 設定成沒有成員模式
    sendGroupSpecificMemberQuery(gvDesc);    // 送出 query,驗證是不是還有人要接收 group
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
croute = findRoute(group);    // 根據 group 位址找 route 紀錄
if(croute==NULL) {    // route 不存在
    newroute = (struct RouteTable*)malloc(sizeof(struct RouteTable));    // 建立新的 route 紀錄
    newroute->group      = group;
    newroute->upstrState = ROUTESTATE_NOTJOINED;    // 新的 route 所以設定為 notjoined
    BIT_ZERO(newroute->vifBits);    // 清空要接收的 vif 表
    croute = newroute;    // croute 後面還會用到
    if(ifx >= 0) {
        BIT_SET(newroute->vifBits, ifx);    // 設定接收的 vif
    }
    if(routing_table == NULL) {
        // routing_table 還不存在,這個 newroute 就當頭
    } else {
        // routing_table 已經存在,找適當位置插入這一筆新的
    }
} else {    // route 已經存在
    BIT_SET(croute->vifBits, ifx);    // 更新要接收的 vif 表
    if(croute->originAddr != 0) {    // 有來源在播送給這個 group 的話
        internUpdateKernelRoute(croute, 1)    // 告訴 kernel 要把這個 group 轉送
    }
}
if(croute->upstrState != ROUTESTATE_JOINED) {    // 還沒對 upstream 送出 join
    sendJoinLeaveUpstream(croute, 1);    // 送出 join report
}
1
2
3
4
5
6
7
croute = findRoute(group);    // 根據 group 尋找 route
if(croute!=NULL) {    // 有找到
    if(croute->upstrState == ROUTESTATE_JOINED && conf->fastUpstreamLeave) {    //已經送過 join 而且有設定 fast leave
        sendJoinLeaveUpstream(croute, 0);    // 送出 leave report
    }
    croute->upstrState = ROUTESTATE_CHECK_LAST_MEMBER;    // 更改 upstream 狀態
}

1
2
3
4
5
6
7
8
9
GroupVifDesc   *gvDesc = (GroupVifDesc*) argument;    // 把參數設定成 GroupVifDesc 型態
if(gvDesc->started) {    // 已經啟用
        if(!lastMemberGroupAge(gvDesc->group)) {    // 最新一次的 member 檢查沒有得到 member 的回應
                return;
} else {
        gvDesc->started = 1;    // 設定成啟動
}
sendIgmp(...);    // 送出 membership query,詢問還有誰在接收這個 group
timer_setTimer(...);    // 重設下次檢查時間

主程式流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int main( int ArgCn, char *ArgVc[] ) {
        for (int c; (c = getopt(ArgCn, ArgVc, "vdh")) != -1;) {
                // 解析輸入參數
                // d 將紀錄輸出到 stderr
                // v 紀錄 info 等級
                // vv 紀錄 debug 等級
                // h 顯示語法說明
        }
        do {    // 這個是主程式迴圈,還不是主迴圈,很有趣的寫法 :D,有什麼好處我不知道,請知道的人幫忙釋疑
                if( ! loadConfig( configFilePath ) ) {}    //讀取設定檔
                if ( !igmpProxyInit() ) {}    // 系統初始化
                igmpProxyRun();    // 真正的主迴圈,開始收 igmp 封包跑 igmpproxy 流程
                igmpProxyCleanUp();    // 清除
        } while ( false );
}

設定檔

1
2
3
4
5
6
7
8
9
int loadConfig(char *configFile) {
        while ( token != NULL ) {
                if(strcmp("phyint", token)==0) {
                        tmpPtr = parsePhyintToken();    // 解析 phy
                }
                else if(strcmp("quickleave", token)==0) {}    // quickleave 標籤
                ... ...
        }
}

PPP 連線的問題

先來看原本的程式 (ifvc.c)
 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
            // Get the interface adress...
            IfDescEp->InAdr = ((struct sockaddr_in *)&IfPt->ifr_addr)->sin_addr;
            addr = IfDescEp->InAdr.s_addr;

            memcpy( IfReq.ifr_name, IfDescEp->Name, sizeof( IfReq.ifr_name ) );
            IfReq.ifr_addr.sa_family = AF_INET;
            ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr = addr;

            // Get the subnet mask...
            if (ioctl(Sock, SIOCGIFNETMASK, &IfReq ) < 0)
                my_log(LOG_ERR, errno, "ioctl SIOCGIFNETMASK for %s", IfReq.ifr_name);
            mask = ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr;
            subnet = addr & mask;

            /* get if flags
            **
            ** typical flags:
            ** lo    0x0049 -> Running, Loopback, Up
            ** ethx  0x1043 -> Multicast, Running, Broadcast, Up
            ** ipppx 0x0091 -> NoArp, PointToPoint, Up 
            ** grex  0x00C1 -> NoArp, Running, Up
            ** ipipx 0x00C1 -> NoArp, Running, Up
            */
            if ( ioctl( Sock, SIOCGIFFLAGS, &IfReq ) < 0 )
                my_log( LOG_ERR, errno, "ioctl SIOCGIFFLAGS" );

            IfDescEp->Flags = IfReq.ifr_flags;

            // Insert the verified subnet as an allowed net...
            IfDescEp->allowednets = (struct SubnetList *)malloc(sizeof(struct SubnetList));
            if(IfDescEp->allowednets == NULL) my_log(LOG_ERR, 0, "Out of memory !");
            
            // Create the network address for the IF..
            IfDescEp->allowednets->next = NULL;
            IfDescEp->allowednets->subnet_mask = mask;
            IfDescEp->allowednets->subnet_addr = subnet;

因為 igmp proxy 是利用 interface 的 ip/netmask 來判斷封包是否過濾,一般的 IPoE 使用上都沒什麼問題,但是遇到 PPP 連線時,對方傳過來的 mcast 封包 source 會是 P-t-P dstaddr,而不是我們這邊的 IP。這個情形會導致 igmpproxy 把 mcast 都丟掉,所以要在 line 28 那邊作一些小改變。

 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
            // Get the interface adress...
            IfDescEp->InAdr = ((struct sockaddr_in *)&IfPt->ifr_addr)->sin_addr;
            addr = IfDescEp->InAdr.s_addr;

            memcpy( IfReq.ifr_name, IfDescEp->Name, sizeof( IfReq.ifr_name ) );
            IfReq.ifr_addr.sa_family = AF_INET;
            ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr = addr;

            // Get the subnet mask...
            if (ioctl(Sock, SIOCGIFNETMASK, &IfReq ) < 0)
                my_log(LOG_ERR, errno, "ioctl SIOCGIFNETMASK for %s", IfReq.ifr_name);
            mask = ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr;
            subnet = addr & mask;

            /* get if flags
            **
            ** typical flags:
            ** lo    0x0049 -> Running, Loopback, Up
            ** ethx  0x1043 -> Multicast, Running, Broadcast, Up
            ** ipppx 0x0091 -> NoArp, PointToPoint, Up 
            ** grex  0x00C1 -> NoArp, Running, Up
            ** ipipx 0x00C1 -> NoArp, Running, Up
            */
            if ( ioctl( Sock, SIOCGIFFLAGS, &IfReq ) < 0 )
                my_log( LOG_ERR, errno, "ioctl SIOCGIFFLAGS" );

            IfDescEp->Flags = IfReq.ifr_flags;

     // aimwang: when pppx get dstaddr for use
            if (0x10d1 == IfDescEp->Flags)
            {
  if ( ioctl( Sock, SIOCGIFDSTADDR, &IfReq ) < 0 )
      my_log(LOG_ERR, errno, "ioctl SIOCGIFDSTADDR for %s", IfReq.ifr_name);
  addr = ((struct sockaddr_in *)&IfReq.ifr_dstaddr)->sin_addr.s_addr;
  subnet = addr & mask;
            }

            // Insert the verified subnet as an allowed net...
            IfDescEp->allowednets = (struct SubnetList *)malloc(sizeof(struct SubnetList));
            if(IfDescEp->allowednets == NULL) my_log(LOG_ERR, 0, "Out of memory !");
            
            // Create the network address for the IF..
            IfDescEp->allowednets->next = NULL;
            IfDescEp->allowednets->subnet_mask = mask;
            IfDescEp->allowednets->subnet_addr = subnet;

Line 29-36 是根據 IfDescEp->Flags == 0x10d1 時,addr 改取 dstaddr 而不是 srcaddr,然後重算 subnet,再交給後面加入 allowednets。這樣就可以讓 PPPoE 也能享受 igmp proxy,提供用戶極致的影音服務。

2014/10/17

Install Redmine 2.5.2 on Debian 7.6 with gitolite 0.7.7

Debian 7.6 + Redmine 2.5.2 + 0.7.7

安裝 Debian 7.6 基本系統(只選標準系統工具跟 ssh)

IP: 172.20.3.12/16
GW: 172.20.254.254
DNS: 8.8.4.4 8.8.8.8
root:111111
octtel:111111

需要更改 IP 時:
nano /etc/network/interfaces

安裝相依套件

aptitude update && aptitude -y install sudo ssh bzip2 zip unzip apache2 libapache2-mod-passenger mysql-server libmysqlclient-dev ruby ruby-dev git git-core gitolite libmagickcore-dev libmagickwand-dev libicu-dev imagemagick

New password for the MySQL "root" user: 回答 "111111"
Repeat password for the MySQL "root" user: 回答 "111111"

建立 redmine, gitolite 的使用者

adduser --system --shell /bin/bash --gecos 'Git Administrator' --group --disabled-password --home /opt/gitolite git
adduser --system --shell /bin/bash --gecos 'Redmine Administrator' --group --disabled-password --home /opt/redmine redmine

產生 redmine 的憑證

su redmine
ssh-keygen -t rsa -N '' -f ~/.ssh/redmine_gitolite_admin_id_rsa
(會產生 /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa 跟 /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub)
ln -s /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa /opt/redmine/.ssh/id_rsa
ln -s /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub /opt/redmine/.ssh/id_rsa.pub
exit

設定 gitolite

dpkg-reconfigure gitolite

System username for gitolite: 回答 "git"
Repository path: 回答 "/opt/gitolite"
Administrator's SSH key: 回答 "/opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub"

安裝 redmine

sudo su - redmine
cd ~
wget http://www.redmine.org/releases/redmine-2.5.2.tar.gz
tar zxf redmine-2.5.2.tar.gz
mv redmine-2.5.2/* .
rm -Rf redmine-2.5.2

建立 database 和 user

mysql -u root -p -v -e "CREATE DATABASE redmine CHARACTER SET utf8;
CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'redmine';
GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';"
Enter password: 輸入 "111111"

組態資料跟mail

cd /opt/redmine/config
cp database.yml.example database.yml
cp configuration.yml.example configuration.yml
nano database.yml
內容改成如下 (其他項目用 # 標示)
production:
 adapter: mysql2
 database: redmine
 host: localhost
 username: root
 password: "111111"
 encoding: utf8


建立 plugin 資料夾

cd ~
mkdir public/plugin_assets

修改 sudoer (為了安全後面會限縮權限,但是這邊安裝需要開放全部)

nano /etc/sudoers
增加
redmine    ALL=(ALL)      NOPASSWD:ALL
git        ALL=(ALL)  NOPASSWD:ALL

完成 redmine

sudo gem install bundler
bundle install --without development test postgresql sqlite
rake generate_secret_token
RAILS_ENV=production rake db:migrate
RAILS_ENV=production rake redmine:load_default_data

設定 sendmail (以 gmail 為例)

修改 /opt/redmine/configuration.yml
email_delivery:
 delivery_method: :smtp
 smtp_settings:
    enable_starttls_auto: true
   address: "smtp.gmail.com"
   port: 587
    domain: "smtp.gmail.com"
    authentication: :plain
    user_name: "your_email@gmail.com"
    password: "your_password"

安裝 gitolite

cd ~/plugins
git clone https://github.com/jbox-web/redmine_bootstrap_kit.git
git clone https://github.com/jbox-web/redmine_git_hosting.git
cd redmine_git_hosting
git checkout v0.7.7
ln -s /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa /opt/redmine/plugins/redmine_git_hosting/ssh_keys/redmine_gitolite_admin_id_rsa
ln -s /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub /opt/redmine/plugins/redmine_git_hosting/ssh_keys/redmine_gitolite_admin_id_rsa.pub

設定 Key

sudo su - git
sed -i 's/$GL_GITCONFIG_KEYS = ""/$GL_GITCONFIG_KEYS = ".*"/g' /opt/gitolite/.gitolite.rc
exit

設定自動初始化 repository

cd ~
git clone git@localhost:gitolite-admin.git
cd gitolite-admin/
echo "repo    @all
RW+    = admin" >> conf/gitolite.conf
git commit -m 'Automatic Repository Initialization' conf/gitolite.conf
git push
cd ~
rm -rf gitolite-admin

安裝 plugin 到 redmine

cd ~
bundle install --without development
RAILS_ENV=production rake redmine:plugins:migrate

修改 sudoer

nano /etc/sudoers
增加
redmine    ALL=(git)      NOPASSWD:ALL
git        ALL=(redmine)  NOPASSWD:ALL

修改 Apache mod_passenger 設定

su root
cd /var/www
ln -s /opt/redmine/public redmine
echo "RailsBaseURI /redmine
PassengerUserSwitching on
PassengerUser redmine
PassengerGroup redmine" > /etc/apache2/sites-available/redmine
a2ensite redmine
service apache2 reload

啟用 https

a2ensite default-ssl
a2enmod ssl
service apache2 restart

其他

git 客戶端無法 clone 未被公證單位認證的憑證提供的 .git,可以執行下列指令不檢查憑證
git config --global http.sslverify false