ios IP Layer Proxy Beta (#215)

This commit is contained in:
wanghongenpin
2024-10-09 02:30:02 +08:00
parent aded2b7f37
commit 86d342cb07
38 changed files with 2458 additions and 84 deletions

View File

@@ -6,10 +6,12 @@
//
import NetworkExtension
import Network
import os.log
class PacketTunnelProvider: NEPacketTunnelProvider {
private var proxyVpnService: ProxyVpnService?
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
NSLog("startTunnel")
@@ -17,11 +19,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
NSLog("[ERROR] No ProtocolConfiguration Found")
exit(EXIT_FAILURE)
}
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
NSLog(conf.debugDescription)
//http
let host = conf["proxyHost"] as! String
let proxyPort = conf["proxyPort"] as! Int
let ipProxy = conf["ipProxy"] as! Bool? ?? false
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
// let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host)
NSLog(conf.debugDescription)
//http
let proxySettings = NEProxySettings()
proxySettings.httpEnabled = true
proxySettings.httpServer = NEProxyServer(address: host, port: proxyPort)
@@ -30,26 +36,49 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
proxySettings.matchDomains = [""]
networkSettings.proxySettings = proxySettings
networkSettings.mtu = 1500
networkSettings.mtu = 1480
let ipv4Settings = NEIPv4Settings(addresses: ["10.0.0.2"], subnetMasks: ["255.255.255.255"])
if (ipProxy){
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
// ipv4Settings.excludedRoutes = [
// NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
// NEIPv4Route(destinationAddress: "100.64.0.0", subnetMask: "255.192.0.0"),
// NEIPv4Route(destinationAddress: "127.0.0.0", subnetMask: "255.0.0.0"),
// NEIPv4Route(destinationAddress: "169.254.0.0", subnetMask: "255.255.0.0"),
// NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0"),
// NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
// NEIPv4Route(destinationAddress: "17.0.0.0", subnetMask: "255.0.0.0"),
// ]
let dns = "114.114.114.114,8.8.8.8"
let dnsSettings = NEDNSSettings(servers: dns.components(separatedBy: ","))
dnsSettings.matchDomains = [""]
networkSettings.dnsSettings = dnsSettings
}
networkSettings.ipv4Settings = ipv4Settings
setTunnelNetworkSettings(networkSettings) {
error in
setTunnelNetworkSettings(networkSettings) { error in
guard error == nil else {
NSLog(error.debugDescription)
NSLog("startTunnel Encountered an error setting up the network: \(error.debugDescription)")
completionHandler(error)
return
}
completionHandler(nil)
if (ipProxy){
let proxyAddress = Network.NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(rawValue: UInt16(proxyPort))!)
self.proxyVpnService = ProxyVpnService(packetFlow: self.packetFlow, proxyAddress: proxyAddress)
self.proxyVpnService!.start()
}
completionHandler(nil)
}
NSLog("startTunnelend")
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
proxyVpnService?.stop()
completionHandler()
}

View File

@@ -0,0 +1,105 @@
//
// Connection.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
import Foundation
import Network
import os.log
class Connection{
var nwProtocol: NWProtocol
var sourceIp: UInt32
var sourcePort: UInt16
var destinationIp: UInt32
var destinationPort: UInt16
var channel: NWConnection?
var isInitConnect: Bool = false
var isConnected: Bool = false
var isClosingConnection: Bool = false
var isAbortingConnection: Bool = false
var isAckedToFin: Bool = false
private let connectionCloser: ConnectionManager
init(nwProtocol: NWProtocol, sourceIp: UInt32, sourcePort: UInt16, destinationIp: UInt32, destinationPort: UInt16, connectionCloser: ConnectionManager) {
self.nwProtocol = nwProtocol
self.sourceIp = sourceIp
self.sourcePort = sourcePort
self.destinationIp = destinationIp
self.destinationPort = destinationPort
self.connectionCloser = connectionCloser
}
//vpn
var sendBuffer = Data()
var hasReceivedLastSegment = false
//
var lastIpHeader: IP4Header?
var lastTcpHeader: TCPHeader?
var lastUdpHeader: UDPHeader?
var timestampSender = 0
var timestampReplyTo = 0
//
var recSequence: UInt32 = 0
//tcpSYN
var maxSegmentSize = 0
//ackack
var sendUnAck: UInt32 = 0
//ack
var sendNext: UInt32 = 0
static func getConnectionKey(nwProtocol: NWProtocol, destIp: UInt32, destPort: UInt16, sourceIp: UInt32, sourcePort: UInt16) -> String {
let destIpString = PacketUtil.intToIPAddress(destIp)
let sourceIpString = PacketUtil.intToIPAddress(sourceIp)
return "\(nwProtocol)|\(sourceIpString):\(sourcePort)->\(destIpString):\(destPort)"
}
func closeConnection() {
connectionCloser.closeConnection(connection: self)
}
func addSendData(data: Data) {
QueueFactory.instance.getQueue().sync {
// sendBuffer.append(data)
// if (channel?.state != .ready) {
// return
// }
self.sendToDestination(data: data)
}
}
//
func sendToDestination(data: Data) {
// QueueFactory.instance.getQueue().sync {
// os_log("Sending data to destination key %{public}@", log: OSLog.default, type: .debug, self.description)
//,
self.channel?.send(content: data, completion: .contentProcessed { error in
if let error = error {
os_log("Error sending data to destination %{public}@: %{public}@", log: OSLog.default, type: .error, self.description, error.localizedDescription)
return
}
})
// }
}
var description: String {
return Connection.getConnectionKey(nwProtocol: nwProtocol, destIp: destinationIp, destPort: destinationPort, sourceIp: sourceIp, sourcePort: sourcePort)
}
}

View File

@@ -0,0 +1,399 @@
//
// 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 ack# %{public}u", log: OSLog.default, type: .default, 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
}
}
}

View File

@@ -0,0 +1,159 @@
//
// ConnectionManager.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/16.
//
import Foundation
import Network
import os.log
//VPN
class ConnectionManager : CloseableConnection{
//static let instance = ConnectionManager()
private var table: [String: Connection] = [:]
private let tableQueue = DispatchQueue(label: "ProxyPin.ConnectionManager")
public var proxyAddress: NWEndpoint?
private let defaultPorts: [UInt16] = [80, 443]
// private init() {}
func getConnection(nwProtocol: NWProtocol, ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection? {
let key = Connection.getConnectionKey(nwProtocol: nwProtocol, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort)
return getConnectionByKey(key: key)
}
func getConnectionByKey(key: String) -> Connection? {
return tableQueue.sync {
return table[key]
}
}
func createTCPConnection(ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection {
let key = Connection.getConnectionKey(nwProtocol: .TCP, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort)
return tableQueue.sync {
if let existingConnection = table[key] {
return existingConnection
}
let connection = Connection(nwProtocol: .TCP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self)
let parameters = NWParameters.tcp
parameters.allowLocalEndpointReuse = true
parameters.includePeerToPeer = true
let endpoint: NWEndpoint
if defaultPorts.contains(port) {
endpoint = proxyAddress!
} else {
let ipString = PacketUtil.intToIPAddress(ip)
endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(ipString), port: NWEndpoint.Port(rawValue: port)!)
}
let nwConnection = NWConnection(to: endpoint, using: parameters)
connection.channel = nwConnection
connection.isInitConnect = true
tableQueue.async(flags: .barrier) {
self.table[key] = connection
}
os_log("Created TCP connection %{public}@", log: OSLog.default, type: .default, key)
return connection
}
// nwConnection.stateUpdateHandler = { state in
// switch state {
// case .ready:
// connection.isConnected = true
// os_log("Connected to %{public}@", log: OSLog.default, type: .debug, endpoint.debugDescription)
// case .failed(let error):
// connection.isConnected = false
// os_log("Failed to connect: %{public}@", log: OSLog.default, type: .error, error.localizedDescription)
//
// default:
// break
// }
// }
}
func createUDPConnection(ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection {
let key = Connection.getConnectionKey(nwProtocol: .UDP, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort)
return tableQueue.sync {
if let existingConnection = table[key] {
return existingConnection
}
let connection = Connection(nwProtocol: .UDP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self)
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host((PacketUtil.intToIPAddress(ip))), port: NWEndpoint.Port(rawValue: port)!)
let nwConnection = NWConnection(to: endpoint, using: .udp)
connection.channel = nwConnection
os_log("Created UDP connection %{public}@", log: OSLog.default, type: .default, key)
connection.isConnected = true
tableQueue.async(flags: .barrier) {
self.table[key] = connection
}
return connection
}
}
func closeConnection(connection: Connection) {
closeConnection(
nwProtocol: connection.nwProtocol, ip: connection.destinationIp, port: connection.destinationPort,
srcIp: connection.sourceIp, srcPort: connection.sourcePort
)
}
//
func closeConnection(nwProtocol: NWProtocol, ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) {
let key = Connection.getConnectionKey(nwProtocol: nwProtocol, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort)
tableQueue.async(flags: .barrier) {
if let connection = self.table.removeValue(forKey: key) {
if connection.channel?.state != .cancelled {
connection.channel?.cancel()
os_log("Closed connection %{public}@", log: OSLog.default, type: .debug, key)
} else {
os_log("Connection %{public}@ is already cancelled", log: OSLog.default, type: .debug, key)
}
}
}
}
//PSH
func addClientData(data: Data, connection: Connection) {
guard data.count > 0 else {
return
}
connection.addSendData(data: data)
}
//java
func keepSessionAlive(connection: Connection) {
let key = Connection.getConnectionKey(
nwProtocol: connection.nwProtocol,
destIp: connection.destinationIp,
destPort: connection.destinationPort,
sourceIp: connection.sourceIp,
sourcePort: connection.sourcePort
)
tableQueue.async(flags: .barrier) {
self.table[key] = connection
}
}
}

View File

@@ -0,0 +1,13 @@
//
// NWProtocol.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
public enum NWProtocol {
case TCP,UDP
}

View File

@@ -0,0 +1,53 @@
//
// ProxyService.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
import NetworkExtension
import Network
import os.log
class ProxyVpnService {
private let queue: DispatchQueue = DispatchQueue(label: "ProxyPin.ProxyVpnService")
private var packetFlow: NEPacketTunnelFlow
private var connectionHandler: ConnectionHandler
private var socketIOService: SocketIOService
init(packetFlow: NEPacketTunnelFlow, proxyAddress: Network.NWEndpoint?) {
self.packetFlow = packetFlow
self.socketIOService = SocketIOService(clientPacketWriter: packetFlow)
let manager = ConnectionManager()
manager.proxyAddress = proxyAddress
self.connectionHandler = ConnectionHandler(manager: manager, writer: packetFlow, ioService: socketIOService)
}
/**
Start processing packets, this should be called after registering all IP stacks.
A stopped interface should never start again. Create a new interface instead.
*/
func start() {
self.readPackets()
}
func stop() {
self.socketIOService.stop()
queue.suspend()
}
func readPackets() -> Void {
self.packetFlow.readPackets { (packets, protocols) in
// os_log("Read %d packets", packets.count)
for (i, packet) in packets.enumerated() {
self.connectionHandler.handlePacket(packet: packet, version: protocols[i])
}
self.readPackets()
}
}
}

View File

@@ -0,0 +1,29 @@
//
// QueueFactory.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
class QueueFactory {
static let instance = QueueFactory()
private let queue: DispatchQueue
private init() {
queue = DispatchQueue(label: "com.network.ProxyPin.queue")
}
func getQueue() -> DispatchQueue {
return queue
}
func executeAsync(block: @escaping () -> Void) {
queue.async {
block()
}
}
}

View File

@@ -0,0 +1,33 @@
//
// ClientPacketWriter.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/
import Foundation
import NetworkExtension
class ClientPacketWriter: NSObject {
private var packetFlow: NEPacketTunnelFlow
private let packetQueue = DispatchQueue(label: "packetQueue", attributes: .concurrent)
private var isShutdown = false
init(packetFlow: NEPacketTunnelFlow) {
self.packetFlow = packetFlow
}
func write(data: Data) {
if !self.isShutdown {
packetQueue.async {
self.packetFlow.writePackets([data], withProtocols: [NSNumber(value: AF_INET)])
}
}
}
func shutdown() {
packetQueue.async(flags: .barrier) {
self.isShutdown = true
}
}
}

View File

@@ -0,0 +1,14 @@
//
// CloseableConnection.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
protocol CloseableConnection {
/// Closes the connection
func closeConnection(connection: Connection)
}

View File

@@ -0,0 +1,214 @@
//
// ProxySocketIOService.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
import NetworkExtension
import os.log
class SocketIOService {
//private static let maxReceiveBufferSize = 16384
private static let maxReceiveBufferSize = 1024
private let queue: DispatchQueue = DispatchQueue(label: "ProxyPin.SocketIOService", attributes: .concurrent)
private var clientPacketWriter: NEPacketTunnelFlow
private var shutdown = false
init(clientPacketWriter: NEPacketTunnelFlow) {
self.clientPacketWriter = clientPacketWriter
}
public func stop() {
os_log("Stopping SocketIOService", log: OSLog.default, type: .default)
queue.async(flags: .barrier) {
self.shutdown = true
}
queue.suspend()
}
//connection client
public func registerSession(connection: Connection) {
connection.channel!.stateUpdateHandler = { state in
switch state {
case .ready:
connection.isConnected = true
os_log("Connected to %{public}@ on receiveMessage", log: OSLog.default, type: .default, connection.description)
//
// connection.sendToDestination()
self.receiveMessage(connection: connection)
case .cancelled:
connection.isConnected = false
// os_log("Connection cancelled", log: OSLog.default, type: .default)
connection.closeConnection()
case .failed(let error):
connection.isConnected = false
os_log("Failed to connect: %{public}@", log: OSLog.default, type: .error, error.localizedDescription)
connection.closeConnection()
default:
break
}
}
connection.channel!.start(queue: self.queue)
}
private func receiveMessage(connection: Connection) {
if (shutdown) {
os_log("SocketIOService is shutting down", log: OSLog.default, type: .default)
return
}
if (connection.nwProtocol == .UDP) {
readUDP(connection: connection)
} else {
readTCP(connection: connection)
}
if (connection.isAbortingConnection) {
os_log("Connection is aborting", log: OSLog.default, type: .default)
connection.closeConnection()
return
}
}
func readTCP(connection: Connection) {
// os_log("Reading from TCP socket")
if connection.isAbortingConnection {
os_log("Connection is aborting", log: OSLog.default, type: .default)
return
}
queue.async {
guard let channel = connection.channel else {
os_log("Invalid channel type", log: OSLog.default, type: .error)
return
}
channel.receive(minimumIncompleteLength: 1, maximumLength: Self.maxReceiveBufferSize) { (data, context, isComplete, error) in
// os_log("Received TCP data packet %{public}@ length %d", log: OSLog.default, type: .default, connection.description, data?.count ?? 0)
if let error = error {
os_log("Failed to read from TCP socket: %@", log: OSLog.default, type: .error, error as CVarArg)
self.sendFin(connection: connection)
connection.isAbortingConnection = true
return
}
guard let data = data, !data.isEmpty else {
return
}
self.pushDataToClient(buffer: data, connection: connection)
// Recursively call readTCP to continue reading messages
self.receiveMessage(connection: connection)
if (isComplete) {
self.sendFin(connection: connection)
connection.isAbortingConnection = true
return
}
}
}
}
func synchronized(_ lock: AnyObject, closure: () -> Void) {
// objc_sync_enter(lock)
closure()
// objc_sync_exit(lock)
}
///create packet data and send it to VPN client
private func pushDataToClient(buffer: Data, connection: Connection) {
// Last piece of data is usually smaller than MAX_RECEIVE_BUFFER_SIZE. We use this as a
// trigger to set PSH on the resulting TCP packet that goes to the VPN.
connection.hasReceivedLastSegment = buffer.count < Self.maxReceiveBufferSize
guard let ipHeader = connection.lastIpHeader, let tcpHeader = connection.lastTcpHeader else {
os_log("Invalid ipHeader or tcpHeader", log: OSLog.default, type: .error)
return
}
synchronized(connection) {
let unAck = connection.sendNext
//
let nextUnAck = UInt32(truncatingIfNeeded: (connection.sendNext + UInt32(buffer.count)) % UInt32.max)
connection.sendNext = nextUnAck
let data = TCPPacketFactory.createResponsePacketData(
ipHeader: ipHeader,
tcpHeader: tcpHeader,
packetData: buffer,
isPsh: connection.hasReceivedLastSegment,
ackNumber: connection.recSequence,
seqNumber: unAck,
timeSender: connection.timestampSender,
timeReplyTo: connection.timestampReplyTo
)
self.clientPacketWriter.writePackets([data], withProtocols: [NSNumber(value: AF_INET)])
// os_log("Sent TCP data packet to client %{public}@ length:%d ack:%u", log: OSLog.default, type: .default, connection.description, data.count, connection.recSequence)
}
}
private func sendFin(connection: Connection) {
guard let ipHeader = connection.lastIpHeader, let tcpHeader = connection.lastTcpHeader else {
os_log("Invalid ipHeader or tcpHeader", log: OSLog.default, type: .error)
return
}
synchronized(connection) {
let data = TCPPacketFactory.createFinData(
ipHeader: ipHeader,
tcpHeader: tcpHeader,
ackNumber: connection.recSequence,
seqNumber: connection.sendNext,
timeSender: connection.timestampSender,
timeReplyTo: connection.timestampReplyTo
)
self.clientPacketWriter.writePackets([data], withProtocols: [NSNumber(value: AF_INET)])
}
}
func readUDP(connection: Connection) {
queue.async {
guard let channel = connection.channel else {
os_log("Invalid channel type", log: OSLog.default, type: .error)
return
}
channel.receive(minimumIncompleteLength: 1, maximumLength: 4196) { (data, context, isComplete, error) in
if let error = error {
os_log("Failed to read from UDP socket: %@", log: OSLog.default, type: .error, error as CVarArg)
connection.isAbortingConnection = true
return
}
// os_log("Received UDP data packet length %d", log: OSLog.default, type: .debug, data?.count ?? 0)
guard let data = data, !data.isEmpty else {
return
}
let packetData = UDPPacketFactory.createResponsePacket(
ip: connection.lastIpHeader!,
udp: connection.lastUdpHeader!,
packetData: data
)
// os_log("Sending UDP data packet to client", log: OSLog.default, type: .default)
self.clientPacketWriter.writePackets([packetData], withProtocols: [NSNumber(value: AF_INET)])
// Recursively call receiveMessage to continue receiving messages
self.receiveMessage(connection: connection)
}
}
}
}

View File

@@ -0,0 +1,21 @@
//
// Packet.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
class Packet {
var ipHeader: IP4Header
var transportHeader: TransportHeader
var buffer: Data
init(ipHeader: IP4Header, transportHeader: TransportHeader, buffer: Data) {
self.ipHeader = ipHeader
self.transportHeader = transportHeader
self.buffer = buffer
}
}

View File

@@ -0,0 +1,96 @@
//
// ICMPPacket.swift
// ProxyPin
//
// Created by wanghongen on 2024/10/3.
//
import Foundation
class ICMPPacket {
// Two ICMP packets we can handle: simple ping & pong
static let ECHO_REQUEST_TYPE: UInt8 = 8
static let ECHO_SUCCESS_TYPE: UInt8 = 0
// One very common packet we ignore: connection rejection. Unclear why this happens,
// random incoming connections that the phone tries to reply to? Nothing we can do though,
// as we can't forward ICMP onwards, and we can't usefully respond or react.
static let DESTINATION_UNREACHABLE_TYPE: UInt8 = 3
let type: UInt8
let code: UInt8 // 0 for request, 0 for success, 0 - 15 for error subtypes
let checksum: UInt16
let identifier: UInt16
let sequenceNumber: UInt16
let data: [UInt8]
init(type: UInt8, code: UInt8, checksum: UInt16, identifier: UInt16, sequenceNumber: UInt16, data: [UInt8]) {
self.type = type
self.code = code
self.checksum = checksum
self.identifier = identifier
self.sequenceNumber = sequenceNumber
self.data = data
}
var description: String {
return "ICMP packet type \(type)/\(code) id:\(identifier) seq:\(sequenceNumber) and \(data.count) bytes of data"
}
}
class ICMPPacketFactory {
static func parseICMPPacket(_ stream: inout Data) -> ICMPPacket? {
guard stream.count >= 8 else { return nil }
let type = stream.removeFirst()
let code = stream.removeFirst()
let checksum = stream.withUnsafeBytes { $0.load(as: UInt16.self) }
stream.removeFirst(2)
let identifier = stream.withUnsafeBytes { $0.load(as: UInt16.self) }
stream.removeFirst(2)
let sequenceNumber = stream.withUnsafeBytes { $0.load(as: UInt16.self) }
stream.removeFirst(2)
let data = Array(stream)
return ICMPPacket(type: type, code: code, checksum: checksum, identifier: identifier, sequenceNumber: sequenceNumber, data: data)
}
static func buildSuccessPacket(_ requestPacket: ICMPPacket) -> ICMPPacket {
return ICMPPacket(
type: ICMPPacket.ECHO_SUCCESS_TYPE,
code: 0,
checksum: 0,
identifier: requestPacket.identifier,
sequenceNumber: requestPacket.sequenceNumber,
data: requestPacket.data
)
}
static func packetToBuffer(ipHeader: IP4Header, packet: ICMPPacket) -> Data {
var buffer = Data()
buffer.append(ipHeader.toBytes())
var icmpDataBuffer = Data()
icmpDataBuffer.append(packet.type)
icmpDataBuffer.append(packet.code)
icmpDataBuffer.append(contentsOf: withUnsafeBytes(of: UInt16(0), Array.init))
if packet.type == ICMPPacket.ECHO_REQUEST_TYPE || packet.type == ICMPPacket.ECHO_SUCCESS_TYPE {
icmpDataBuffer.append(contentsOf: packet.identifier.bytes)
icmpDataBuffer.append(contentsOf: packet.sequenceNumber.bytes)
icmpDataBuffer.append(contentsOf: packet.data)
} else {
fatalError("Can't serialize unrecognized ICMP packet type")
}
let checksum = PacketUtil.calculateChecksum(data: icmpDataBuffer, offset: 0, length: icmpDataBuffer.count)
icmpDataBuffer.replaceSubrange(2..<4, with: checksum)
buffer.append(icmpDataBuffer)
return buffer
}
}

View File

@@ -0,0 +1,161 @@
//
// IP4Header.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/16.
//
import Foundation
import os.log
// IPv4 header data structure
class IP4Header {
var ipVersion: UInt8 // IPv44IPv4 4bit
var internetHeaderLength: UInt8 // 4bit
var diffTypeOfService: UInt8 // =>6
var ecn: UInt8 // ECN
var totalLength: UInt16 // IP 16bit
var identification: UInt16 // IP 16bit
var mayFragment: Bool // 1bit
var lastFragment: Bool // 1bit
var fragmentOffset: UInt16 // IP 13bit
var timeToLive: UInt8 // 8bit
var protocolNumber: UInt8 // IP使 8bit
var headerChecksum: UInt16 // 16 16bit
var sourceIP: UInt32 // IPv4 32bit
var destinationIP: UInt32 // IPv4 32bit
//3
//bit 0:
//bit 1: Don't Fragment (DF)
//bit 2: More Fragments (MF)
private var flag: UInt8
init(
ipVersion: UInt8, internetHeaderLength: UInt8, diffTypeOfService: UInt8, ecn: UInt8, totalLength: UInt16, identification: UInt16,
mayFragment: Bool, lastFragment: Bool, fragmentOffset: UInt16, timeToLive: UInt8, protocolNumber: UInt8, headerChecksum: UInt16,
sourceIP: UInt32, destinationIP: UInt32
) {
self.ipVersion = ipVersion
self.internetHeaderLength = internetHeaderLength
self.diffTypeOfService = diffTypeOfService
self.ecn = ecn
self.totalLength = totalLength
self.identification = identification
self.mayFragment = mayFragment
self.lastFragment = lastFragment
self.fragmentOffset = fragmentOffset
self.timeToLive = timeToLive
self.protocolNumber = protocolNumber
self.headerChecksum = headerChecksum
self.sourceIP = sourceIP
self.destinationIP = destinationIP
self.flag = IP4Header.initFlag(mayFragment: mayFragment, lastFragment: lastFragment)
}
private static func initFlag(mayFragment: Bool, lastFragment: Bool) -> UInt8 {
var initFlag: UInt8 = 0
if mayFragment {
initFlag = 0x40
}
if lastFragment {
initFlag |= 0x20
}
return initFlag
}
func setMayFragment(_ mayFragment: Bool) {
self.mayFragment = mayFragment
flag = mayFragment ? (flag | 0x40) : (flag & 0xBF)
}
func getIPHeaderLength() -> Int {
return Int(internetHeaderLength * 4)
}
func copy() -> IP4Header {
return IP4Header(
ipVersion: ipVersion, internetHeaderLength: internetHeaderLength, diffTypeOfService: diffTypeOfService, ecn: ecn, totalLength: totalLength, identification: identification,
mayFragment: mayFragment, lastFragment: lastFragment, fragmentOffset: fragmentOffset, timeToLive: timeToLive, protocolNumber: protocolNumber, headerChecksum: headerChecksum,
sourceIP: sourceIP, destinationIP: destinationIP
)
}
func toBytes() -> Data {
var buffer = Data()
buffer.append(UInt8((ipVersion << 4) + internetHeaderLength))
buffer.append(UInt8((diffTypeOfService << 2) + ecn))
buffer.append(contentsOf: totalLength.bytes)
buffer.append(contentsOf: identification.bytes)
//
buffer.append(UInt8((fragmentOffset >> 8) & 0x1F) | flag)
buffer.append(UInt8(fragmentOffset & 0xFF))
buffer.append(timeToLive)
buffer.append(protocolNumber)
buffer.append(contentsOf: headerChecksum.bytes)
buffer.append(contentsOf: sourceIP.bytes)
buffer.append(contentsOf: destinationIP.bytes)
return buffer
}
}
class IPPacketFactory {
static let IP4_HEADER_SIZE = 20
static let IP4_VERSION: UInt8 = 0x04
//ByteBufferIPv4
static func createIP4Header(data: Data) -> IP4Header? {
guard data.count >= IP4_HEADER_SIZE else {
return nil
}
let buffer = [UInt8](data)
let versionAndHeaderLength = buffer[0]
let ipVersion = versionAndHeaderLength >> 4
guard ipVersion == IP4_VERSION else {
return nil
}
let internetHeaderLength = versionAndHeaderLength & 0x0F
let typeOfService = buffer[1]
let diffTypeOfService = typeOfService >> 2
let ecn = typeOfService & 0x03
let totalLength = UInt16(buffer[2]) << 8 | UInt16(buffer[3])
let identification = UInt16(buffer[4]) << 8 | UInt16(buffer[5])
let flagsAndFragmentOffset = UInt16(buffer[6]) << 8 | UInt16(buffer[7])
let mayFragment = (flagsAndFragmentOffset & 0x4000) != 0
let lastFragment = (flagsAndFragmentOffset & 0x2000) != 0
let fragmentOffset = flagsAndFragmentOffset & 0x1FFF
let timeToLive = buffer[8]
let protocolNumber = buffer[9]
let checksum = UInt16(buffer[10]) << 8 | UInt16(buffer[11])
let sourceIp = UInt32(buffer[12]) << 24 | UInt32(buffer[13]) << 16 | UInt32(buffer[14]) << 8 | UInt32(buffer[15])
let desIp = UInt32(buffer[16]) << 24 | UInt32(buffer[17]) << 16 | UInt32(buffer[18]) << 8 | UInt32(buffer[19])
if internetHeaderLength > 5 {
// drop the IP option
for _ in 0..<(internetHeaderLength - 5) {
// Skip the IP options
}
}
return IP4Header(
ipVersion: ipVersion, internetHeaderLength: internetHeaderLength, diffTypeOfService: diffTypeOfService, ecn: ecn, totalLength: totalLength, identification: identification,
mayFragment: mayFragment, lastFragment: lastFragment, fragmentOffset: fragmentOffset, timeToLive: timeToLive, protocolNumber: protocolNumber, headerChecksum: checksum,
sourceIP: sourceIp, destinationIP: desIp
)
}
public static func printPacket(data: Data) {
guard let ipHeader = createIP4Header(data: data) else {
return
}
os_log("IP Header: version: %{public}d, internetHeaderLength: %{public}d, diffTypeOfService: %{public}d, ecn: %{public}d, totalLength: %{public}d, identification: %{public}d, mayFragment: %{public}d, lastFragment: %{public}d, fragmentOffset: %{public}d, timeToLive: %{public}d, protocolNumber: %{public}d, headerChecksum: %{public}d, sourceIP: %{public}@, destinationIP: %{public}@", log: OSLog.default, type: .default, ipHeader.ipVersion, ipHeader.internetHeaderLength, ipHeader.diffTypeOfService, ipHeader.ecn, ipHeader.totalLength, ipHeader.identification, ipHeader.mayFragment, ipHeader.lastFragment, ipHeader.fragmentOffset, ipHeader.timeToLive, ipHeader.protocolNumber, ipHeader.headerChecksum, PacketUtil.intToIPAddress(ipHeader.sourceIP), PacketUtil.intToIPAddress(ipHeader.destinationIP))
}
}

View File

@@ -0,0 +1,182 @@
//
// TCPHeader.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/16.
//
import Foundation
/// Represents a TCP header in a network packet.
class TCPHeader : TransportHeader{
/// Source port number (16 bits)
var sourcePort: UInt16
/// Destination port number (16 bits)
var destinationPort: UInt16
/// Sequence number (32 bits)
var sequenceNumber: UInt32
/// Acknowledgment number (32 bits)
var ackNumber: UInt32
/// Data offset (4 bits)
var dataOffset: UInt8
var isNS: Bool = false // ECN-nonce concealment protection (experimental: see RFC 3540)
/// Flags (9 bits)
var flags: UInt8
/// Window size (16 bits)
var windowSize: UInt16
/// Checksum (16 bits)
var checksum: UInt16
/// Urgent pointer (16 bits)
var urgentPointer: UInt16
/// Options (variable length)
var options: Data?
var payload: Data?
//Static section for constants
static let END_OF_OPTIONS_LIST: UInt8 = 0
static let NO_OPERATION: UInt8 = 1
static let MAX_SEGMENT_SIZE: UInt8 = 2
static let WINDOW_SCALE: UInt8 = 3
static let SELECTIVE_ACK_PERMITTED: UInt8 = 4
static let TIME_STAMP: UInt8 = 8
init(sourcePort: UInt16, destinationPort: UInt16, sequenceNumber: UInt32, ackNumber: UInt32, dataOffset: UInt8, isNS: Bool, flags: UInt8, windowSize: UInt16, checksum: UInt16, urgentPointer: UInt16, options: Data?, payload: Data? = nil) {
self.sourcePort = sourcePort
self.destinationPort = destinationPort
self.sequenceNumber = sequenceNumber
self.ackNumber = ackNumber
self.dataOffset = dataOffset
self.isNS = isNS
self.flags = flags
self.windowSize = windowSize
self.checksum = checksum
self.urgentPointer = urgentPointer
self.options = options
self.payload = payload
}
//options
var maxSegmentSize: UInt16 = 0
private var windowScale: UInt8 = 0
private var isSelectiveAckPermitted = false
var timeStampSender = 0
var timeStampReplyTo = 0
func getSourcePort() -> Int {
return Int(sourcePort)
}
func getDestinationPort() -> Int {
return Int(destinationPort)
}
func isFIN() -> Bool {
return flags & 0x01 != 0
}
/// Checks if the SYN flag is set.
func isSYN() -> Bool {
return flags & 0x02 != 0
}
/// Checks if the RST flag is set.
func isRST() -> Bool {
return flags & 0x04 != 0
}
/// Checks if the PSH flag is set.
func isPSH() -> Bool {
return flags & 0x08 != 0
}
/// Checks if the ACK flag is set.
func isACK() -> Bool {
return flags & 0x10 != 0
}
/// Checks if the URG flag is set.
func isURG() -> Bool {
return flags & 0x20 != 0
}
/// Checks if the ECE flag is set.
func isECE() -> Bool {
return flags & 0x40 != 0
}
/// Checks if the CWR flag is set.
func isCWR() -> Bool {
return flags & 0x80 != 0
}
/// Sets or clears the RST flag.
func setIsRST(_ isRST: Bool) {
flags = isRST ? (flags | 0x04) : (flags & 0xFB)
}
/// Sets or clears the SYN flag.
func setIsSYN(_ isSYN: Bool) {
flags = isSYN ? (flags | 0x02) : (flags & 0xFD)
}
/// Sets or clears the FIN flag.
func setIsFIN(_ isFIN: Bool) {
flags = isFIN ? (flags | 0x01) : (flags & 0xFE)
}
/// Sets or clears the PSH flag.
func setIsPSH(_ isPSH: Bool) {
flags = isPSH ? (flags | 0x08) : (flags & 0xF7)
}
/// Sets or clears the ACK flag.
func setIsACK(_ isACK: Bool) {
flags = isACK ? (flags | 0x10) : (flags & 0xEF)
}
/// Returns the length of the TCP header.
func getTCPHeaderLength() -> Int {
return Int(dataOffset) * 4
}
/// Converts the TCP header to a byte array.
func toBytes() -> Data {
var buffer = Data()
buffer.append(contentsOf: sourcePort.bytes)
buffer.append(contentsOf: destinationPort.bytes)
buffer.append(contentsOf: sequenceNumber.bytes)
buffer.append(contentsOf: ackNumber.bytes)
//is ns and data offset
let headerLength = 5
buffer.append(UInt8((headerLength << 4) | (isNS ? 1 : 0)))
buffer.append(flags)
buffer.append(contentsOf: windowSize.bytes)
buffer.append(contentsOf: checksum.bytes)
buffer.append(contentsOf: urgentPointer.bytes)
// if let options = options {
// buffer.append(options)
// }
return buffer
}
/// Creates a copy of the TCP header.
func copy() -> TCPHeader {
return TCPHeader(
sourcePort: sourcePort,
destinationPort: destinationPort,
sequenceNumber: sequenceNumber,
ackNumber: ackNumber,
dataOffset: dataOffset,
isNS: isNS,
flags: flags,
windowSize: windowSize,
checksum: checksum,
urgentPointer: urgentPointer,
options: options
)
}
}

View File

@@ -0,0 +1,325 @@
//
// TCPPacketFactory.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/16.
//
//
import Foundation
import os.log
/// Factory class for creating TCP packets.
class TCPPacketFactory {
public static let TCP_HEADER_LENGTH = 20
//tcptcpHeader
static func createTCPHeader(data: Data) -> TCPHeader? {
if data.count < TCP_HEADER_LENGTH {
os_log("Data is too short to be a TCP packet", log: OSLog.default, type: .error)
return nil
}
var offset = 0
func readUInt16() -> UInt16 {
let value = data.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self).bigEndian }
offset += 2
return value
}
func readUInt32() -> UInt32 {
let value = data.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt32.self).bigEndian }
offset += 4
return value
}
let sourcePort = readUInt16()
let destinationPort = readUInt16()
let sequenceNumber = readUInt32()
let ackNumber = readUInt32()
let dataOffsetAndReserved = data[offset]
offset += 1
let dataOffset = UInt8((dataOffsetAndReserved & 0xF0) >> 4)
let isNs = (dataOffsetAndReserved & 0x01) == 1
let flags = UInt8(data[offset])
offset += 1
let windowSize = readUInt16()
let checksum = readUInt16()
let urgentPointer = readUInt16()
var optionsSize = Int(dataOffset) - 5
var options: Data?
if (optionsSize > 0) {
optionsSize *= 4
options = data.subdata(in: offset..<offset + optionsSize)
}
let payload: Data? = offset < data.count ? data.subdata(in: offset..<data.count) : nil
return TCPHeader(
sourcePort: sourcePort,
destinationPort: destinationPort,
sequenceNumber: sequenceNumber,
ackNumber: ackNumber,
dataOffset: dataOffset,
isNS: isNs,
flags: flags,
windowSize: windowSize,
checksum: checksum,
urgentPointer: urgentPointer,
options: options,
payload: payload
)
}
//
static func createResponseAckData(ipHeader: IP4Header, tcpHeader: TCPHeader, ackToClient: UInt32) -> Data {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
flipIp(ip: &ip, tcp: &tcp)
let seqNumber = tcp.ackNumber
tcp.ackNumber = ackToClient
tcp.sequenceNumber = seqNumber
ip.identification = UInt16(truncatingIfNeeded: PacketUtil.getPacketId())
// Set TCP flags
tcp.setIsACK(true)
tcp.setIsSYN(false)
tcp.setIsPSH(false)
tcp.setIsFIN(false)
tcp.dataOffset = 5 // tcp header length 5 * 4 = 20 bytes
tcp.options = nil
ip.totalLength = UInt16(ip.getIPHeaderLength() + tcp.getTCPHeaderLength())
return createPacketData(ipHeader: ip, tcpHeader: tcp, data: nil)
}
///RST便
static func createRstData(ipHeader: IP4Header, tcpHeader: TCPHeader, dataLength: Int) -> Data {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
var ackNumber: UInt32 = 0
var seqNumber: UInt32 = 0
if tcp.ackNumber > 0 {
seqNumber = tcp.ackNumber
} else {
ackNumber = tcp.sequenceNumber + UInt32(dataLength)
}
tcp.ackNumber = ackNumber
tcp.sequenceNumber = seqNumber
// Flip IP from source to destination
flipIp(ip: &ip, tcp: &tcp)
ip.identification = 0
tcp.flags = 0
tcp.isNS = false
tcp.setIsRST(true)
tcp.dataOffset = 5
tcp.options = nil
tcp.windowSize = 0
// Recalculate IP length
let totalLength = ip.getIPHeaderLength() + tcp.getTCPHeaderLength()
ip.totalLength = UInt16(totalLength)
return createPacketData(ipHeader: ip, tcpHeader: tcp, data: nil)
}
//FIN-ACK
static func createFinAckData(ipHeader: IP4Header, tcpHeader: TCPHeader, ackToClient: UInt32, seqToClient: UInt32, isFin: Bool, isAck: Bool) -> Data {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
flipIp(ip: &ip, tcp: &tcp)
tcp.dataOffset = 5 // tcp header length 5 * 4 = 20 bytes
tcp.options = nil
tcp.ackNumber = ackToClient
tcp.sequenceNumber = seqToClient
ip.identification = UInt16(truncatingIfNeeded: PacketUtil.getPacketId())
tcp.setIsACK(isAck)
tcp.setIsSYN(false)
tcp.setIsPSH(false)
tcp.setIsFIN(isFin)
ip.totalLength = UInt16(ip.getIPHeaderLength() + tcp.getTCPHeaderLength())
return createPacketData(ipHeader: ip, tcpHeader: tcp, data: nil)
}
//SYN-ACK
public static func createSynAckPacketData(ipHeader: IP4Header, tcpHeader: TCPHeader) -> Packet {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
flipIp(ip: &ip, tcp: &tcp)
tcp.dataOffset = 5 // tcp header length 5 * 4 = 20 bytes
tcp.options = nil
// ack = received sequence + 1
let ackNumber = tcpHeader.sequenceNumber + 1
tcp.ackNumber = ackNumber
// Server-generated initial sequence number
let seqNumber = UInt64.random(in: 0..<100000)
tcp.sequenceNumber = UInt32(seqNumber)
// SYN-ACK
tcp.setIsACK(true)
tcp.setIsSYN(true)
tcp.timeStampReplyTo = tcp.timeStampSender
tcp.timeStampSender = PacketUtil.currentTime
ip.totalLength = UInt16(ip.getIPHeaderLength() + tcp.getTCPHeaderLength())
return Packet(ipHeader: ip, transportHeader: tcp, buffer: createPacketData(ipHeader: ip, tcpHeader: tcp, data: nil))
}
//
public static func createResponsePacketData(
ipHeader: IP4Header, tcpHeader: TCPHeader, packetData: Data?, isPsh: Bool,
ackNumber: UInt32, seqNumber: UInt32, timeSender: Int, timeReplyTo: Int
) -> Data {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
flipIp(ip: &ip, tcp: &tcp)
tcp.dataOffset = 5 // tcp header length 5 * 4 = 20 bytes
tcp.options = nil
tcp.ackNumber = ackNumber
tcp.sequenceNumber = seqNumber
ip.identification = UInt16(truncatingIfNeeded: PacketUtil.getPacketId())
// ACK is always sent
tcp.setIsACK(true)
tcp.setIsSYN(false)
tcp.setIsPSH(isPsh)
tcp.setIsFIN(false)
tcp.timeStampSender = timeSender
tcp.timeStampReplyTo = timeReplyTo
var totalLength = ip.getIPHeaderLength() + tcp.getTCPHeaderLength()
if let packetData = packetData {
totalLength += packetData.count
}
ip.totalLength = UInt16(totalLength)
return createPacketData(ipHeader: ip, tcpHeader: tcp, data: packetData)
}
//IP
private static func flipIp(ip: inout IP4Header, tcp: inout TCPHeader) {
let sourceIp = ip.destinationIP
let destIp = ip.sourceIP
let sourcePort = tcp.destinationPort
let destPort = tcp.sourcePort
ip.destinationIP = destIp
ip.sourceIP = sourceIp
tcp.destinationPort = destPort
tcp.sourcePort = sourcePort
}
public static func createFinData(
ipHeader: IP4Header, tcpHeader: TCPHeader, ackNumber: UInt32, seqNumber: UInt32,
timeSender: Int, timeReplyTo: Int
) -> Data {
var ip = ipHeader.copy()
var tcp = tcpHeader.copy()
flipIp(ip: &ip, tcp: &tcp)
tcp.ackNumber = ackNumber
tcp.sequenceNumber = seqNumber
ip.identification = UInt16(truncatingIfNeeded: PacketUtil.getPacketId())
tcp.timeStampReplyTo = timeReplyTo
tcp.timeStampSender = timeSender
tcp.flags = 0
tcp.isNS = false
tcp.setIsACK(true)
tcp.setIsFIN(true)
tcp.options = nil
tcp.windowSize = 0
ip.totalLength = UInt16(ip.getIPHeaderLength() + TCP_HEADER_LENGTH)
return createPacketData(ipHeader: ip, tcpHeader: tcp, data: nil)
}
//tcpHeadertcp
private static func createPacketData(ipHeader: IP4Header, tcpHeader: TCPHeader, data: Data?) -> Data {
let dataLength = data?.count ?? 0
var buffer = Data()
// Add IP header
let ipBuffer = ipHeader.toBytes()
buffer.append(ipBuffer)
// Add TCP header
let tcpBuffer = tcpHeader.toBytes()
buffer.append(tcpBuffer)
// Add data if exists
if let data = data {
buffer.append(data)
}
// Zero out IP checksum
buffer[10] = 0
buffer[11] = 0
// Calculate IP checksum
let ipChecksum = PacketUtil.calculateChecksum(data: buffer, offset: 0, length: ipBuffer.count)
buffer[10] = ipChecksum[0]
buffer[11] = ipChecksum[1]
// IPPacketFactory.printPacket(data: ipBuffer)
// Zero out TCP checksum
let tcpStart = ipBuffer.count
buffer[tcpStart + 16] = 0
buffer[tcpStart + 17] = 0
// Calculate TCP checksum
let tcpChecksum = PacketUtil.calculateTCPHeaderChecksum(
data: buffer, offset: tcpStart, tcpLength: tcpBuffer.count + dataLength,
sourceIP: ipHeader.sourceIP, destinationIP: ipHeader.destinationIP
)
buffer[tcpStart + 16] = tcpChecksum[0]
buffer[tcpStart + 17] = tcpChecksum[1]
return buffer
}
static func printPacket(data: Data) {
guard let tcpHeader = createTCPHeader(data: data) else {
os_log("Failed to create TCP header", log: OSLog.default, type: .error)
return
}
os_log("TCP Header: sourcePort: %{public}d, destinationPort: %{public}d, sequenceNumber: %{public}u, ackNumber: %{public}u, dataOffset: %{public}d, isNS: %{public}d, flags: %{public}d, windowSize: %{public}d, checksum: %{public}u, urgentPointer: %{public}u",
log: OSLog.default, type: .default, tcpHeader.sourcePort, tcpHeader.destinationPort, tcpHeader.sequenceNumber, tcpHeader.ackNumber, tcpHeader.dataOffset, tcpHeader.isNS ? 1 : 0, tcpHeader.flags, tcpHeader.windowSize, tcpHeader.checksum, tcpHeader.urgentPointer)
}
}

View File

@@ -0,0 +1,13 @@
//
// TransportHeader.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
protocol TransportHeader {
func getSourcePort() -> Int
func getDestinationPort() -> Int
}

View File

@@ -0,0 +1,112 @@
//
// UDPHeader.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
import os.log
///UDP
struct UDPHeader {
var sourcePort: UInt16 // 16bit
var destinationPort: UInt16 // 16bit
var length: UInt16 //UDP 16bit
var checksum: UInt16 // 16bit
init(sourcePort: UInt16, destinationPort: UInt16, length: UInt16, checksum: UInt16) {
self.sourcePort = sourcePort
self.destinationPort = destinationPort
self.length = length
self.checksum = checksum
}
}
class UDPPacketFactory {
static let UDP_HEADER_LENGTH = 8
static func createUDPHeader(from data: Data) -> UDPHeader? {
guard data.count >= UDP_HEADER_LENGTH else {
return nil
}
let srcPort = data.withUnsafeBytes { $0.load(fromByteOffset: 0, as: UInt16.self).bigEndian }
let destPort = data.withUnsafeBytes { $0.load(fromByteOffset: 2, as: UInt16.self).bigEndian }
let length = data.withUnsafeBytes { $0.load(fromByteOffset: 4, as: UInt16.self).bigEndian }
let checksum = data.withUnsafeBytes { $0.load(fromByteOffset: 6, as: UInt16.self).bigEndian }
return UDPHeader(sourcePort: srcPort, destinationPort: destPort, length: length, checksum: checksum)
}
static func createResponsePacket(ip: IP4Header, udp: UDPHeader, packetData: Data?) -> Data {
var udpLen = 8
if let packetData = packetData {
udpLen += packetData.count
}
let srcPort = udp.destinationPort
let destPort = udp.sourcePort
let ipHeader = ip.copy()
let srcIp = ip.destinationIP
let destIp = ip.sourceIP
ipHeader.setMayFragment(false)
ipHeader.sourceIP = srcIp
ipHeader.destinationIP = destIp
ipHeader.identification = UInt16(truncatingIfNeeded: PacketUtil.getPacketId())
//ip => IP header length + UDP header length (8) + UDP body length
let totalLength = ipHeader.getIPHeaderLength() + udpLen
ipHeader.totalLength = UInt16(totalLength)
var ipData = ipHeader.toBytes()
// clear IP checksum
ipData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) in
bytes[10] = 0
bytes[11] = 0
}
//os_log("Create UDP response packet from %{public}@:%{public}d to %{public}@:%{public}d totalLength:%{public}d", log: OSLog.default, type: .default, PacketUtil.intToIPAddress(srcIp), srcPort, PacketUtil.intToIPAddress(destIp), destPort, totalLength)
// calculate checksum for IP header
let ipChecksum = PacketUtil.calculateChecksum(data: ipData, offset: 0, length: ipData.count)
// write result of checksum back to buffer
ipData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) in
bytes[10] = ipChecksum[0]
bytes[11] = ipChecksum[1]
}
var buffer = Data()
// copy IP header to buffer
buffer.append(ipData)
// copy UDP header to buffer
buffer.append(contentsOf: srcPort.bytes)
buffer.append(contentsOf: destPort.bytes)
buffer.append(contentsOf: UInt16(udpLen).bytes)
let checksum: UInt16 = 0
buffer.append(contentsOf: checksum.bytes)
if let packetData = packetData {
buffer.append(packetData)
}
return buffer
}
//
public static func printPacket(data: Data) {
guard let udpHeader = createUDPHeader(from: data) else {
return
}
os_log("UDP Header: sourcePort: %{public}d, destinationPort: %{public}d, length: %{public}d, checksum: %{public}d", log: OSLog.default, type: .default, udpHeader.sourcePort, udpHeader.destinationPort, udpHeader.length, udpHeader.checksum)
}
}

View File

@@ -0,0 +1,138 @@
//
// PacketUtil.swift
// ProxyPin
//
// Created by wanghongen on 2024/9/17.
//
import Foundation
import os.log
class PacketUtil {
private static var packetId: Int = 0
static func getPacketId() -> Int {
defer { packetId += 1 }
return packetId
}
static var currentTime: Int {
return Int(Date().timeIntervalSince1970)
}
static func writeIntToBytes(value: UInt32, buffer: inout Data, offset: Int) {
guard buffer.count >= offset + 4 else { return }
var intValue = value.bigEndian
let intData = Data(bytes: &intValue, count: 4)
buffer.replaceSubrange(offset..<offset+4, with: intData)
}
static func intToIPAddress(_ ip: UInt32) -> String {
return String(format: "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF)
}
static func calculateTCPHeaderChecksum(data: Data, offset: Int, tcpLength: Int, sourceIP: UInt32, destinationIP: UInt32) -> Data {
var bufferSize = tcpLength + 12
var isOdd = false
if bufferSize % 2 != 0 {
bufferSize += 1
isOdd = true
}
var buffer = Data()
// Add source IP
buffer.append(contentsOf: sourceIP.bytes)
// Add destination IP
buffer.append(contentsOf: destinationIP.bytes)
// Add reserved byte and protocol (6 for TCP)
buffer.append(0)
buffer.append(6)
// Add TCP length
buffer.append(contentsOf: UInt16(tcpLength).bytes)
// Add TCP header and data
buffer.append(contentsOf: data[offset..<offset + tcpLength])
// Pad with zero if odd length
if isOdd {
buffer.append(0)
}
// Calculate checksum
return calculateChecksum(data: buffer, offset: 0, length: bufferSize)
}
static func calculateChecksum(data: Data, offset: Int, length: Int) -> Data {
var start = offset
var sum = 0
while start < length {
sum += getNetworkInt(buffer: data, start: start, length: 2)
start += 2
}
// Carry over one's complement
while (sum >> 16) > 0 {
sum = (sum & 0xFFFF) + (sum >> 16)
}
// Flip the bits to get one's complement
sum = ~sum
// Extract the last two bytes of the int
let checksum = Data([UInt8(truncatingIfNeeded: (sum >> 8) & 0xFF), UInt8(truncatingIfNeeded: sum & 0xFF)])
return checksum
}
static func getNetworkInt(buffer: Data, start: Int, length: Int) -> Int {
var value = 0
var end = start + min(length, 4)
if end > buffer.count { end = buffer.count }
for i in start..<end {
value = value | (Int(buffer[i]) & 0xFF)
if i < end - 1 { value = value << 8 }
}
return value
}
static func isPacketCorrupted(tcpHeader: TCPHeader) -> Bool {
guard let options = tcpHeader.options else {
return false
}
var i = 0
while i < options.count {
let kind = options[i]
switch kind {
case 0, 1:
break
case 2:
i += 3
case 3, 14:
i += 2
case 4:
i += 1
case 5, 15:
i += Int(options[i + 1]) - 2
case 8:
i += 9
case 23:
return true
default:
print("Unknown option: \(kind)")
}
i += 1
}
return false
}
}
extension FixedWidthInteger {
var bytes: [UInt8] {
withUnsafeBytes(of: self.bigEndian) { Array($0) }
}
}