ターミナルに出力する文字に色をつけるということを試してみた。

LinuxであったりWindowsであったり他にも色々あるけれど、どの程度互換性があるのか分からないけど、とりあえずCentOS5.4とMacOSX10.6で動作することは確認してる。

Pythonで書くとこんな感じ。

print '\033[32mAAAA\033[0m'

AAAAが緑色で表示される。

\033が、ASCIIコードでESCを表していて、[32mがANSIカラーで緑色を表してる。
[0mはリセットを表しており、[32と[0mで文字を囲むことで、その囲まれた部分だけに色がつき、[0m以降に書く文字には色がつかなくなる。

 

ローカル環境にテスト用の IRC サーバーを立てたメモ。

OSは CentOS 5.4

インストール

ソースからインストールする。

今回落としてきたのは、こちら
最終更新日時が 31-May-2005 21:42 のやつ。

解凍したら configure する。

configure が終わったら i686-pc-linux-gnu に移動して make する。(そう指示される)

$ wget http://www.ircnet.jp/dist/server/jp-patch/irc2.11.1p1.tgz
$ tar zxvf irc2.11.1p1.tgz
$ cd irc2.11.1p1
$ ./configure --prefix=~/opt/irc2.11.1p1
$ cd i686-pc-linux-gnu/
$ make all
$ make install

自分の環境では何事もなくすんなりいった。

設定

設定ファイルを作成する。

インストール先に etc ディレクトリがあるので、そこに ircd.conf を作成する。

今回はローカル用サーバなので、一番簡単な設定で済ます。

設定ファイルの書き方は、同じインストール先の etc ディレクトリにある ircd.conf.example を参考。

$ cd ~/opt/irc2.11.1p1/etc
$ vi ircd.conf
M:local.host:192.168.11.30:Tokyo:6667:392A
A:HOGE:HOGE<hoge@hoge.com>:Client Server::IRCnet:
P::::6667::
Y:10:90::100:512000:10.2:32.2:
I:::::10::

実行

マシンを起動するときに自動起動・・・とかは考えない。

直接コマンドを叩いて起動する。

$ cd ~/opt/irc2.11.1p1/sbin
$ ./ircd

TCPポート 6667 の開放を忘れずに。

 

pthread_key 周辺の使い方を勉強。
スレッドごとにメモリ領域を確保する仕組み。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
 
static pthread_key_t g_key;
 
// スレッド単位で保持するデータ
struct MyData{
    int count;
    char data;
};
 
static void* thread_call( void *arg )
{
    pthread_t self_id = pthread_self();
    printf( "self_id:%u\n", self_id );
 
    // データがすでに存在するか調査
    struct MyData *p;
    if( pthread_getspecific( g_key ) ){
        printf( "self_id:%u Already exists.", self_id );
        return NULL;
    }
 
    // データ作成
    if( !(p = (struct MyData *)calloc( 1, sizeof(*p) )) ){
        perror( "calloc()" );
        return NULL;
    }
    pthread_setspecific( g_key, p );
    p->count = 0;
    p->data = 'A';
 
    // 本当にスレッド単位で保持されているかテスト
    int i;
    for( i = 0; i < 26; ++i ){
        p = (struct MyData *)(pthread_getspecific( g_key ));
        printf( "self_id:%u id:%u data:%c\n", self_id, p->count++, p->data++ );
        usleep(1000);
    }
    p = (struct MyData *)(pthread_getspecific( g_key ));
    if( p->count == 26 ){
        printf( "self_id:%u success.\n", self_id );
    }
    else{
        printf( "self_id:%u error!!\n", self_id );
    }
 
    // 終了
    free(p);
    pthread_setspecific( g_key, 0 );
}
 
int main()
{
    // pthread key を初期化
    if( pthread_key_create( &g_key, NULL ) ){
        perror( "pthread_key_create()" );
        return 0;
    }
 
    // スレッドを複数作成。
    const int TNUM = 3;
    pthread_t tid[TNUM];
    int i;
    for( i = 0; i < TNUM; ++i ){
        if( pthread_create( &tid[i], NULL, thread_call, NULL ) ){
            perror( "pthread_create()" );
            continue;
        }
        printf( "Created a thread. id:%u\n", tid[i] );
    }
 
    // 全スレッドが終了するのを待機。
    for( i = 0; i < TNUM; ++i ){
        pthread_join( tid[i], NULL );
    }
 
    // pthread key を削除
    pthread_key_delete( g_key );    
    return 0;
}

実行

$ gcc main.c -lpthread
$ ./a.out
Created a thread. id:663552
Created a thread. id:2625536
Created a thread. id:3162112
self_id:663552
self_id:2625536
self_id:3162112
self_id:663552 id:0 data:A
self_id:2625536 id:0 data:A
self_id:3162112 id:0 data:A
self_id:663552 id:1 data:B
self_id:2625536 id:1 data:B
...
self_id:2625536 id:25 data:Z
self_id:663552 success.
self_id:3162112 id:25 data:Z
self_id:2625536 success.
self_id:3162112 success.
 

send と recv の使い方と送信バッファ溢れについて。

manpage

Manpage of SEND
Manpage of RECV

SEND

non-blocking の場合。
send が失敗して外部変数 errno に EINTR が設定されている場合は、再度送信を試みる必要がある。
データをソケットの送信バッファに入れることが出来ない場合、外部変数 errno に EAGAIN か EWOULDBLOCK が設定される。
いつデータが送信できるようになるかを知るために select などのシステムコールを使うことが出来る。(後述:送信バッファ溢れ)

以下、ソースコードから抜粋した send のサンプル。

int res = 0;
char* p = buffer; // 送るデータ
size_t len = buffer_size; // 送るデータのサイズ
while( len > 0 ){
    while( true ){
        res = send( socket, p, len, MSG_DONTWAIT ); // 一度に全部送れるとは限らない
        if( errno != EINTR ) break; // システム割り込みチェック
    }
    if( res < 0 ){
        if( errno == EAGAIN ||
            errno == EWOULDBLOCK ){
            // 書き込めない状態。それなりの対応が必要(後述:送信バッファ溢れ)
        }
        else{
            // なんらかのエラー(コネクション切断とか)
        }
    }
    len -= res;
    p += res;
}

RECV

non-blocking の場合。
操作が停止するような場合、外部変数 errno に EAGAIN か EWOULDBLOCK が設定される。

以下、ソースコードから抜粋した recv のサンプル。

int res = 0;
while( true ){
    res = recv( socket, buffer, buffer_size, MSG_DONTWAIT );
    if( errno != EINTR ) break;
}
if( res == 0 ){
    // EOF
}
 
if( res < 0 ){
    if( errno == EAGAIN ||
        errno == EWOULDBLOCK ){
        // not ready yet.
    }
    else{
        // error.
    }
}

送信バッファ溢れ

データ送信時に送信バッファが溢れてしまった場合、書き込み可能なタイミングを見計らって、溢れたデータを再送信してあげる必要がある。
送信バッファが溢れたとき、send 関数は失敗し、errno に EAGAIN, EWOULDBLOCK を格納する。
対策として例えば、送信データをキューなり可変長バッファなりで一時的に保持し、select などで書き込み可能になるタイミングを監視して、再送信する方法がある。

サンプル:sendbuffer-overflow
事前に libevent をインストールしておく必要あり。
動作環境 Linux のみ。(送信バッファと受信バッファの数値を linux/sockios.h 使って確認してる為)
1.サーバは受信時に3秒間待機する。
2.クライアントはサーバへデータを送信しまくり、EAGAIN or EWOULDBLOCK を発生させる。
3.エラー発生後、クライアントは書き込み可能イベントを監視し、検知したらデータを再送信する。
2ー3の動作を繰り返し、サーバに規定のデータ(0 – 499 までの連番)が全部届いたら成功。

インストール

$ git clone http://github.com/utahta/sendbuffer-overflow.git
$ cd sendbuffer-overflow
$ ./configure --prefix=/path/to/sendbuffer-overflow --with-libevent=/path/to/libevent
$ make
$ make install

実行

コマンドプロンプトをふたつ立ち上げ、1つめにサーバを起動します。

$ /path/to/sendbuffer-overflow/bin/sbt_server

2つめにクライアントを起動します。

$ /path/to/sendbuffer-overflow/bin/sbt_client

手元では、以下のような結果になりました。

$ /path/to/sendbuffer-overflow/bin/sbt_server
connected. sd:7
recv buffer size:274 sd:7
waiting 3 sec.
... 省略
message received. sd:7 str:abcdefghijklnmopqrstuvwxyz:495
message received. sd:7 str:abcdefghijklnmopqrstuvwxyz:496
message received. sd:7 str:abcdefghijklnmopqrstuvwxyz:497
message received. sd:7 str:abcdefghijklnmopqrstuvwxyz:498
message received. sd:7 str:abcdefghijklnmopqrstuvwxyz:499 // 499 まで届いた
$ /path/to/sendbuffer-overflow/bin/sbt_client
send buffer size. :50436
... 省略
send buffer size:48498 res:0 sd:6
send buffer size:48772 res:0 sd:6
send buffer size:49046 res:0 sd:6
not ready yet: Resource temporarily unavailable // 書き込み失敗
send buffer size:49152 res:-10 sd:6
not ready yet: Resource temporarily unavailable
send buffer size:49152 res:-10 sd:6
... 省略
resend buffer size:88558 res:0 sd:6 // 書き込み可能になったので再送信

このサンプルでは、可変長処理をさぼってあらかじめ大きなバッファを確保してごまかしてたり。
終了処理に手抜きがあったり。(delete してなかったり)
とりあえずサンプルということで。

1月 102010
 

下書きで放置していたSNMPに関するメモ。

インストール

Net-SNMPからソースをダウンロード。

$ tar zxvf net-snmp-5.4.2.1.tar.gz
$ cd net-snmp-5.4.2.1
$ ./configure --prefix=/usr/local/net-snmp-5.4.2.1 --disable-embedded-perl --without-perl-modules
 ************** Configuration Section **************
-Press return to continue-
Default version of SNMP to use (3):
System Contact Information (@):
System Location (Unknown):
Location to write logfile (/var/log/snmpd.log):
Location to write persistent information (/var/net-snmp):
$ make
# make install

必要であれば、エイリアスを設定。

$ cd /usr/local
# ln -s net-snmp-5.4.2.1 net-snmp

SNMP設定

# vi /usr/local/net-snmp/snmpd.conf
... 以下、設定内容。
com2sec hogetest 192.168.11.0/24 hogetest_com
com2sec hogetest localhost hogetest_com
 
group hogetest_grp v1 hogetest
 
view hogetest_view included .1.3.6.1.4.1.2021
 
access hogetest_grp "" any noauth exact hogetest_view none none
 
exec writetest /usr/local/net-snmp/a.sh
...

アクセスサンプル用のテキスト

# vi /usr/local/net-snmp/a.sh
/bin/echo aaa

起動

# snmpd -Lsd -Lf /var/log/snmpd.log -p /var/run/snmpd -a

-Lsd : syslogを有効にする
-Lf : snmpdの出力を送るファイル名の指定
-p : ファイル名 プロセスIDを保存するファイル名
-a : 送信元IPアドレス(NMS)をログに記録する

OIDを取得

英文字の形(OID = name)

$ ./snmpwalk -OQS -c hogetest_com -v 1 localhost .1.3.6.1.4.1.2021.8.1.extNames | grep writetest

数値の形(OID = name)

$ ./snmpwalk -OQSn -c hogetest_com -v 1 localhost .1.3.6.1.4.1.2021.8.1.extNames | grep writetest

値(exec)を取得

$ ./snmpget -c hogetest_com -v 1 localhost UCD-SNMP-MIB::extOutput.1
UCD-SNMP-MIB::extOutput.1 = STRING: aaa
$ ./snmpget -OQv -c hogetest_com -v 1 localhost UCD-SNMP-MIB::extOutput.1
aaa

参考

NET-SNMPのインストール
SNMPによるネットワークシステムの監視
SNMPによるネットワークモニタリング

1月 102010
 

下書きで放置していた cacti に関するメモ。

インストール

$ tar zxvf cacti-0.8.7a.tar.gz
$ cp cacti-0.8.7a /path/to/
$ cd /path/to
$ ln -s cacti-0.8.7a cacti
$ cd cacti/include
$ vi config.php

apache のエイリアスをきってからアクセス。
アクセス先は、http://path/to/cacti/ とか。

0.8.7b バグ

Invalid PHP_SELF Path とか表示されたら、下記のように修正すればよいらしい。
(- が修正前、+ が修正後。-, + は実際はいらない)

include/global.php 113行目

- if (!((is_file($_SERVER["SCRIPT_FILENAME"])) && (substr_count($_SERVER["SCRIPT_FILENAME"], $_SERVER["PHP_SELF"])))) { 
+ if (!((is_file($_SERVER["SCRIPT_FILENAME"])))) {

新しい定義

  1. スクリプトを書く
  2. DataInputMethod を cacti で定義。
  3. Data Templates を cacti で定義。
  4. Data Sources を cacti で定義。
  5. Graph Templates を cacti で定義。
 

TCPサーバのサンプルコードのメモ。
簡単なコードだからか、何度も書いてはどこかへやってたのでいい加減保存することに。

動作は一応 Linux CentOS5, Mac OS X 10.6 で確認済み。

server/main.cpp に socket, bind, listen, accept, select, recv など。
client/main.cpp に socket, connect, send など。

ソースコード

simple-tcpserver

インストール

$ git clone http://github.com/utahta/simple-tcpserver.git
$ cd simple-tcpserver
$ ./configure --prefix=/path/to/simple-tcpserver
$ make
$ make install

実行

・サーバ

$ cd /path/to/simple-tcpserver/bin
$ ./easy_tcpserver

・クライアント

$ cd /path/to/simple-tcpserver/bin
$ ./easy_tcpclient
11月 172009
 

libevent のソースコードを参考にしつつ epoll を使ってみたメモ。
ただの興味本位。
動作は、Linux CentOS 5 で確認。
エラー処理とかはやってたりやってなかったり、わりと適当。

server.cpp

/**
 * @file    server.cpp
 * @brief   epoll test server.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/epoll.h>
 
#define LISTEN_PORT (12345) ///< 待ち受けポート
#define MAX_EVENTS (1000) ///< 最大イベント数
#define MAX_BACKLOG (10) ///< リクエスト待機キューの格納数
 
struct epollev
{
    void (*callback)(int, void *); ///< callback function.
    void *arg; ///< user data.
};
 
struct epollop
{
    int epfd; ///< epoll fd.
    struct epoll_event *events; ///< epoll events.
    int num_events; ///< maximum number of events.
    struct epollev *fds; ///< fds
};
static epollop g_epollop;
 
static void callback_accept( int fd, void *arg );
static void callback_client( int fd, void *arg );
 
//! @brief    強制終了
static void server_exit( const char *log )
{
    perror( log );
    exit( EXIT_FAILURE );
}
 
//! @brief    イベント初期化
static void server_init()
{
    int epfd, nfiles = MAX_EVENTS;
 
    if( ( epfd = epoll_create( nfiles ) ) == -1 ){
        server_exit( "epoll_create()" );
    }
    g_epollop.epfd = epfd;
    g_epollop.events = new struct epoll_event[nfiles];
    memset( g_epollop.events, 0, sizeof(struct epoll_event)*nfiles );
    g_epollop.num_events = MAX_EVENTS;
    g_epollop.fds = new epollev[nfiles];
    memset( g_epollop.fds, 0, sizeof(struct epollev)*nfiles );
}
 
//! @brief    イベント追加
static void server_add( int fd, int events, void (*callback)(int, void *), void *arg )
{
    struct epoll_event epev = {0, {0}};
    struct epollev *ev = &g_epollop.fds[fd];
 
    int op = EPOLL_CTL_ADD;
    if( ev->callback != NULL ){
        op = EPOLL_CTL_MOD;
    }
 
    epev.data.fd = fd;
    epev.events = events;
    if( epoll_ctl( g_epollop.epfd, op, fd, &epev ) == -1 ){
        perror( "epoll_ctl()" );
        return;
    }
    ev->callback = callback;
    ev->arg = arg;
}
 
//! @brief    イベント削除
static void server_del( int fd )
{
    struct epoll_event epev = {0, {0}};
    struct epollev *ev = &g_epollop.fds[fd];
    int op;
 
    op = EPOLL_CTL_DEL;
    epev.data.fd = fd;
    memset( ev, 0, sizeof( struct epollev ) );
 
    if( epoll_ctl( g_epollop.epfd, op, fd, &epev ) == -1 ){
        perror( "epoll_ctl()" );
        return;
    }
    close( fd );
}
 
//! @brief    受付
static void server_listen()
{
    int sock;
    struct sockaddr_in saddr;
 
    if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ){
        server_exit( "socket()" );
    }
 
    memset( &saddr, 0, sizeof( saddr ) );
    saddr.sin_family    = AF_INET;
    saddr.sin_addr.s_addr = htonl( INADDR_ANY );
    saddr.sin_port = htons( LISTEN_PORT );
 
    if( bind( sock, (struct sockaddr *) &saddr, sizeof(saddr) ) != 0 ){
        close( sock );
        server_exit( "bind()" );
    }
 
    if( listen( sock, MAX_BACKLOG ) < 0 ){
        close( sock );
        server_exit( "listen()" );
    }
 
    server_add( sock, EPOLLIN, callback_accept, NULL );
}
 
//! @brief    メインループ
static int server_loop( )
{
    fprintf( stdout, "in server_loop.\n" );
 
    while( true ){
        int i, res;
        int timeout = -1;
        struct epoll_event *events = g_epollop.events;
 
        res = epoll_wait( g_epollop.epfd, events, g_epollop.num_events, timeout );
        if( res == -1 ){
            server_exit( "epoll_wait()" );
        }
 
        for( i = 0; i < res; ++i ){
            int fd = events[i].data.fd;
            struct epollev *ev = &g_epollop.fds[fd];
 
            ev->callback( fd, ev->arg );
        }
    }
    return 0;
}
 
//! @brief    受付コールバック
static void callback_accept( int fd, void *arg )
{
    fprintf( stdout, "in callback_accept. fd:%d\n", fd );
 
    int sock;
    struct sockaddr saddr;
    socklen_t len = sizeof( struct sockaddr_in );
 
    if( ( sock = accept( fd, (struct sockaddr *)&saddr, &len ) ) < 0 ){
        perror( "accept()" );
        return;
    }
 
    int flag = fcntl( sock, F_GETFL, 0 );
    fcntl( sock, F_SETFL, flag | O_NONBLOCK );
 
    fprintf( stdout, "accept. sock:%d\n", sock );
    server_add( sock, EPOLLIN, callback_client, NULL );
}
 
//! @brief    クライアントコールバック
static void callback_client( int fd, void *arg )
{
    fprintf( stdout, "in callback_client. fd:%d\n", fd );
 
    char buff[1024];
    int ret = ::recv( fd, buff, sizeof(buff), 0 );
    if( ret <= 0 ){
        fprintf( stdout, "exit. fd:%d ret:%d\n", fd, ret );
        server_del( fd );
    }
    else{
        fprintf( stdout, "fd:%d -> %s\n", fd, buff );
    }
}
 
int main()
{
    server_init();
    server_listen();
    return server_loop();
}

client.cpp

/**
 * @file    client.cpp
 * @brief   test client.
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
 
#define CONNECT_ADDR "127.0.0.1"
#define CONNECT_PORT (12345)
 
bool setnonblock(int fd)
{
    int flags = fcntl( fd, F_GETFL );
    if( flags < 0 ){
        return false;
    }
 
    if( fcntl( fd, F_SETFL, flags |= O_NONBLOCK ) < 0 ){
        return false;
    }
    return true;
}
 
int main(void)
{
    int ret = 0, sock = 0, clilen = 0;
    struct sockaddr_in cliaddr;
 
    // socket
    if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ){
        puts( "socket error!!" );
        return 1;
    }
 
    memset( &cliaddr, 0, sizeof( cliaddr ) );
    cliaddr.sin_addr.s_addr = inet_addr( CONNECT_ADDR );
    cliaddr.sin_family    = AF_INET;
    cliaddr.sin_port    = htons( CONNECT_PORT );
 
    clilen = sizeof( cliaddr );
    if( ( ret = connect( sock, (struct sockaddr *)&cliaddr, clilen ) ) < 0 ){
        perror( "connect()" );
        return 1;
    }
    if( !setnonblock( sock ) ){
        perror( "setnonblock()" );
    }
 
    puts( "in client loop." );
    char str[1024];
    char *strpos;
    while( 1 )
    {
        memset( str, 0, sizeof( str ) );
        fgets( str, sizeof(str), stdin );
 
        // \n を除去
        if( ( strpos = strchr( str, '\n' ) ) != NULL ){
            (*strpos) = '\0';
        }
 
        // exit
        if( strncmp( str, "exit", 4 ) == 0 )
            break;
 
        // send
        if( send( sock, str, strlen( str ) + 1, 0 ) < 0 ){
            perror( "send()" );
            break;
        }
    }
 
    // 後始末
    while( 1 ){
        ret = recv( sock, str, sizeof(str), 0 );
        if( ret <= 0 )
            break;
    }
    close( sock );
 
    return 0;
}
10月 272009
 

core ファイルを解析するメモ。

下準備

まず意図的に SEGV させるコードを書く。

$ vi a.cpp
#include <stdio.h>
 
class CPrint
{
private:
    int m_number;
    char *m_str;
 
public:
    CPrint() : m_number(10), m_str(NULL) {}
    ~CPrint() {}
 
    void print(){
        // ここで SEGV る予定
        printf( "%d, %c\n", m_number, m_str[0] );
    }
};
 
int main()
{
    CPrint p;
    p.print();
    return 0;
}

続けて core を出力させる設定。環境は Linux CentOS 5。

$ ulimit -c unlimited

core dumped

下準備で作成したソースコードをコンパイル。-g を忘れずに。

$ g++ a.cpp -g

実行する。当然 SEGV る。

$ ./a.out
セグメンテーション違反です (core dumped)

core ファイルが吐かれていることを確認。

$ ls
a.cpp  a.out  core.1613

core 解析

gdb を立ち上げる。gdb 実行バイナリ coreファイル

$ gdb a.out core.1613
...
Program terminated with signal 11, Segmentation fault.
#0  0x08048568 in CPrint::print (this=0xbfe45794) at a.cpp:15
15              printf( "%d, %c\n", m_number, m_str[0] );
(gdb)

呼び出された順番で関数を表示する。

(gdb) where
#0  0x08048568 in CPrint::print (this=0xbfe45794) at a.cpp:15
#1  0x080484fd in main () at a.cpp:22
(gdb)

ソースコードを表示する。

(gdb) list
10          CPrint() : m_number(10), m_str(NULL) {}
11          ~CPrint() {}
12
13          void print(){
14              // ここで SEGV る予定
15              printf( "%d, %c\n", m_number, m_str[0] );
16          }
17      };
18
19      int main()
(gdb)

変数の値を確認する。
クラスのメンバ変数もアドレスさえ分かれば確認できる。

(gdb) p ((class CPrint *) 0xbfe45794)->m_number
$2 = 10
(gdb)

いちいち ((class CPrint *) 0xbfe45794) とうつのが面倒な場合は、変数に代入する。

(gdb) set $c = ((class CPrint *) 0xbfe45794)
(gdb) p $c->m_number
$3 = 10
(gdb) p $c->m_str
$4 = 0x0
 

自宅サーバのネットワークが突如不安定になる問題の解決メモ。

samba 経由で 10GB 程度のファイルをダウンロードしていると、サーバと通信が途切れてその後まともに通信出来なくなるという現象。
上記なことをしなければまったく問題なかったので、解決まで時間がかかったかかった。
いろいろ無駄な作業もしたけど、忘れがたい経験になったのでまとめメモ。

* 自宅サーバは CentOS 5.4 を使用しています。
* ディストリビューションによっては、そのまま従って解決というわけにいかないかもしれません。

原因追及

とりあえず情報収集。
手始めに /var/log/message を眺める。
いろいろあやしげな文字列がある中、ひときわ目立ってたのが次の1文。

$ vi /var/log/message
...
NETDEV WATCHDOG: eth0: transmit timed out
...

このメッセージが出た直後から、まともに ssh すらできない状態に陥るもよう。

最初はクライアント側の Mac のせいかと思って、Windows から同じことを試したりしたけど、状況は変わらず
いま思ってみれば、サーバにエラー的なメッセージが出ている時点でほぼ原因はサーバと断定して良かったかもしれない。

まぁとにかく原因はサーバ側にあるらしい。
けど、具体的な症状はこの時点でさっぱりだったので、引き続き情報収集。

# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:1C:C0:B4:F6:4A  
          inet addr:192.168.11.30  Bcast:192.168.11.255  Mask:255.255.255.0
...
# ethtool eth0
Settings for eth0:
...
	Advertised auto-negotiation: Yes
	Speed: 1000Mb/s
	Duplex: Full
...
# lspci -v
...
01:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168B PCI Express Gigabit Ethernet controller (rev 02)
	Subsystem: Intel Corporation Unknown device 0001
...

いろいろコマンドの使い方は分かったものの、原因はさっぱり。

調べた情報を元に google 先生と協力しながら、
ethtool で speed を 1000 から 100 にしてみたり。

# ethtool -s eth0 speed 100

TCP Segmentation Offload をオフにしてみたり。(後でもともとオフだったことに気づいたり)

# ethtool -K eth0 tso off

けれど、解決には至らず。

検索語を変えながらさらにググってみると、
「NIC のドライバのバージョンが違うとネットワークが不安定になるよ」という情報を発見。
どうやら、NICのバージョンとインストールされているドライバのバージョンが異なると問題が発生するらしい。

解決編

先人の知恵をもとに NIC のバージョンとドライバを確認。

# lspci | grep Ether
01:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168B PCI Express Gigabit Ethernet controller (rev 02)
# lsmod | grep r81
... r8169 ですよ的な表示 ...

NICのバージョンは、RTL8111/8168B。
インストールされているドライバのバージョンは、r8169。

ビンゴか!!

RTL8111/8168B には、r8168 がインストールされていないとだめとのこと。
ここまで来れば解決したも同然!
さっそくドライバを再インストール。
ssh 経由 だとサーバから切断されて作業が中断されること請け合いなので、直接キーボードを繋いでの作業がオススメ。
事前に ドライバ をダウンロードしてサーバに置いておく。
作業時は r8168-8.014.00.tar.bz2 を /usr/local/src に置いた。

$ su -
# cd /usr/local/src
# rmmod r8169
# vi /etc/modprobe.conf
r8169 を r8168 に置換
...
# tar jxvf r8168-8.014.00.tar.bz2 
# cd r8168-8.014.00
# vi readme 
... すべき作業が書いてある...
# make clean modules
# make install
# man depmod
# depmod -a
# modprobe r8168
# lsmod | grep r8168
# reboot

以上のコマンドを叩いた結果、無事に samba 経由で 10GB 程度のファイルをダウンロードすることが出来るようになった。

まさかのドライバ違いに思わぬ時間を取られてしまったけど、いい経験になったかも。

参考

CentOSにNICを手動インストールした人は、正しいドライバが入っているかチェックしたほうがよさげ