Open 2EXP opened 5 years ago
谢谢。根据测试,尽管pfioc_natlook
设置了 proto=IPPROTO_UDP
,但在 macOS 上仍提示找不到匹配项。
sudo pfctl -ss
倒可以列出待查找UDP,或许需要其它设定?
macOS上的pf应该是不支持udp,目前没找到办法
Привет парни! Ничего ничего что вы тут на Китайском, а я на Русском? Можно успешно получить состояния для proto=IPPROTO_UDP
из macos через DIOCGETSTATES
pfioc_states
pfsync_state
. Это не очень эффективно, но работает. 👌
Привет парни! Ничего ничего что вы тут на Китайском, а я на Русском? Можно успешно получить состояния для
proto=IPPROTO_UDP
из macos черезDIOCGETSTATES
pfioc_states
pfsync_state
. Это не очень эффективно, но работает. 👌
Wow. Is it actually working on macOS? Do you have any document references or code pieces about how to make it work?
@zonyitoo I'm writing in Go. But language doesn't matter. MacOS pf looks like 💩 But you get an idea
// LookUpUDP looks up for the specific UDP IP in the NAT table
func (p *PFCTL) LookUpUDP(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int) (net.IP, int, error) {
records, err := p.LookUpTable()
if err != nil {
return nil, 0, fmt.Errorf("unable to look up states table: %w", err)
}
for _, state := range records {
if state.Proto == syscall.IPPROTO_UDP &&
int(state.Lan.Port) == dstPort && int(state.ExtGwy.Port) == srcPort &&
bytes.Equal(state.Lan.Addr[:4], dstIP.To4()) && bytes.Equal(state.ExtGwy.Addr[:4], srcIP.To4()) {
toIP := net.IPv4(state.Gwy.Addr[0], state.Gwy.Addr[1], state.Gwy.Addr[2], state.Gwy.Addr[3])
return toIP, int(state.Gwy.Port), nil
}
}
return nil, 0, os.NewSyscallError("ioctl", fmt.Errorf("%d", int(syscall.ENOENT)))
}
// LookUpTable gets list of PF NAT states
func (p *PFCTL) LookUpTable() ([]*State, error) {
records := []*State{}
states := PFIOCStates{
BufferLength: StateBufferSize,
Buffer: &StateBuffer,
}
err := p.Read(DIOCGETSTATES, unsafe.Pointer(&states))
if err != nil {
return nil, err
}
for i := 0; i < int(states.BufferLength)/StateSize; i++ {
state := State{}
err = binary.Read(bytes.NewReader(states.Buffer[i*StateSize:(i+1)*StateSize]), binary.BigEndian, &state)
if err != nil {
return records, fmt.Errorf("unable to read state %d: %w", i, err)
}
records = append(records, &state)
}
return records, nil
}
p.Read is
// Read reads IOCTL data
func (c *IOCTL) Read(cmd uint32, ptr unsafe.Pointer) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, c.File.Fd(), uintptr(cmd), uintptr(ptr))
if errno != 0 {
return os.NewSyscallError("ioctl", fmt.Errorf("%d", int(errno)))
}
return nil
}
本文是对不同平台不同 IP packet 过滤程序透明代理实现的一些思考,主要考虑过滤规则以及如何在代理程序中获取客户端网络请求的原目标 IP 地址。
参数、函数的解释
dst_addr:所需获取的目标地址;
client_addr:客户端请求的源地址,可通过 accept() 获取;
local_addr:代理程序绑定的地址,在 bind() 调用中传入的地址;
log_printf():类比 printf(),只不过用来输出信息至日志文件,这里没有给出具体的实现。
Netfilter
应用于 Linux kernel 2.4+。
过滤规则
TCP 过滤规则用了 REDIRECT 规则,UDP 过滤规则用了 TPROXY 规则,TCP 同样也可以使用 TPROXY 规则过滤。需要注意的是,使用 TPROXY 规则时,在写代理程序时需要 setsockopt(IP_TRANSPARENT),具体见下文 TPROXY 方案获取目标 IP 地址。
设置规则可参阅 iptables(8)、Netfilter 文档。
获取目标 IP 地址
使用 TPROXY 方案,获取原目标 IP 地址只需简单调用 getsockname(),与下文通过 IPFW 的实现透明代理方案一样。
使用普通方案,则可通过调用 getsockopt(SO_ORIGINAL_DST)(IPv4 地址)和 getsockopt(IP6T_SO_ORIGINAL_DST)(IPv6 地址)获取。
Packet Filter
出自 OpenBSD,应用于 OpenBSD 3.0+、FreeBSD 5.3+、NetBSD 3.0+、Solaris 11.3+、macOS 10.7+、iOS 和 QNX。
过滤规则
以下规则应用于 TCP 过滤,UDP 过滤可以用同样方式设置,PF 规则具体设置见 pf.conf(5)、FreeBSD 相关文档。FreeBSD 中使用的 PF 是 OpenBSD 4.5 的版本。OpenBSD 可以在 pass 规则增加 rdr-to 规则,可省去第一条规则,可参阅 OpenBSD 文档。macOS 中的 PF 与 FreeBSD 的更加相似。
获取目标 IP 地址
调用 open() 以只读方式打开 PF 设备文件,如果需要动态地加入过滤规则可以使用可读写方式。之后,主要通过 ioctl(DIOCNATLOOK) 实现,其它与 PF 设备相关动态的操作可参阅 pf(4)。实际上文档中的内容还是不够详细,直接在 net/pfvar.h 看结构体的声明反而更加直观。
IPFilter
应用于 FreeBSD 2.2+、NetBSD 1.2+、Solaris 10+、illumos 和 QNX。
IPFilter 过滤规则语法看起来与 PF 差不多,我还没仔细看,FreeBSD 文档也可以作为参考。
获取目标 IP 地址流程也差不多,打开设备文件,然后 ioctl(SIOCGNATL) ,参阅 ipnat(4)。
IPFW
出自 FreeBSD,应用于 FreeBSD、macOS 10.6-(macOS 10.10 完全移除),也曾被用于 Linux kernel 1.1,为 Linux 第一代防火墙。
IPFW 过滤规则暂时还没看,可参考 FreeBSD IPFW 相关文档。
原目标 IP 地址的获取与上文 Netfilter 的 TPROXY 方案一样。
NPF
出自 NetBSD,应用于 NetBSD 6.0+。
规则设置文档
提供的部分 API 可参考文档 libnpf(3),但是并不齐全,以下用到的 npf_nat_lookup() 就没有列举在其中,直接看源码——lib/libnpf/npf.h、lib/libnpf/npf.c。
以下 get_dst_addr() 中的 dst_addr 为 inout 参数,应传入 client_addr,调用函数后其值为所需的 dst_addr。
WFP
Windows 平台暂时没发现简单易用的透明代理方案,但是或许可以通过 Windows Filtering Platform 提供的 API 解决。
WFP 相关文档