totofugaのブログ

ネットワークとかc言語、perlの話。

カーネルからユーザ空間にnetlinkブロードキャスト

iptablesのULOGを調べててカーネル空間にnetlinkにブロードキャストを送る方法が気になったので書いてみた

カーネル空間のプログラム

#include <linux/socket.h>                                                     
#include <net/sock.h>                                                         
                                                                              
static struct timer_list my_timer;                                            
static struct sock *sock;                                                     
static int seq;                                                               
                                                                              
MODULE_LICENSE("GPL");                                                        
                                                                              
void my_timer_callback( unsigned long data ) {                                
    char word[] = "hello world!";                                             
    struct sk_buff *skb;                                                      
    int type = 0;                                                             
    struct nlmsghdr *nlh;                                                     
    char *d;                                                                  
    int ret;                                                                  
                                                                              
    printk("timeout\n");                                                      
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));                    
                                                                              
                                                                              
    skb = alloc_skb(1024, GFP_ATOMIC);                                        
    if ( !skb ) {                                                             
        printk("error: skb alloc\n");                                         
        return;                                                               
    }                                                                         
                                                                              
    nlh = NLMSG_PUT(skb, 0, ++seq, type, strlen(word));                       
    d = NLMSG_DATA(nlh);                                                      
    memcpy(d, word, strlen(word));                                            
                                                                              
    ret = netlink_broadcast(sock, skb, 0, 5, GFP_ATOMIC);                                 
                                                                              
    return;                                                                   
                                                                              
nlmsg_failure:                                                                
    printk("error:NLMSG_PUT\n");                                              
    return;                                                                   
}                                                                             
                                                                              
int myrtnl_init(void) {                                                       
                                                                              
    sock = netlink_kernel_create(&init_net, 20, 32, NULL, NULL, THIS_MODULE); 
    if ( !sock ) {                                                            
        printk("error: netlink kernel create\n");                             
        return 1;                                                             
    }                                                                         
                                                                              
    setup_timer(&my_timer, my_timer_callback, 0);                             
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));                    
    return 0;                                                                 
}                                                                             
                                                                              
void myrtnl_exit(void) {                                                      
    del_timer(&my_timer);                                                     
    netlink_kernel_release(sock);                                             
}                                                                             
                                                                              
module_init(myrtnl_init);                                                     
module_exit(myrtnl_exit);                                                     

200ミリ秒ごとにNETLINKにブロードキャストを送るプログラム

cat /proc/net/netlink で空いてるをとりあえず使ってみた。(ルールは今度ちゃんと調べる)

netlink_broadcastにsk_buffを渡せば作れる。

NLMSG_PUTというショートカットメソッドも用意されている。

ユーザ空間のプログラム

#include <stdio.h>                                                               
#include <libnetlink.h>                                                          
#include <stdlib.h>                                                              
#include <string.h>                                                              
                                                                                 
int listen_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg) {
    char buf[256] = {0};                                                         
    memcpy(buf, NLMSG_DATA(n), 12);                                              
    printf("data: %s\n", buf);                                                   
    return 0;                                                                    
}                                                                                
void main() {                                                                    
    struct rtnl_handle rh;                                                       
    int n = 5;                                                                   
    if ( rtnl_open_byproto(&rh, 1 << (n - 1), 20) != 0 ) {                       
        perror("open error\n");                                                  
        exit(1);                                                                 
    }                                                                            
                                                                                 
    printf("listen start\n");                                                    
    rtnl_listen(&rh, listen_handler, NULL);                                      
}                                                                                

libnetlinkを使った受信

ユーザ空間のグループ指定はビットシフトになるので注意

出力結果

[root@localhost rtnetlink]# ./a.out 
listen start                        
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  

iptables勉強(2)

iptablesのtargetrの拡張

iptablesだいぶわかってきたので最小構成のtargetを書いてみた

構成ファイル

user空間用とkernel空間用の2種類のファイルを用意する必要がある

user空間用ファイル

iptablesコマンドを拡張するために /lib64/xtables/libに共有ライブラリを作成して配置する

名前はlibipt_<target名>.so

エントリポイントは_initという関数を作りxtables_targetを登録する

xtables.hはiptables-develパッケージに含まれている

#include <xtables.h>                                                                        
#include <stdio.h>                                                                          
                                                                                            
static void MYLOG_help() {                                                                  
    // printf("commandオプションがあればここに記述\n");  // iptables -j MYLOG --helpの時に表示される                                   
}                                                                                           
                                                                                            
static void MYLOG_print(const void *ip, const struct xt_entry_target *target, int numeric) {
    printf(" MYLOG");    // iptables -Lの時に表示される名前                                                                
}                                                                                           
                                                                                            
static struct xtables_target my_log_target = {                                              
    .name  = "MYLOG",                                                                       
    .version = XTABLES_VERSION,                                                             
    .family = NFPROTO_IPV4,                                                                 
    .help = MYLOG_help,                                                                     
    .print = MYLOG_print                                                                    
};                                                                                          
                                                                                            
void _init() {                                                                              
    xtables_register_target(&my_log_target);                                                
}                                                                                           

作成したら共有ライブラリとしてコンパイルして/lib64/xtables/libに配置

gcc -fpic -shared ./libipt_MYLOG.c -o libipt_MYLOG.so

kernel空間用ファイル

user空間用ファイルに対応するカーネルモジュールを作成する

今回はヒットしたらログを出すだけ。

ユーザ空間のファイルとの対応はxt_targetのnameで行なっている

#include <linux/module.h>                                                              
#include <linux/netfilter.h>                                                           
#include <linux/netfilter/x_tables.h>                                                  

MODULE_LICENSE("GPL");

static unsigned int log_mylog(struct sk_buff *skb, const struct xt_action_param *par) {
    printk(KERN_ALERT "run mylog\n");
    return XT_CONTINUE;                                                                
}                                                                                      
                                                                                       
static struct xt_target mylog_target __read_mostly = {                                 
    .name       = "MYLOG",                                                             
    .family     = NFPROTO_IPV4,                                                        
    .target     = log_mylog,                                                           
    .me     = THIS_MODULE,                                                             
};  

static int __init mylog_init(void) {                                                   
    return xt_register_target(&mylog_target);                                          
}   

static void __exit mylog_exit(void) {
    xt_unregister_target(&mylog_target);
}

module_init(mylog_init);
module_exit(mylog_exit);

作成したらカーネルモジュールをロード

insmod mylog.ko

iptables

user空間とkernel空間のファイルを作成すると、iptablesコマンドの-jオプションにて自分の作成したターゲットを指定できる

# iptables -A INPUT -j MYLOG
# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
MYLOG      all  --  anywhere             anywhere             MYLOG

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

この状態でアクセスがあるとカーネルログにrun mylogが出力される

iptables勉強

ユーザ空間から IPTablesのチェインの取り出し。

とりあえずなんとなくとれるようになったので今日はここまで。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <net/if.h>
#include <getopt.h>
#include <stdlib.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/x_tables.h>

int show_ipt_entry(struct ipt_entry * ent, struct ipt_getinfo *info, struct ipt_get_entries *entries) {
    struct xt_entry_target *xt_target = ipt_get_target(ent);

    // user定義チェイン
    if ( strcmp(xt_target->u.user.name, "ERROR") == 0 ) {
        printf("user定義チェイン(%s)\n", (char*)xt_target->data);
        return 0;
    }

    // builtinチェイン
    int i;
    for ( i = 0; i < NF_IP_NUMHOOKS; ++i ) {
        if ( info->valid_hooks & (1 << i) ) { // 有効か確認
            if ( (struct ipt_entry*)((char*)entries->entrytable + info->hook_entry[i]) == ent ) {
                switch( i ) {
                    case NF_IP_PRE_ROUTING:
                        printf("PREROUTING\n");
                        break;
                    case NF_IP_POST_ROUTING:
                        printf("POSTROUTING\n");
                        break;
                    case NF_IP_LOCAL_IN:
                        printf("LOACLIN\n");
                        break;
                    case NF_IP_LOCAL_OUT:
                        printf("LOCALOUT\n");
                        break;
                    case NF_IP_FORWARD:
                        printf("FORWARD\n");
                        break;
                    default:
                        printf("unknown chain\n");
                        break;
                }
            }
        }
    }

    struct xt_standard_target *st = (struct xt_standard_target*)xt_target;
    printf("#### ipt_verdict %d ####\n", st->verdict);

    return 0;
}

void main(int argc, char *argv[]) {

    char table[256] = {};
    strcpy(table, "filter");
    int arg;
    while( (arg = getopt(argc, argv, "t:")) != -1 ) {
        switch(arg) {
            case 't':
                strcpy(table, optarg);
                break;
        }
    }

    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

    struct ipt_getinfo info;
    memset(&info, 0, sizeof(info));
    strcpy(info.name, table);


    int len = sizeof(info);
    if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &len) == -1 ) {
        perror("IPT_SO_GET_INFO error\n");
        return;
    }

    printf("#### ipt_getinfo ####\n");
    printf("name : %s\n", info.name);
    printf("valid_hooks : %d\n", info.valid_hooks);
    printf("underflow: \n");
    printf("\tNF_INET_PRE_ROUTING : %d\n", info.underflow[NF_INET_PRE_ROUTING]);
    printf("\tNF_INET_LOCAL_IN: %d\n", info.underflow[NF_INET_LOCAL_IN]);
    printf("\tNF_INET_FORWARD: %d\n", info.underflow[NF_INET_FORWARD]);
    printf("\tNF_INET_LOCAL_OUT: %d\n", info.underflow[NF_INET_LOCAL_OUT]);
    printf("\tNF_INET_POST_ROUTING: %d\n", info.underflow[NF_INET_POST_ROUTING]);
    printf("num_entries: %d\n", info.num_entries);
    printf("size: %d\n", info.size);

    struct ipt_get_entries *entries;
    len = sizeof(*entries) + info.size;
    entries = calloc(1, len);
    strcpy(entries->name, info.name);
    entries->size = info.size;
    if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &len) == -1 ) {
        perror("IPT_SO_GET_ENTRIES error\n");
        return;
    }

    int i;
    for(i = 0; i < NF_INET_NUMHOOKS; ++i) {
        if ( info.underflow[i] == -1 ) {
            continue;
        }
        struct ipt_entry* ent = (struct ipt_entry*)((char*)entries->entrytable + info.underflow[i]);
        struct xt_entry_target *t = ipt_get_target(ent);
        if ( strcmp(t->u.user.name, "") != 0 ) {
            printf("error\n");
        }

        struct xt_standard_target *st = (struct xt_standard_target*)t;
    }

    IPT_ENTRY_ITERATE(entries->entrytable, entries->size, show_ipt_entry, &info, entries);

}

linux arpの内部データ

arpの統計情報が欲しかったのでnetlink経由で取得するものを作った NDTA_PARMSはrtattrが入れ子になっている。

#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/neighbour.h>
#include <linux/rtnetlink.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>

static int seq;

void msg_receive(int sock) {
    char buf[4096]; 

    int n = read(sock, buf, sizeof(buf));
    if ( n <= 0 ) {
        perror("read error\n");
        exit(1);
    }
    struct nlmsghdr *nh = (struct nlmsghdr *)buf;
    // printf("read size: %d\n", n);
    if ( nh->nlmsg_seq != seq ) {
        printf("seq error\n");
        exit(1);
    }
    if ( nh->nlmsg_type == NLMSG_ERROR ) {
        printf("error\n");
        exit(1);
    } else if ( nh->nlmsg_type == RTM_NEWNEIGHTBL ) {
        NLMSG_ALIGN(nh->nlmsg_len);
        struct ndtmsg *ndtm = NLMSG_DATA(nh);
        if ( ndtm->ndtm_family != AF_INET ) {
            printf("ndgm_family is not AF_INET\n");
            exit(1);
        }

        struct rtattr *rta;
        int len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndtmsg));
        for (rta = (struct rtattr*)((char *)ndtm + sizeof(struct ndtmsg)); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
            switch(rta->rta_type) {
                case NDTA_NAME:
                    printf("NAME: %s\n", (char*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH1:
                    printf("THRESH1 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH2:
                    printf("THRESH2 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH3:
                    printf("THRESH3 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_CONFIG:
                     {
                        struct ndt_config *config = (struct ndt_config*)RTA_DATA(rta);
                        printf("CONFIG:\n");
                        printf("\tkey_len: %d\n", config->ndtc_key_len);
                        printf("\tentry_size: %d\n", config->ndtc_entry_size);
                        printf("\tentries: %d\n", config->ndtc_entries);
                        printf("\tlast_flush: %d\n", config->ndtc_last_flush);
                        printf("\tlast_rand: %d\n", config->ndtc_last_rand);
                        printf("\thash_rnd: %d\n", config->ndtc_hash_rnd);
                        printf("\thash_mask: %d\n", config->ndtc_hash_mask);
                        printf("\thash_chain_gc: %d\n", config->ndtc_hash_chain_gc);
                        printf("\tproxy_qlen: %d\n", config->ndtc_proxy_qlen);
                    }
                    break;
                case NDTA_PARMS:
                    {
                        printf("PARAMS:\n");
                        struct rtattr *parm_rta;
                        int parm_len = rta->rta_len;
                        for (parm_rta = rta + 1; RTA_OK(parm_rta, parm_len); parm_rta = RTA_NEXT(parm_rta, parm_len)) {
                            switch(parm_rta->rta_type) {
                                case NDTPA_IFINDEX:
                                    printf("\tifindex: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_REFCNT:
                                    printf("\trefcnt: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_REACHABLE_TIME:
                                    printf("\treachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_BASE_REACHABLE_TIME:
                                    printf("\tbase_reachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_RETRANS_TIME:
                                    printf("\tretrans_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_GC_STALETIME:
                                    printf("\tgc_staletime: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_DELAY_PROBE_TIME:
                                    printf("\tdelay_probe_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_QUEUE_LEN:
                                    printf("\tqueue_len: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_APP_PROBES:
                                    printf("\tapp_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_UCAST_PROBES:
                                    printf("\tucast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_MCAST_PROBES:
                                    printf("\tmcast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_ANYCAST_DELAY:
                                    printf("\tanycast_delay: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_PROXY_DELAY:
                                    printf("\tproxy_delay: %d\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_PROXY_QLEN:
                                    printf("\tproxy_qlen: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_LOCKTIME:
                                    printf("\tlocktime: %d\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                            }
                        }
                    }
                    break;
                case NDTA_STATS:
                    {
                        struct ndt_stats *stats = (struct ndt_stats*)RTA_DATA(rta);
                        printf("STATS:\n");
                        printf("\tallocs: %d\n", stats->ndts_allocs);
                        printf("\tdestorys: %d\n", stats->ndts_destroys);
                        printf("\tgrow: %d\n", stats->ndts_hash_grows);
                        printf("\tres_failed: %d\n", stats->ndts_res_failed);
                        printf("\tlookups %d\n", stats->ndts_lookups);
                        printf("\thits: %d\n", stats->ndts_hits);
                        printf("\trcv_probes_mcast: %d\n", stats->ndts_rcv_probes_mcast);
                        printf("\trcv_probes_ucast: %d\n", stats->ndts_rcv_probes_ucast);
                        printf("\tperiodic_gc_runs: %d\n", stats->ndts_periodic_gc_runs);
                        printf("\tforced_gc_runs: %d\n", stats->ndts_forced_gc_runs);
                    }
                    break;
                case NDTA_GC_INTERVAL:
                    printf("GC_INTERVAL %lld\n", *(uint64_t*)RTA_DATA(rta));
                    break;
            }
        }
    }
}

void main() {
    srand(time(NULL));
    seq = rand();

    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
    if ( sock == -1 ) {
        perror("sock error\n");
        exit(1);
    }

    struct {
        struct nlmsghdr nh;
        struct rtgenmsg rt;
    } h;

    struct sockaddr_nl nl;
    memset(&nl, 0, sizeof(nl));
    nl.nl_family = AF_NETLINK;

    if ( bind(sock, (struct sockaddr*)&nl, sizeof(nl)) == -1 ) {
        perror("bind error\n");
        exit(1);
    }

    memset(&h, 0, sizeof(h));
    h.nh.nlmsg_type  = RTM_GETNEIGHTBL;
    h.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
    h.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(h.rt));
    h.nh.nlmsg_seq   = seq;
    h.rt.rtgen_family = AF_INET;

    int n = write(sock, &h, sizeof(h));
    if ( n <= 0 ) {
        perror("write error\n");
        exit(1);
    }
    msg_receive(sock);
}

出力はこんな感じ

NAME: arp_cache
GC_INTERVAL 10000
THRESH1 128
THRESH2 512
THRESH3 1024
CONFIG:
        key_len: 4
        entry_size: 260
        entries: 3
        last_flush: 27997298
        last_rand: 63783
        hash_rnd: 398501669
        hash_mask: 7
        hash_chain_gc: 0
        proxy_qlen: 0
STATS:
        allocs: 49
        destorys: 46
        grow: 0
        res_failed: 25
        lookups 61661
        hits: 48360
        rcv_probes_mcast: 0
        rcv_probes_ucast: 0
        periodic_gc_runs: 2274
        forced_gc_runs: 0
PARAMS:
        refcnt: 1
        queue_len: 3
        proxy_qlen: 64
        app_probes: 0
        ucast_probes: 3
        mcast_probes: 3
        reachable_time: 18336
        base_reachable_time: 20000
        gc_staletime: 60000
        delay_probe_time: 5000
        retrans_time: 1000
        anycast_delay: 1000
        proxy_delay: 800
        locktime: 1000

linuxカーネルのpid管理(取得周り)

カーネルのpid管理と取得周りを調べてみた

PID名前空間

Linuxではpid_namespace(詳しくはman 7参照)に対応しているため、 1つのプロセスに対して複数のプロセスIDを複数持てるようになっている。

pid_namespaceは階層構造になっていて、 子のプロセスIDは同一プロセスでもそれぞれの名前空間で別のプロセスidとなる f:id:totofuga:20170624091642p:plain

親で作成されたのプロセスIDは子からは見えないようになっている f:id:totofuga:20170624091957p:plain

したがってプロセスIDは作成された階層の深度分プロセスIDを持つようになっている

カーネルでの表現

カーネル内部ではPIDはpidとupidという構造で表される

enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

struct upid {                                                             
    int nr;  // 実際のID
    struct pid_namespace *ns; // 属しているネームスペース
    struct hlist_node pid_chain;                                          
};

struct pid                                 
{                                         
    atomic_t count;                        
    unsigned int level;  // このプロセスが作成された時のname_spaceの深度
    struct hlist_head tasks[PIDTYPE_MAX];  // pid構造体のプロセス、プロセスグループ、セッションに属するタスク
    struct rcu_head rcu;                   
    struct upid numbers[1]; // 可変長でlevel分だけupidを持つ
};

pid構造体のIDはネームスペース毎にネームスペース毎に持つため、所属している深度分のupidを持つようになっていて、upidに実際の番号を持っている。

pid->tasksメンバ

tasksメンバはそのpidのプロセス、プロセスグループ、セッションに属するtask_structの一覧を管理する f:id:totofuga:20170624102843p:plain

pid構造体に属するPIDTYPE_PIDに結びつくプロセスは1つしかないので、 1つまでしか入らないことが保証されている。

IDからpidへの変換処理

IDからpidへの変換処理はよく行われるので、 pid_hashというIDとネームスペースのポインタをキーにしたハッシュを使い処理を高速化している

関数としてはネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。

extern struct pid *find_pid_ns(int nr, struct pid_namespace *ns);
extern struct pid *find_vpid(int nr);

pidからidへの変換処理

ネームスペースのlevelからupidを探し出してnrを返す関数

こちらもネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns);
pid_t pid_vnr(struct pid *pid);                            

現在実行中のプロセスのネームスペース取得方法

// 呼び出し元
task_active_pid_ns(current->group_leader); // currentはスレッドに対応するのでプロセスのtask_structで呼び出し

struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)  
{
    return ns_of_pid(task_pid(tsk)); // task_pidでpid構造体を取得してpid構造体からネームスペースを取得する
}                              

static inline struct pid *task_pid(struct task_struct *task)  
{
    return task->pids[PIDTYPE_PID].pid; // カレントプロセスのPID構造体を取得
}

static inline struct pid_namespace *ns_of_pid(struct pid *pid) 
{                                                              
    struct pid_namespace *ns = NULL;
    if (pid)                                                   
        ns = pid->numbers[pid->level].ns; // カレントプロセスの最大深度 = カレントプロセスが作成された名前空間となる。                      
    return ns;                                                 
}    
                                  

LinuxとFreeBSDの共有ライブラリについて

共有ライブラリについて

Linuxでは共有ライブラリとして.soファイルが使用される。 .soは実行時にリンクする以外でも、実行中にdlopenを使用して読み込むことができる。

実行中の共有ライブラリ読み込みの仕組み

共有ライブラリは.textセクションを共有する為、 静的ライブラリのように外部ライブラリのアドレスを relセクションを作ってリンク時に書き換えということができない。

.textセクションの関数呼び出し先を共有しない書き込み可能な.got(global offset table)という領域に分けて、 そのポインタを参照することにより対処している。

共有ライブラリの遅延ロード

dlopenにはRTLD_LEZYというフラグがある。 RTLD_LEZYは関数の実行時まで参照先のシンボルを解決しないというフラグだが、 それを実現するために.pltという領域を使用している。

.pltには

   0x00007fffeb62f568 <+0>:     jmpq   *0x2022ea(%rip)        # 0x7fffeb831858
   0x00007fffeb62f56e <+6>:     pushq  $0x22
   0x00007fffeb62f573 <+11>:    jmpq   0x7fffeb62f338

のような関数ごとにjmpを2回するようなコードがある。 0x7fffeb831858はGOTのアドレスを指していて、この場合一度も実行していないと0x00007fffeb62f56e(pushdのところ)のアドレスが入っている。

したがって1回目の実行では最初のjmpは何もしないで次の行に進みシンボルをを解決したい関数のID(0x22)を入れて、シンボル解決するコードにジャンプする。 シンボル解決コードでは0x7fffeb831858の中を本来jmpしたいアドレスに書き換えてから、本来jmpしたいアドレスにjmpする。 2回目の呼び出しでは、0x7fffeb831858のGOTが書き換えられているので本来呼びたい関数に直接ジャンプできるという仕組みになっている

f:id:totofuga:20170622105542p:plain

f:id:totofuga:20170622105623p:plain

LinuxFreeBSDの挙動の違い

一度解決したリンク先がdlcloseによって参照先がなくなった場合の挙動にlinuxBSDでは微妙に違うところがある。

first.so

#include <stdio.h>
void do_action() {  printf("action for first_test\n"); }

second.so

#include <stdio.h>
void do_action() { printf("action for second_test\n"); }

test.so

void do_test() { do_action(); }

main.c

void main(){
    void (*do_test)();
    void *first  = dlopen("./libfirst.so", RTLD_LAZY | RTLD_GLOBAL);
    void *h = dlopen("./libtest.so", RTLD_LAZY | RTLD_LOCAL);
    *(void **)(&do_test) = dlsym(h, "do_test");
    (*do_test)();
    dlclose(first);
    void *second = dlopen("./libsecond.so", RTLD_LAZY | RTLD_GLOBAL);
    (*do_test)();
}

main => do_test => do_actionの順番に呼ばれているプログラムである。

上記実行するとFreeBSDではセグフォで落ちる どうなっているのかというとdo_testを読んだ時点で、do_actionの.gotがfirst.soのシンボルとして解決され、 その後, first.soをdlcloseにてプロセスから削除している。

次に呼ばれたdo_testでは削除されたアドレスにシンボル解決されたdo_actionを呼ぶので落ちるのである。

Linuxの出力は

action for first_test
action for first_test

とfirstが二回出力される。

Linuxではシンボル解決された段階で参照先(first.so)の参照カウントが上がるようで、 dl_closeしても、プロセス空間から削除されないようだ。

まとめ

dl_openで読み込む対象を動的に切り替えるのはうまくいかない。 Linuxではエラーにはならないが最初に読み込まれたもののみが実行される。 FreeBSDではセグフォで落ちる

linuxカーネルのIPフラグメント処理(1)

IPフラグメント処理

第一フラグメント便乗攻撃を調べるためlinuxのIPフラグメント処理を調べてみた。

長くなりそうなのでとりあえずinet_fragmentのメモ

ソースコード

  • net/ipv4/ip_fragment
  • net/ipv4/inet_fragment

inet_fragmentはプロトコルに依存しない処理を実装して、 ip_fragmentはipプロトコルに関わる処理を実装している

inet_fragment

ネットワーク層プロトコルに依存しないフラグメント処理をしている。 主にやっていることは下記の3つ

  • フラグメント可されたパケットの管理
  • タイムアウト処理
  • リミット処理

procによるパラメータの設定

ipv4の場合、タイムアウト処理とリミット処理の閾値は下記proc経由で設定可能

  • /proc/sys/net/ipv4/ipfrag_time (フラグメント可されたパケットを最初に受信してから有効な秒数)
  • /proc/sys/net/ipv4/ipfrag_high_thresh (メモリ使用量がこの値を超えるとフラグメント可されたパケットを受信できなくなる)
  • /proc/sys/net/ipv4/ipfrag_low_thresh (メモリ使用量がこの値を超えると値を下回るまで削除が始まる)

カーネル内部でパラメータの格納はnetns_fragsで行われる

struct netns_frags {
    struct percpu_counter   mem; // 現在使用しているメモリ量
    int         timeout; // フラグメントのタイムアウト
    int         high_thresh; // 削除を開始するメモリ上限
    int         low_thresh; // 処理を中止するメモリ上限
}

この値はネームスペース毎に保持してあり、プロセスからアクセスするには

task_struct->nsproxy->net_ns->ipv4->frags

となる

フラグメント可されたパケットの管理

フラグメント可されたパケットはinet_fragsのハッシュで管理されており、値はinet_frag_queueとなる プロトコルに関わるところは抽象化されている 主なパラメータは下記の通り

struct inet_frag_queue {
    struct timer_list   timer; // タイムアウトの設定
    struct hlist_node   list; // ハッシュリスト
    struct sk_buff      *fragments; // 同一パケット内のフラグメント可されたパケットのリスト
    struct sk_buff      *fragments_tail; // fragmentsの最後の要素
    int         len;   // 現在格納されているパケットの最終位置
    int         meat;   // 現在格納されてるパケットのバイト数 (最後のパケットが格納されていてlen == meatになると全て格納されたことになる)
    __u8            flags; // 下で説明
    struct netns_frags  *net; // 対象名前空間の総計と制限値へのポインタ
};  

struct inet_frags {
    struct inet_frag_bucket hash[INETFRAGS_HASHSZ];
    unsigned int        (*hashfn)(const struct inet_frag_queue *); // プロトコル毎のハッシュ値の計算 (必須)
    bool            (*match)(const struct inet_frag_queue *q, const void *arg); // プロトコル毎の値比較(必須)
    void            (*destructor)(struct inet_frag_queue *); // プロトコル固有の情報削除用フック(任意)
    void            (*skb_free)(struct sk_buff *); // sk_buff削除時のフック(任意)
    void            (*frag_expire)(unsigned long data); // タイムアウトまたはメモリ上限が超えて削除される時のフック(必須)
    struct kmem_cache   *frags_cachep; // inet_frag_initで設定される名前がfrags_cache_nameでサイズがqsizeのスラブキャッシュ
    const char      *frags_cache_name; // スラブキャッシュに使用される名前(必須)
    int         qsize;  // プロトコル固有データとinet_frag_queueデータの合計サイズ(必須)
}

構造体自体は使用する側で確保して、必須となっているパラメータの設定を行い、

inet_frag_init

を呼ぶことにより使用可能となる。 inet_frag_init内ではhashの初期化とfrag_cache_name、qsizeからスラブハッシュを構築する

タイムアウト処理

IPフラグメントは各セグメント毎にタイムアウトが設定されるため、 inet_frag_queueにタイマーを持っている。

作成と同時にタイマーが設定され、 タイムアウト時の呼び出しはinet_fragsに設定されたfrag_expire関数が呼ばれる

inet_frag_create
    inet_frag_alloc
        setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
    inet_flag_intern
        mod_timer(&qp->timer, jiffies + nf->timeout)

リミット処理

制限値を超えた場合は、カーネルのワークキューを使用してメモリの解放処理が行われている。

トリガーとなるのはソフトリミットを超えた場合で、ハードリミットを超えると処理を中断する。 ワーカースレッドが実行する関数は

inet_frag_worker

で、ソフトリミットを超えなくなるまでハッシュの要素を削除する。

expireが呼ばれる時のタイムアウトとリミット処理判断

expireはタイムアウトとリミット処理の両方で、リミットを超えて削除される場合は

INET_FRAG_EVICTED

フラグが設定される。 呼び出し元で判断を行いたい場合はこのフラグを参照する。

参考にさててもらったサイト

RFC 791