经过多方的调研,发现,域名过滤其实本质也是IP地址的过滤,我们接着上篇文章来续写WFP过滤域名的方法:其实大致思路是这样的,首选将域名调用本地的DNS解析成IP地址,然后再将IP地址使用上篇文章的方法添加过滤器,但是这个时候有个问题,就是域名对应的IP地址可能会更新可能会变化,怎么办呢?我的初步解决办法是再添加了过滤器后,创建一个定时任务或者说创建一个线程,定时去DNS解析域名,将解析出来的IP地址去更新之前创建的过滤器,即可实现WFP域名过滤的功能。
1、解析域名
我的入参是多个域名的vector,在解析完vector后,再将解析的Ip地址集合以vector的方式返回,简易代码如下:
cpp
DWORD FilterFunC::DomainsToIps(const std::vector<std::string>& domains,std::vector<std::string>& ips) {
DWORD res = ERROR_SUCCESS;
// 2. 使用 getaddrinfo 解析域名(示例:允许访问 example.com)
struct addrinfo hints = { 0 };
hints.ai_family = AF_INET; // 只支持 IPv4
hints.ai_socktype = SOCK_STREAM;
for (auto domain : domains) {
struct addrinfo* result = NULL;
res = getaddrinfo(domain.c_str(), NULL, &hints, &result);
if (res != 0 || result == NULL)
{
domain = "DomainsToIps 域名转换错误:"+domain;
ERROR_PRINT(domain.c_str(),ERROR_INVALID_FUNCTION);
continue;
}
sockaddr_in* ipv4_addr = (sockaddr_in*)result->ai_addr;
uint32_t ip = ipv4_addr->sin_addr.S_un.S_addr; // 网络字节序IP地址
std::string ipStr = Uint32ToIpStr(ntohl(ip));
ips.push_back(ipStr);
}
return res;
}
要使用getaddrinfo函数进行转换,需要包含以下头文件
#include <winsock2.h>
#include <ws2tcpip.h>
2、添加过滤器
可以看到上面的输出已经是ip地址的vector数组了,然后使用此数组,去创建多IP的过滤器即可:
cpp
DWORD FilterFunC::AddWFPFilterArray(HANDLE engineHandle, const std::vector<std::string>& ipAddresses,GUID filterKeyDef) {
DWORD result = ERROR_SUCCESS;
FWPM_FILTER0 filter = {};
//filter.providerKey = outProviderGUID;
filter.displayData.name = const_cast<wchar_t*>(L"IP Whitelist");
filter.displayData.description = const_cast<wchar_t*>(L"Block traffic to Ips");
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; // IPv4包层
filter.weight.type = FWP_EMPTY; // 默认权重
filter.action.type = FWP_ACTION_PERMIT; // 允许,白名单
filter.numFilterConditions = ipAddresses.size();
filter.filterKey = filterKeyDef;
FWPM_FILTER_CONDITION0 condition[ipAddresses.size()];
// Initialize conditions for each IP address
for (size_t i = 0; i < ipAddresses.size(); ++i) {
condition[i].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
condition[i].matchType = FWP_MATCH_EQUAL;
condition[i].conditionValue.type = FWP_UINT32;
// Convert IP address string to DWORD
unsigned long ipAddress = inet_addr(ipAddresses[i].c_str());
if (ipAddress == INADDR_NONE) {
std::cerr << "Invalid IP address: " << ipAddresses[i] << std::endl;
return ERROR_INVALID_DATA;
}
condition[i].conditionValue.uint32 = htonl(ipAddress);
}
filter.filterCondition = condition;
result = FwpmTransactionBegin0(engineHandle, 0); // 0 - 读写事务
if (result != ERROR_SUCCESS) {
// 错误处理
ERROR_PRINT("FwpmTransactionBegin0 failed", result);
return 1;
}
result = FwpmFilterAdd0(engineHandle, &filter, NULL, NULL);
if (result != ERROR_SUCCESS) {
ERROR_PRINT("FwpmFilterAdd0 failed", result);
FwpmTransactionAbort0(engineHandle);
} else {
SUCCESS_PRINT("Filter added successfully. Traffic to is blocked.");
FwpmTransactionCommit0(engineHandle);
}
return result;
}
这部分的代码和上一篇的文章的代码有些许差别,主要是是否自定义创建filterKey 的区别,上篇文章没有定义filterKey ,使用系统自动分配,这次的功能,将自定义的filterKey 以参数的形式传递进去。此处这样的做法是为了后续定期更新过滤器做准备,因为你需要定期更新过滤器,更新的方法是先删除后创建,删除不能全部删除过滤器,有其他人的设置,所以此处我们自定义一个过滤器的filterKey ,后面删除也通过此filterKey 来进行删除。
3、定期更新过滤器
此处不做详细的代码示例,此处比较简单,要么是在再创建一个进程去定期执行这个事情,要么创建一个进行定期去解析域名,然后更新过滤器。