Files
proxypin/ios/ProxyPin/vpn/ConnectionHandler.swift
2024-10-16 17:12:23 +08:00

400 lines
18 KiB
Swift

//
// ConnectionHandler.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/16.
//
import Foundation
import NetworkExtension
import os.log
enum ProtocolType: UInt8 {
case icmp = 1, tcp = 6, udp = 17
}
/// Handles incoming packets and routes them to the appropriate connection.
class ConnectionHandler {
private let manager: ConnectionManager
private let writer: NEPacketTunnelFlow
private let ioService: SocketIOService
init(manager: ConnectionManager, writer: NEPacketTunnelFlow, ioService: SocketIOService) {
self.manager = manager
self.writer = writer
self.ioService = ioService
}
//Handle unknown raw IP packet data
public func handlePacket(packet: Data, version: NSNumber?) {
guard let ipHeader = IPPacketFactory.createIP4Header(data: packet) else {
os_log("Malformed IP packet", log: OSLog.default, type: .error)
return
}
if ipHeader.ipVersion != 4 {
os_log("Unsupported IP version: %d", log: OSLog.default, type: .error, ipHeader.ipVersion)
return
}
// os_log("Handling packet length:%d, protocolNumber: %d", log: OSLog.default, type: .default, packet.count, ipHeader.protocolNumber)
var clientPacketData = packet.subdata(in: IPPacketFactory.IP4_HEADER_SIZE..<packet.count)
switch ipHeader.protocolNumber {
case ProtocolType.tcp.rawValue:
handleTCPPacket(packet: clientPacketData, ipHeader: ipHeader)
case ProtocolType.udp.rawValue:
handleUDPPacket(clientPacketData: clientPacketData, ipHeader: ipHeader)
case ProtocolType.icmp.rawValue:
handleICMPPacket(clientPacketData: &clientPacketData, ipHeader: ipHeader)
default:
os_log("Unsupported IP protocol: %d", log: OSLog.default, type: .error, ipHeader.protocolNumber)
}
}
func synchronized(_ lock: AnyObject, closure: () -> Void) {
// objc_sync_enter(lock)
closure()
// objc_sync_exit(lock)
}
private func handleUDPPacket(clientPacketData: Data, ipHeader: IP4Header) {
guard let udpHeader = UDPPacketFactory.createUDPHeader(from: clientPacketData) else {
os_log("Malformed UDP packet", log: OSLog.default, type: .error)
return
}
var connection = manager.getConnection(
nwProtocol: .UDP,
ip: ipHeader.destinationIP,
port: udpHeader.destinationPort,
srcIp: ipHeader.sourceIP,
srcPort: udpHeader.sourcePort
)
let newSession = connection == nil
if connection == nil {
connection = manager.createUDPConnection(
ip: ipHeader.destinationIP,
port: udpHeader.destinationPort,
srcIp: ipHeader.sourceIP,
srcPort: udpHeader.sourcePort
)
}
guard let connection = connection else {
os_log("Failed to create UDP connection", log: OSLog.default, type: .error)
return
}
synchronized(connection) {
// os_log("Received UDP packet", log: OSLog.default, type: .default)
if newSession {
ioService.registerSession(connection: connection)
}
let payload = clientPacketData.subdata(in: UDPPacketFactory.UDP_HEADER_LENGTH..<clientPacketData.count)
connection.lastIpHeader = ipHeader
connection.lastUdpHeader = udpHeader
manager.addClientData(data: payload, connection: connection)
}
manager.keepSessionAlive(connection: connection)
}
func printByteArray(_ byteArray: Data) {
let byteArrayString = byteArray.map { String( format: "0x%02X",$0) }.joined(separator: ",")
os_log("Packet data: %{public}@", log: OSLog.default, type: .default, byteArrayString)
}
private func handleTCPPacket(packet: Data, ipHeader: IP4Header) {
guard let tcpHeader = TCPPacketFactory.createTCPHeader(data: packet) else {
os_log("Malformed TCP packet", log: OSLog.default, type: .error)
return
}
// printByteArray(packet)
let dataLength = tcpHeader.payload?.count ?? 0
let sourceIP = ipHeader.sourceIP
let destinationIP = ipHeader.destinationIP
let sourcePort = tcpHeader.sourcePort
let destinationPort = tcpHeader.destinationPort
// os_log("Handling TCP packet for %{public}@ flags:%d", log: OSLog.default, type: .default, Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort), tcpHeader.flags)
if (tcpHeader.isSYN()) {
// os_log("Received SYN packet %{public}@ seq:%u", log: OSLog.default, type: .default, Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort), tcpHeader.sequenceNumber)
// 3-way handshake + create new session
replySynAck(ipHeader: ipHeader, tcpHeader: tcpHeader)
} else if (tcpHeader.isACK()) {
let key = Connection.getConnectionKey(nwProtocol: .TCP, destIp: destinationIP, destPort: destinationPort, sourceIp: sourceIP, sourcePort: sourcePort)
// os_log("Received ACK packet for key: %{public}@", log: OSLog.default, type: .debug, key)
guard let connection = manager.getConnectionByKey(key: key) else {
os_log("Ack for unknown session: %{public}@", log: OSLog.default, type: .default, key)
if tcpHeader.isFIN() {
sendLastAck(ip: ipHeader, tcp: tcpHeader)
} else if !tcpHeader.isRST() {
sendRstPacket(ip: ipHeader, tcp: tcpHeader, dataLength: dataLength)
}
return
}
synchronized(connection) {
connection.lastIpHeader = ipHeader
connection.lastTcpHeader = tcpHeader
if dataLength > 0 {
// initProxyConnect(packet: packet, destinationIP: destinationIP, destinationPort: destinationPort, connection: connection)
// os_log("Received data packet %{public}@ length:%d seq:%u", log: OSLog.default, type: .default, connection.description, dataLength, tcpHeader.sequenceNumber)
//accumulate data from client
manager.addClientData(data: tcpHeader.payload!, connection: connection)
//send ack to client only if new data was added
sendAck(ipHeader: ipHeader, tcpHeader: tcpHeader, acceptedDataLength: dataLength, connection: connection)
} else {
// os_log("Received ACK packet %{public}@ seq:%u", log: OSLog.default, type: .default, connection.description, tcpHeader.sequenceNumber)
//an ack from client for previously sent data
acceptAck(tcpHeader: tcpHeader, connection: connection)
if connection.isClosingConnection {
sendFinAck(ipHeader: ipHeader, tcpHeader: tcpHeader, connection: connection)
} else if connection.isAckedToFin && !tcpHeader.isFIN() {
//the last ACK from client after FIN-ACK flag was sent
manager.closeConnection(nwProtocol: .TCP, ip: destinationIP, port: destinationPort, srcIp: sourceIP, srcPort: sourcePort)
}
}
//received the last segment of data from vpn client
if tcpHeader.isPSH() {
// Tell the NIO thread to immediately send data to the destination
pushDataToDestination(connection: connection, tcpHeader: tcpHeader)
} else if tcpHeader.isFIN() {
//fin from vpn client is the last packet
//ack it
ackFinAck(ipHeader: ipHeader, tcpHeader: tcpHeader, connection: connection)
} else if tcpHeader.isRST() {
resetTCPConnection(ip: ipHeader, tcp: tcpHeader)
}
if !connection.isAbortingConnection {
manager.keepSessionAlive(connection: connection)
}
}
} else if tcpHeader.isFIN() {
os_log("Received FIN packet %{public}@:%d seq:%u", log: OSLog.default, type: .default, PacketUtil.intToIPAddress(destinationIP), destinationPort, tcpHeader.sequenceNumber)
//case client sent FIN without ACK
guard let connection = manager.getConnection(nwProtocol: .TCP, ip: destinationIP, port: destinationPort, srcIp: sourceIP, srcPort: sourcePort) else {
ackFinAck(ipHeader: ipHeader, tcpHeader: tcpHeader, connection: nil)
return
}
manager.keepSessionAlive(connection: connection)
} else if tcpHeader.isRST() {
os_log("Received RST packet %{public}@:%d seq:%u", log: OSLog.default, type: .debug, PacketUtil.intToIPAddress(destinationIP), destinationPort, tcpHeader.sequenceNumber)
resetTCPConnection(ip: ipHeader, tcp: tcpHeader)
} else {
os_log("Unknown TCP flag", log: OSLog.default, type: .error)
}
}
//set connection as aborting so that background worker will close it.
func resetTCPConnection(ip: IP4Header, tcp: TCPHeader) {
let session = manager.getConnection(nwProtocol: .TCP, ip: ip.destinationIP, port: tcp.destinationPort, srcIp: ip.sourceIP, srcPort: tcp.sourcePort)
if let session = session {
session.isAbortingConnection = true
}
}
func ackFinAck(ipHeader: IP4Header, tcpHeader: TCPHeader, connection: Connection?) {
let ackNumber = tcpHeader.sequenceNumber + 1
let seqNumber = tcpHeader.ackNumber
let finAckData = TCPPacketFactory.createFinAckData(ipHeader: ipHeader, tcpHeader: tcpHeader, ackToClient: ackNumber, seqToClient: seqNumber, isFin: true, isAck: true)
write(data: finAckData)
// os_log("Sent FIN-ACK packet ack# %{public}d, seq# %{public}d", log: OSLog.default, type: .default, ackNumber, seqNumber)
if let connection = connection {
manager.closeConnection(connection: connection)
}
}
func pushDataToDestination(connection: Connection, tcpHeader: TCPHeader) {
connection.timestampReplyTo = tcpHeader.timeStampSender
connection.timestampSender = Int(Date().timeIntervalSince1970)
}
func sendFinAck(ipHeader: IP4Header, tcpHeader: TCPHeader, connection: Connection) {
let ackNumber = tcpHeader.sequenceNumber
let seqNumber = tcpHeader.ackNumber
let finAckData = TCPPacketFactory.createFinAckData(ipHeader: ipHeader, tcpHeader: tcpHeader, ackToClient: ackNumber, seqToClient: seqNumber, isFin: true, isAck: false)
write(data: finAckData)
connection.sendNext = seqNumber + 1
connection.isClosingConnection = false
}
//acknowledge a packet.
func acceptAck(tcpHeader: TCPHeader, connection: Connection) {
let isCorrupted = PacketUtil.isPacketCorrupted(tcpHeader: tcpHeader)
if isCorrupted {
os_log("Packet is corrupted", log: OSLog.default, type: .error)
}
if (tcpHeader.sequenceNumber > connection.recSequence) {
connection.recSequence = tcpHeader.sequenceNumber
}
if tcpHeader.ackNumber >= connection.sendUnAck - 1 || tcpHeader.ackNumber == connection.sendNext {
connection.sendUnAck = tcpHeader.ackNumber
connection.timestampReplyTo = tcpHeader.timeStampSender
connection.timestampSender = Int(Date().timeIntervalSince1970)
} else {
os_log("%{public}@ Not accepting ack# %d, it should be: %d", log: OSLog.default, type: .error, connection.description ,tcpHeader.ackNumber, connection.sendNext)
os_log("%{public}@ Previous sendUnAck: %d", log: OSLog.default, type: .error, connection.description, connection.sendUnAck)
}
}
func sendAckForDisorder(ipHeader: IP4Header, tcpHeader: TCPHeader, acceptedDataLength: Int) {
let ackNumber = tcpHeader.sequenceNumber + UInt32(acceptedDataLength)
// os_log("Sent disorder ack, ack# %{public}d", log: OSLog.default, type: .debug, ackNumber)
let ackData = TCPPacketFactory.createResponseAckData(ipHeader: ipHeader, tcpHeader: tcpHeader, ackToClient: ackNumber)
write(data: ackData)
}
func sendAck(ipHeader: IP4Header, tcpHeader: TCPHeader, acceptedDataLength: Int, connection: Connection) {
synchronized(connection) {
let ackNumber = (tcpHeader.sequenceNumber + UInt32(acceptedDataLength)) % UInt32.max
connection.recSequence = ackNumber
let ackData = TCPPacketFactory.createResponseAckData(ipHeader: ipHeader, tcpHeader: tcpHeader, ackToClient: ackNumber)
self.write(data: ackData)
// os_log("Sent ACK packet to client %{public}@:%{public}d ack# %{public}d", log: OSLog.default, type: .debug, PacketUtil.intToIPAddress(ipHeader.destinationIP), tcpHeader.destinationPort, ackNumber)
}
}
private func sendLastAck(ip: IP4Header, tcp: TCPHeader) {
let data = TCPPacketFactory.createResponseAckData(ipHeader: ip, tcpHeader: tcp, ackToClient: tcp.sequenceNumber + 1)
self.write(data: data)
os_log("Sent last ACK Packet to client with dest => %{public}@:%{public}d", log: OSLog.default, type: .debug, PacketUtil.intToIPAddress(ip.destinationIP), tcp.destinationPort)
}
private func sendRstPacket(ip: IP4Header, tcp: TCPHeader, dataLength: Int) {
let data = TCPPacketFactory.createRstData(ipHeader: ip, tcpHeader: tcp, dataLength: dataLength)
self.write(data: data)
os_log("Sent RST Packet to client with dest => %{public}@:%{public}d", log: OSLog.default, type: .debug, PacketUtil.intToIPAddress(ip.destinationIP), tcp.destinationPort)
}
//create a new client's session and SYN-ACK packet data to respond to client
private func replySynAck(ipHeader: IP4Header, tcpHeader: TCPHeader) -> Void {
ipHeader.identification = 0
let packet = TCPPacketFactory.createSynAckPacketData(ipHeader: ipHeader, tcpHeader: tcpHeader)
guard let tcpTransport = packet.transportHeader as? TCPHeader else {
os_log("Failed to extract TCP header from packet", log: OSLog.default, type: .error)
return
}
let connection = manager.createTCPConnection(
ip: ipHeader.destinationIP,
port: tcpHeader.destinationPort,
srcIp: ipHeader.sourceIP,
srcPort: tcpHeader.sourcePort
)
if connection.lastIpHeader != nil {
resendAck(connection: connection)
return
}
synchronized(connection) {
connection.maxSegmentSize = Int(tcpTransport.maxSegmentSize)
connection.sendUnAck = tcpTransport.sequenceNumber
connection.sendNext = tcpTransport.sequenceNumber + 1
//client initial sequence has been incremented by 1 and set to ack
connection.recSequence = tcpTransport.ackNumber
connection.lastIpHeader = ipHeader
connection.lastTcpHeader = tcpHeader
if connection.isInitConnect {
self.ioService.registerSession(connection: connection)
}
self.write(data: packet.buffer)
// os_log("SYN-ACK packet length:%d sent", log: OSLog.default, type: .default, packet.buffer.count)
}
}
/**
* resend the last acknowledgment packet to VPN client, e.g. when an unexpected out of order
* packet arrives.
*/
private func resendAck(connection: Connection) {
let data = TCPPacketFactory.createResponseAckData(
ipHeader: connection.lastIpHeader!,
tcpHeader: connection.lastTcpHeader!,
ackToClient: connection.recSequence
)
// os_log("Resending ACK packet %{public}@ ackToClient: %d", log: OSLog.default, type: .default, connection.description, connection.recSequence)
self.write(data: data)
}
private func write(data: Data) {
self.writer.writePackets([data], withProtocols: [NSNumber(value: AF_INET)])
}
private func handleICMPPacket(clientPacketData: inout Data, ipHeader: IP4Header) {
guard let requestPacket = ICMPPacketFactory.parseICMPPacket(&clientPacketData) else {
os_log("Failed to parse ICMP packet", log: OSLog.default, type: .error)
return
}
// os_log("Handling ICMP packet type: %d", log: OSLog.default, type: .default, requestPacket.type)
if requestPacket.type == ICMPPacket.DESTINATION_UNREACHABLE_TYPE {
// This is a packet from the phone, telling somebody that a destination is unreachable.
// Might be caused by issues on our end, but it's unclear what kind of issues. Regardless,
// we can't send ICMP messages ourselves or react usefully, so we drop these silently.
return
} else if requestPacket.type != ICMPPacket.ECHO_REQUEST_TYPE {
// We only actually support outgoing ping packets. Loudly drop anything else:
os_log("Unknown ICMP type: %d", log: OSLog.default, type: .error, requestPacket.type)
return
}
QueueFactory.instance.getQueue().async {
if !self.isReachable(ipAddress: PacketUtil.intToIPAddress(ipHeader.destinationIP)) {
os_log("Failed ping, ignoring", log: OSLog.default, type: .default)
return
}
let response = ICMPPacketFactory.buildSuccessPacket(requestPacket)
// Flip the address
let destination = ipHeader.destinationIP
let source = ipHeader.sourceIP
ipHeader.sourceIP = destination
ipHeader.destinationIP = source
let responseData = ICMPPacketFactory.packetToBuffer(ipHeader: ipHeader, packet: response)
os_log("Successful ping response", log: OSLog.default, type: .default)
self.write(data: responseData)
}
}
private func isReachable(ipAddress: String) -> Bool {
do {
return true
// return try InetAddress.getByName(ipAddress).isReachable(timeout: 10000)
} catch {
return false
}
}
}