目录
摘要 开发环境要求 Hook开发需求 Hook代码编写 Hook代码编译 Hook Kea配置 总结 参考文献 附录
摘要
简单Kea DHCP开发案例教程。通过本篇文章使读者能够对Hook开发有一个大致轮廓。主要侧重操作,对于概念的说明请查看官网。申明:本篇使用的是官方文档案例
开发环境要求
- Ubuntu
- gcc编译环境
apt install gcc
- vim编辑器
- 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函数
- load肯定被调用,unload不一定被调用,比如系统异常关闭。
- 这两个函数主要用于资源的分配和关闭。
- 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 ? : );
}
源码
- 将创建两个文件,分别为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
- 定义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代码编译
根据上面的操作,完整的文件列表如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLzATN0MTO5czLcBjMtITMtcTMvwVbvNmLuRGZ19Gbj5CdrJmLiZjZnRDatB3bvw1LcpDc0RHaiojIsJye.jpg)
编译
上面编译成功后会在编译目录下,看到 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如何测试测试,请查阅Karaf教程之Config Admin服务的使用。
参考文献
Hooks Developer’s Guide
Hooks Libraries
Linux共享对象之编译参数fPIC
附录
源码附件