在 Objective-C 中,是否有一种优雅的方式将 NSFileHandle 对象的整个输入转发到另一个 NSFileHandle 对象?也许处理程序?
In Objective-C, is there an elegant way to forward the entire input of a NSFileHandle object to another NSFileHandle object ? Perhaps handlers?
我正在 Objective-C 开发 macOS 网络应用程序。我必须以某种方式过滤 NSFileHandle 对象的输入并将其转发到另一个 NSFileHandle 对象。只是为了开始学习一些东西,我想将整个对象 1 输入转发到对象 2 输出。有没有一种干净的方法,考虑到它直接处理网络数据包,它也必须很快?在等待可能的答案时,我认为最好的办法是仔细阅读 this 文档。我相信它允许以极快的速度在最低级别执行此任务,但它提到了我不太熟悉的技术,尤其是 Core Foundation API。我不是被迫使用 NSFileHandle,我从套接字描述符开始,所以我相信我可以使用任何提到的 API。我唯一关心的是我需要的是一种简单的方法来检测套接字上是否有可用数据,因为我必须与一些使用以下代码的现有代码库协调:
ssize_t actual = ::read(fileDescriptor, buffer.data(), mtu);
当有可用数据时直接从套接字读取。我试图将上面的代码放在一个用套接字描述符实例化的 NSFileHandle 的可读性处理程序中,这有点老套,因为正确的方法是在可读性处理程序中调用 [handle availableData],但这是我来的最好的跟上。我收到了从套接字读取的所有数据,然后我使用 ::sendto 写入输出,其中 returns 是一个有意义的大小值,但是数据包没有正常工作。我暂时不知道为什么。
任何帮助是极大的赞赏。谢谢
编辑:
从接受的答案中学习后,我得出了以下代码:
- (void)monitorSocket:(uintptr_t)fileDescriptor {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileDescriptor, 0, queue);
if(!readSource) {
// handle error
NSLog(@"Handle error");
} else {
self.theSource = readSource;
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource);
// No data
if(estimated == 0) {
NSLog(@"Here we are.");
return;
}
BOOL done = NO;
void *buffer2 = malloc(estimated);
if(buffer2) {
const int mtu = self.currentTunnel.mtu;
std::vector<unsigned char> buffer(mtu);
buffer.resize(mtu);
ssize_t actual = ::read(fileDescriptor, buffer.data(), mtu);
buffer.resize(actual);
if(actual == -1)
{
qWarning() << "Unable to read from split tunnel device:" << "ErrnoTracer{errno}";
return;
}
// First 4 bytes indicate address family (IPv4 or IPv6)
int addressFamily = ntohl(*reinterpret_cast<int *>(buffer.data()));
switch(addressFamily)
{
case AF_INET: {
ssize_t effective = [self handleIp4:std::move(buffer) actualSize:actual socketDescriptor:self.currentTunnel._rawFd4];
if (effective == (actual - 4)) {
done = YES;
}
break;
}
case AF_INET6:
//handleIp6(std::move(buffer), actual);
break;
default:
qWarning() << "Unsupported address family:" << addressFamily;
}
free(buffer2);
// If there is no more data, cancel the source.
if(done) {
dispatch_source_cancel(readSource);
}
}
});
// Install the cancellation handler
// (close may not be appropriate for your use case)
dispatch_source_set_cancel_handler(readSource, ^{
dispatch_async( dispatch_get_main_queue(), ^{
[self monitorSocket:fileDescriptor];
});
});
// Start reading
dispatch_resume(readSource);
// Keep a reference to readSource until it's no longer needed
}
- (ssize_t)handleIp4:(std::vector<unsigned char>)buffer actualSize: (ssize_t)actualSize socketDescriptor:(int)socketDescriptor
{
// skip the first 4 bytes (it stores AF_NET)
auto pPacket = Packet::createFromData(std::move(buffer), 4);
if(!pPacket)
{
qWarning() << "Packet is invalid; read" << actualSize << "bytes from stun";
return 0;
}
// PidFinder bypassFinder{_excludedApps};
// PidFinder vpnOnlyFinder{_vpnOnlyApps};
// PiaConnections piaConnections{Path::ExecutableDir, this};
// Update the cache for non-split apps, to keep track of the ports we care about
// when generating firewall rules
// _defaultAppsCache.refresh(IPv4);
// Get ports for our tracked apps
// auto bypassPorts = bypassFinder.ports(bypassFinder.pids(), PidFinder::IPv4);
// auto vpnOnlyPorts = vpnOnlyFinder.ports(vpnOnlyFinder.pids(), PidFinder::IPv4);
// auto defaultPorts = _defaultAppsCache.ports(IPv4);
// These packets seem to have protocol 255, so drop them
if(pPacket->packetType() == Packet::Other)
return 0;
// Update with our pia-specific connections
// bypassPorts += piaConnections.bypassPorts();
// vpnOnlyPorts += piaConnections.vpnOnlyPorts();
// // Drop vpnOnly packets when not connected
// if(!_params.isConnected && pPacket->sourcePort() && vpnOnlyPorts.contains(pPacket->sourcePort()))
// {
// qInfo() << "Dropping an Ipv4 vpnOnly packet";
// return;
// }
// We only add a (non-split) app cache entry if the port wasn't associated with
// a bypass or vpnonly app
// if(pPacket->packetType() != Packet::Other && !isSplitPort(pPacket->sourcePort(), bypassPorts, vpnOnlyPorts))
// {
// pid_t newPid = bypassFinder.pidForPort(pPacket->sourcePort(), PidFinder::IPv4);
// if(newPid)
// _defaultAppsCache.addEntry(IPv4, newPid, pPacket->sourcePort());
// else
// // We could not find an associated PID for the packet, so drop it.
// // We drop a packet by just returning since a packet only goes further if it's re-injected
// return;
// }
// Drop multicast/broadcast and self-addressed packets
const auto destAddress = QHostAddress { pPacket->destAddress() };
// Drop multicast/broadcast and self-addressed packets
if(destAddress.isMulticast() || destAddress.isBroadcast() || [destAddress.toString().toNSString() isEqualToString:self.currentTunnel.splitTunnelIP.ipv4AddressString]) {
return 0; // We drop a packet by just returning
}
bool conversionOK = false;
QHostAddress ip4Address(destAddress.toIPv4Address(&conversionOK));
QString ip4String;
if (conversionOK)
{
ip4String = ip4Address.toString();
}
NSString *test = ip4String.toNSString();
NSLog(@"Destination Address is %@",test);
if (![test isEqualToString:@"147.75.47.199"]) {
return 0;
}
const AddressPair newPacketAddress{pPacket->sourceAddress(), pPacket->sourcePort()};
if(self._lastPacketAddress4 == newPacketAddress)
++self._lastPacketCount4;
else
self._lastPacketCount4 = 0;
if(self._lastPacketCount4 > 10)
{
qInfo() << "Received repeated packet (10 times), dropping" << pPacket->toString();
return 0;
}
// Prevent default traffic when KS=always and disconnected
// All other traffic is fine - vpnOnly is blocked anyway and bypass is allowed
// if(_params.blockAll && !_params.isConnected)
// defaultPorts.clear();
//_defaultRuleUpdater.update(IPv4, defaultPorts);
//_bypassRuleUpdater.update(IPv4, bypassPorts);
//_vpnOnlyRuleUpdater.update(IPv4, vpnOnlyPorts);
self._lastPacketAddress4 = newPacketAddress;
qInfo() << "Re-injecting IPv4 packet:" << pPacket->toString();
// Re-inject the packet
struct sockaddr_in to{};
to.sin_family = AF_INET;
to.sin_addr.s_addr = htonl(pPacket->destAddress());
ssize_t effectiveTransmittedSize = ::sendto(socketDescriptor, pPacket->toRaw(), pPacket->len() , 0, reinterpret_cast<sockaddr *>(&to), sizeof(to));
NSLog(@"Eccoci");
if(effectiveTransmittedSize == -1)
{
qWarning() << "Unable to reinject packet" << pPacket->toString() << "-"
<< "ErrnoTracer{errno}";
qWarning() << "Packet -" << pPacket->len() << "bytes";
std::uint32_t *pPktWords = reinterpret_cast<std::uint32_t*>(pPacket->toRaw());
for(int i=0; i+4 <= pPacket->len(); i += 4)
{
qWarning() << QString::asprintf("%03d", i) << QString::asprintf("%08X", pPktWords[i/4]);
}
if(pPacket->len() % 4)
{
std::uint8_t *pTailBytes = reinterpret_cast<std::uint8_t*>(pPacket->toRaw());
unsigned lastWordOffset = pPacket->len() / 4;
pTailBytes += lastWordOffset * 4;
std::uint32_t lastWord = 0;
lastWord |= pTailBytes[0];
lastWord <<= 8;
if(pPacket->len() % 4 >= 2)
lastWord |= pTailBytes[1];
lastWord <<= 8;
if(pPacket->len() % 4 >= 3)
lastWord |= pTailBytes[2];
lastWord <<= 8;
qWarning() << QString::asprintf("%03d", lastWordOffset*4) << QString::asprintf("%08X", lastWord);
}
} else {
NSBeep();
return effectiveTransmittedSize;
}
return 0;
}
这是一个C++ Qt项目移植的第一个版本,我目前的目标是建立stun设备,然后curl 147.75.47.199并获得我的public IP回来。这就是为什么目前有很多代码被注释的原因。非常感谢你帮助我,真的很感激。
因为你不是被迫使用 NSFileHandle
一个选项是文件描述符 read dispatch source:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
if(!readSource) {
// handle error
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource);
// If estimated is 0 malloc may return NULL which is not an error
void *buffer = malloc(estimated);
if(!buffer && estimated > 0) {
// handle error
}
ssize_t actual = read(fd, buffer, estimated);
// Process/send the data
BOOL done = MyProcessData(buffer, actual);
free(buffer);
// If there is no more data, cancel the source.
if(done)
dispatch_source_cancel(readSource);
}
});
// Install the cancellation handler
// (close may not be appropriate for your use case)
dispatch_source_set_cancel_handler(readSource, ^{close(fd);});
// Start reading
dispatch_resume(readSource);
// Keep a reference to readSource until it's no longer needed
我正在 Objective-C 开发 macOS 网络应用程序。我必须以某种方式过滤 NSFileHandle 对象的输入并将其转发到另一个 NSFileHandle 对象。只是为了开始学习一些东西,我想将整个对象 1 输入转发到对象 2 输出。有没有一种干净的方法,考虑到它直接处理网络数据包,它也必须很快?在等待可能的答案时,我认为最好的办法是仔细阅读 this 文档。我相信它允许以极快的速度在最低级别执行此任务,但它提到了我不太熟悉的技术,尤其是 Core Foundation API。我不是被迫使用 NSFileHandle,我从套接字描述符开始,所以我相信我可以使用任何提到的 API。我唯一关心的是我需要的是一种简单的方法来检测套接字上是否有可用数据,因为我必须与一些使用以下代码的现有代码库协调:
ssize_t actual = ::read(fileDescriptor, buffer.data(), mtu);
当有可用数据时直接从套接字读取。我试图将上面的代码放在一个用套接字描述符实例化的 NSFileHandle 的可读性处理程序中,这有点老套,因为正确的方法是在可读性处理程序中调用 [handle availableData],但这是我来的最好的跟上。我收到了从套接字读取的所有数据,然后我使用 ::sendto 写入输出,其中 returns 是一个有意义的大小值,但是数据包没有正常工作。我暂时不知道为什么。 任何帮助是极大的赞赏。谢谢
编辑:
从接受的答案中学习后,我得出了以下代码:
- (void)monitorSocket:(uintptr_t)fileDescriptor {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileDescriptor, 0, queue);
if(!readSource) {
// handle error
NSLog(@"Handle error");
} else {
self.theSource = readSource;
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource);
// No data
if(estimated == 0) {
NSLog(@"Here we are.");
return;
}
BOOL done = NO;
void *buffer2 = malloc(estimated);
if(buffer2) {
const int mtu = self.currentTunnel.mtu;
std::vector<unsigned char> buffer(mtu);
buffer.resize(mtu);
ssize_t actual = ::read(fileDescriptor, buffer.data(), mtu);
buffer.resize(actual);
if(actual == -1)
{
qWarning() << "Unable to read from split tunnel device:" << "ErrnoTracer{errno}";
return;
}
// First 4 bytes indicate address family (IPv4 or IPv6)
int addressFamily = ntohl(*reinterpret_cast<int *>(buffer.data()));
switch(addressFamily)
{
case AF_INET: {
ssize_t effective = [self handleIp4:std::move(buffer) actualSize:actual socketDescriptor:self.currentTunnel._rawFd4];
if (effective == (actual - 4)) {
done = YES;
}
break;
}
case AF_INET6:
//handleIp6(std::move(buffer), actual);
break;
default:
qWarning() << "Unsupported address family:" << addressFamily;
}
free(buffer2);
// If there is no more data, cancel the source.
if(done) {
dispatch_source_cancel(readSource);
}
}
});
// Install the cancellation handler
// (close may not be appropriate for your use case)
dispatch_source_set_cancel_handler(readSource, ^{
dispatch_async( dispatch_get_main_queue(), ^{
[self monitorSocket:fileDescriptor];
});
});
// Start reading
dispatch_resume(readSource);
// Keep a reference to readSource until it's no longer needed
}
- (ssize_t)handleIp4:(std::vector<unsigned char>)buffer actualSize: (ssize_t)actualSize socketDescriptor:(int)socketDescriptor
{
// skip the first 4 bytes (it stores AF_NET)
auto pPacket = Packet::createFromData(std::move(buffer), 4);
if(!pPacket)
{
qWarning() << "Packet is invalid; read" << actualSize << "bytes from stun";
return 0;
}
// PidFinder bypassFinder{_excludedApps};
// PidFinder vpnOnlyFinder{_vpnOnlyApps};
// PiaConnections piaConnections{Path::ExecutableDir, this};
// Update the cache for non-split apps, to keep track of the ports we care about
// when generating firewall rules
// _defaultAppsCache.refresh(IPv4);
// Get ports for our tracked apps
// auto bypassPorts = bypassFinder.ports(bypassFinder.pids(), PidFinder::IPv4);
// auto vpnOnlyPorts = vpnOnlyFinder.ports(vpnOnlyFinder.pids(), PidFinder::IPv4);
// auto defaultPorts = _defaultAppsCache.ports(IPv4);
// These packets seem to have protocol 255, so drop them
if(pPacket->packetType() == Packet::Other)
return 0;
// Update with our pia-specific connections
// bypassPorts += piaConnections.bypassPorts();
// vpnOnlyPorts += piaConnections.vpnOnlyPorts();
// // Drop vpnOnly packets when not connected
// if(!_params.isConnected && pPacket->sourcePort() && vpnOnlyPorts.contains(pPacket->sourcePort()))
// {
// qInfo() << "Dropping an Ipv4 vpnOnly packet";
// return;
// }
// We only add a (non-split) app cache entry if the port wasn't associated with
// a bypass or vpnonly app
// if(pPacket->packetType() != Packet::Other && !isSplitPort(pPacket->sourcePort(), bypassPorts, vpnOnlyPorts))
// {
// pid_t newPid = bypassFinder.pidForPort(pPacket->sourcePort(), PidFinder::IPv4);
// if(newPid)
// _defaultAppsCache.addEntry(IPv4, newPid, pPacket->sourcePort());
// else
// // We could not find an associated PID for the packet, so drop it.
// // We drop a packet by just returning since a packet only goes further if it's re-injected
// return;
// }
// Drop multicast/broadcast and self-addressed packets
const auto destAddress = QHostAddress { pPacket->destAddress() };
// Drop multicast/broadcast and self-addressed packets
if(destAddress.isMulticast() || destAddress.isBroadcast() || [destAddress.toString().toNSString() isEqualToString:self.currentTunnel.splitTunnelIP.ipv4AddressString]) {
return 0; // We drop a packet by just returning
}
bool conversionOK = false;
QHostAddress ip4Address(destAddress.toIPv4Address(&conversionOK));
QString ip4String;
if (conversionOK)
{
ip4String = ip4Address.toString();
}
NSString *test = ip4String.toNSString();
NSLog(@"Destination Address is %@",test);
if (![test isEqualToString:@"147.75.47.199"]) {
return 0;
}
const AddressPair newPacketAddress{pPacket->sourceAddress(), pPacket->sourcePort()};
if(self._lastPacketAddress4 == newPacketAddress)
++self._lastPacketCount4;
else
self._lastPacketCount4 = 0;
if(self._lastPacketCount4 > 10)
{
qInfo() << "Received repeated packet (10 times), dropping" << pPacket->toString();
return 0;
}
// Prevent default traffic when KS=always and disconnected
// All other traffic is fine - vpnOnly is blocked anyway and bypass is allowed
// if(_params.blockAll && !_params.isConnected)
// defaultPorts.clear();
//_defaultRuleUpdater.update(IPv4, defaultPorts);
//_bypassRuleUpdater.update(IPv4, bypassPorts);
//_vpnOnlyRuleUpdater.update(IPv4, vpnOnlyPorts);
self._lastPacketAddress4 = newPacketAddress;
qInfo() << "Re-injecting IPv4 packet:" << pPacket->toString();
// Re-inject the packet
struct sockaddr_in to{};
to.sin_family = AF_INET;
to.sin_addr.s_addr = htonl(pPacket->destAddress());
ssize_t effectiveTransmittedSize = ::sendto(socketDescriptor, pPacket->toRaw(), pPacket->len() , 0, reinterpret_cast<sockaddr *>(&to), sizeof(to));
NSLog(@"Eccoci");
if(effectiveTransmittedSize == -1)
{
qWarning() << "Unable to reinject packet" << pPacket->toString() << "-"
<< "ErrnoTracer{errno}";
qWarning() << "Packet -" << pPacket->len() << "bytes";
std::uint32_t *pPktWords = reinterpret_cast<std::uint32_t*>(pPacket->toRaw());
for(int i=0; i+4 <= pPacket->len(); i += 4)
{
qWarning() << QString::asprintf("%03d", i) << QString::asprintf("%08X", pPktWords[i/4]);
}
if(pPacket->len() % 4)
{
std::uint8_t *pTailBytes = reinterpret_cast<std::uint8_t*>(pPacket->toRaw());
unsigned lastWordOffset = pPacket->len() / 4;
pTailBytes += lastWordOffset * 4;
std::uint32_t lastWord = 0;
lastWord |= pTailBytes[0];
lastWord <<= 8;
if(pPacket->len() % 4 >= 2)
lastWord |= pTailBytes[1];
lastWord <<= 8;
if(pPacket->len() % 4 >= 3)
lastWord |= pTailBytes[2];
lastWord <<= 8;
qWarning() << QString::asprintf("%03d", lastWordOffset*4) << QString::asprintf("%08X", lastWord);
}
} else {
NSBeep();
return effectiveTransmittedSize;
}
return 0;
}
这是一个C++ Qt项目移植的第一个版本,我目前的目标是建立stun设备,然后curl 147.75.47.199并获得我的public IP回来。这就是为什么目前有很多代码被注释的原因。非常感谢你帮助我,真的很感激。
因为你不是被迫使用 NSFileHandle
一个选项是文件描述符 read dispatch source:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
if(!readSource) {
// handle error
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource);
// If estimated is 0 malloc may return NULL which is not an error
void *buffer = malloc(estimated);
if(!buffer && estimated > 0) {
// handle error
}
ssize_t actual = read(fd, buffer, estimated);
// Process/send the data
BOOL done = MyProcessData(buffer, actual);
free(buffer);
// If there is no more data, cancel the source.
if(done)
dispatch_source_cancel(readSource);
}
});
// Install the cancellation handler
// (close may not be appropriate for your use case)
dispatch_source_set_cancel_handler(readSource, ^{close(fd);});
// Start reading
dispatch_resume(readSource);
// Keep a reference to readSource until it's no longer needed