天天看点

Kea DHCP Hooks开发目录

目录

摘要 开发环境要求 Hook开发需求 Hook代码编写 Hook代码编译 Hook Kea配置 总结 参考文献 附录

摘要

简单Kea DHCP开发案例教程。通过本篇文章使读者能够对Hook开发有一个大致轮廓。主要侧重操作,对于概念的说明请查看官网。申明:本篇使用的是官方文档案例

开发环境要求

  1. Ubuntu
  2. gcc编译环境

    apt install gcc

  3. vim编辑器
  4. Kea相关Hook开发库(安装Kea即可)

Hook开发需求

Kea DHCPv4用于分配IPv4地址给客户端(也会传给客户端其他信息,比如DNS服务器地址)。假设需要根据客户硬件地址和客户请求IPv4地址进行分类,且记录感兴趣的客户端硬件地址和已分配IP地址。

Hook代码编写

  • version - Hook代码被编译时Kea代码使用的版本
  • load - 当Hook被服务器加载时调用
  • unload - 当Hook被服务器卸载是调用

version函数

用于检查Hook是否兼容当前正在运行的Kea服务器。

源码

// version.cc

#include <hooks/hooks.h>

extern "C" {
    int version() {
        return (KEA_HOOKS_VERSION);
    }
}
           

load和unload函数

  1. load肯定被调用,unload不一定被调用,比如系统异常关闭。
  2. 这两个函数主要用于资源的分配和关闭。
  3. load函数可以注册自定义callout名字,比如如下代码:
int load(LibraryHandle& libhandle) {
    // Register the callouts on the hooks. We assume that a header file declares the "classify" and "write_data" functions.
    libhandle.registerCallout("pkt4_receive", classify);
    libhandle.registerCallout("pkt4_send", write_data);
    // Open the log file
    interesting.open("/tmp/interesting.log", std::fstream::out | std::fstream::app);
    return (interesting ?  : );
}
           

源码

  1. 将创建两个文件,分别为library_common.h和load_unload.cc,library_common.h用于文件处理器声明。load_unload.cc用于定义load和unload函数。
// library_common.h
#ifndef LIBRARY_COMMON_H
#define LIBRARY_COMMON_H
#include <fstream>
// 日志文件处理器声明
extern std::fstream interesting;
#endif // LIBRARY_COMMON_H
           
  1. 定义load和unload函数源文件。==interesting.open的文件必须保证文件夹是存在的==,否则加载会失败
// load_unload.cc
#include <hooks/hooks.h>
#include "library_common.h"

using namespace isc::hooks;

// 日志文件处理器定义
std::fstream interesting;

extern "C" {
    int load(LibraryHandle&) {
        interesting.open("/tmp/interesting.log",std::fstream::out | std::fstream::app);

        return (interesting ?  : );
    }

    int unload() {
        if (interesting) {
            interesting.close();
        }

        return ();
    }
}
           

Callout函数

这里的Callout是所有Hook point的统称。并不是真的叫callout这个名字。

callout签名

返回0表示成功,非0表示失败。

extern "C" {
    int callout(CalloutHandle& handle);
}
           

callout参数

CalloutHandle对象提供2个方法getArgument和setArgument,获取和设置参数。下面的举例如何使用

// 服务端代码片段,演示设置参数
int count = ;
boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
// 假设handle对象已经创建
handle.setArgument("data_count", count);
handle.setArgument("inpacket", pktptr);
// 调用 callouts
...
// 获取修改的值
handle.getArgument("data_count", count);
handle.getArgument("inpacket", pktptr);
           

源码

// pkt4_receive.cc
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"
#include <string>

using namespace isc::dhcp;
using namespace isc::hooks;
using namespace std;

extern "C" {
// 在pkt4_receive时调用该函数
int pkt4_receive(CalloutHandle& handle) {
    // A pointer to the packet is passed to the callout via a "boost" smart
    // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
    // object as Pkt4Ptr.  Retrieve a pointer to the object.
    Pkt4Ptr query4_ptr;
    handle.getArgument("query4", query4_ptr);

    // Point to the hardware address.
    HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr();

    // The hardware address is held in a public member variable. We'll classify
    // it as interesting if the sum of all the bytes in it is divisible by 4.
    //  (This is a contrived example after all!)
    long sum = ;
    for (int i = ; i < hwaddr_ptr->hwaddr_.size(); ++i) {
        sum += hwaddr_ptr->hwaddr_[i];
    }

    // Classify it.
    if (sum %  == ) {
        // Store the text form of the hardware address in the context to pass
        // to the next callout.
        string hwaddr = hwaddr_ptr->toText();
        handle.setContext("hwaddr", hwaddr);
    }
    return ();
};
}
           
// pkt4_send.cc
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"
#include <string>

using namespace isc::dhcp;
using namespace isc::hooks;
using namespace std;

extern "C" {
// 在pkt4_send hook的时候调用
int pkt4_send(CalloutHandle& handle) {
    // Obtain the hardware address of the "interesting" client.  We have to
    // use a try...catch block here because if the client was not interesting,
    // no information would be set and getArgument would thrown an exception.
    string hwaddr;
    try {
        handle.getContext("hwaddr", hwaddr);

        // getContext didn't throw so the client is interesting.  Get a pointer
        // to the reply.
        Pkt4Ptr response4_ptr;
        handle.getArgument("response4", response4_ptr);

        // Get the string form of the IP address.
        string ipaddr = response4_ptr->getYiaddr().toText();

        // Write the information to the log file.
        interesting << hwaddr << " " << ipaddr << "\n";

        // ... and to guard against a crash, we'll flush the output stream.
        flush(interesting);


    } catch (const NoSuchCalloutContext&) {
        // No such element in the per-request context with the name "hwaddr".
        // This means that the request was not an interesting, so do nothing
        // and dismiss the exception.
     }
    return ();
}
}
           

Hook代码编译

根据上面的操作,完整的文件列表如下:

Kea DHCP Hooks开发目录

编译

上面编译成功后会在编译目录下,看到 example.so

指向kea安装时的目录,除非你安装时指定–prefix,否则默认为/usr/local

选项 解释
-I 指定额外的头文件搜索路径DIRECTORY。
-L 指定额外的函数库搜索路径DIRECTORY
-fpic 表明使用地址无关代码,PIC:Position Independent Code.
-shared 生成共享目标文件
-o 指定生成库的名字
-l 连接时搜索指定的函数库LIBRARY

Hook Kea配置

复制

复制Hook库到/usr/local/lib/hooks/下(根据你的喜好你可以放置在任意位置)

编辑

编辑vim /usr/local/etc/kea/kea-dhcp4.conf,在文件的Dhcp4节点下添加如下配置

"hooks-libraries": [{
    "library" : "/usr/local/lib/hooks/example.so"
}]
           

重启

重启后,运行测试工具,成功的话可以在/tmp/interesting.log目录如下输出:

Kea DHCP Hooks开发目录

总结

Kea如何测试测试,请查阅Karaf教程之Config Admin服务的使用。

参考文献

Hooks Developer’s Guide

Hooks Libraries

Linux共享对象之编译参数fPIC

Kea DHCP Hooks开发目录

附录

源码附件

继续阅读