diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt index 0bbe2a8..cc7d57a 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt @@ -44,12 +44,14 @@ class ARPTable( fun getMacFromIP(ip: Int): Pair { val gw = routeTable.getGateway(ip) - if (gw == null) { + if (gw == null || gw == 0) { // not found in route table, just use the ip + TerminalLogger.debugf { "ARP: IP=${ipToString(ip)} has no gateway (gw=$gw), querying IP directly." } val value = content.get(ip) return Pair(value?.mac, ip) } else { // gw not null, try find the gw's mac + TerminalLogger.debugf { "ARP: IP=${ipToString(ip)} uses gateway=${ipToString(gw)}." } val value = content.get(gw) return Pair(value?.mac, gw) } @@ -128,20 +130,14 @@ class ARPWaitList { // just skip the packet continue } + TerminalLogger.debugf { "ARP resumed flow: ${describeIpv4Packet(item.originData)}" } val packet = formEthernetPacket(node.mac.toByteArray(), mac.toByteArray(), item.originData) val size = packet.remaining() - val encrypted = node.aes.encrypt(packet) + val packetBytes = ByteArray(size) + packet.get(packetBytes) + val encrypted = node.aes.encrypt(packetBytes) if (encrypted != null) { - val data = SDLData.newBuilder() - .setIsP2P(true) - .setNetworkId(networkid) - .setTtl(2) - .setSrcMac(node.mac) - .setDstMac(mac) - .setData(encrypted.toByteString()) - .build() - val msg = encodeToUDPMessage(data, PacketType.Data) - sendPacketToNet(node, mac, msg, size.toLong()) + sendPacketToNet(node, mac, encrypted, size.toLong()) } } diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt index d094255..d72bfd1 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt @@ -22,7 +22,6 @@ import java.net.Inet4Address import java.net.Socket import java.nio.ByteBuffer import java.nio.ByteOrder -import java.util.zip.CRC32 interface Iface { suspend fun doInit() @@ -101,24 +100,16 @@ interface IfaceTun: Iface { arp.dipaddr = arp.sipaddr arp.sipaddr = node.deviceConfig.ip.netAddr - val bytes = arp.marshalToBytes() - val encrypted = node.aes.encrypt(bytes) + val bytesBuf = arp.marshalToBytes() + val bytesArr = ByteArray(bytesBuf.remaining()) + bytesBuf.get(bytesArr) + val encrypted = node.aes.encrypt(bytesArr) if (encrypted != null) { val dstmac = arp.dhwaddr.toByteString() - val data = SDLData.newBuilder() - .setIsP2P(true) - .setTtl(2) - .setNetworkId(node.networkID.get()) - .setSrcMac(node.mac) - .setDstMac(dstmac) - .setData(encrypted.toByteString()) - .build() - TerminalLogger.debugf { "send arp reply to ${macToString(dstmac)}" } - val content = encodeToUDPMessage(data, PacketType.Data) - sendPacketToNet(node, dstmac, content, 0) + sendPacketToNet(node, dstmac, encrypted, 0) } } } @@ -166,17 +157,28 @@ interface IfaceTun: Iface { TerminalLogger.infof { "dropping tun packet due to not authed" } return } + val version = (data[0].toInt() ushr 4) and 0x0f + if (version != 4) { + TerminalLogger.debugf { "dropping non-IPv4 packet from tun: version=$version, bytes=${data.size}" } + return + } val buffer = ByteBuffer.wrap(data, 12, 8).order(ByteOrder.BIG_ENDIAN) val srcip = buffer.getInt() val dstip = buffer.getInt() TerminalLogger.debugf { "got ${data.size} bytes from tun" } + TerminalLogger.debugf { "IPv4 flow from tun: ${describeIpv4Packet(data)}" } if (!node.config.allowRouting && (srcip != node.deviceConfig.ip.netAddr)) { TerminalLogger.infof { "dropping routed packet from tun" } return } + if (node.config.dropMulticast && (dstip.toUInt() shr 28) == 14u) { + TerminalLogger.debugf { "dropping multicast packet from tun: ${com.jihe.punchnet.sdlan.utils.ipToString(dstip)}" } + return + } + val (arpinfo, gwip) = arpTable.getMacFromIP(dstip) if (arpinfo == null) { println("arp info is null") @@ -190,27 +192,11 @@ interface IfaceTun: Iface { val buffer = arpinfo + node.mac.toByteArray() + byteArrayOf((EtherType.IPV4.toInt() shr 8).toByte(), EtherType.IPV4.toByte()) + data - - val crc = CRC32() - crc.update(buffer) - val cksum = ByteBuffer.allocate(4) - .putInt(crc.value.toInt()) - .array() - val packet = buffer + cksum - val size = packet.size - val encrypted = node.aes.encrypt(packet) + val size = buffer.size + val encrypted = node.aes.encrypt(buffer) if (encrypted != null) { val mac = arpinfo.toByteString() - val data = SDLData.newBuilder() - .setIsP2P(true) - .setNetworkId(node.networkID.get()) - .setTtl(2) - .setSrcMac(node.mac) - .setDstMac(mac) - .setData(encrypted.toByteString()) - .build() - val msg = encodeToUDPMessage(data, PacketType.Data) - sendPacketToNet(node, mac, msg, size.toLong()) + sendPacketToNet(node, mac, encrypted, size.toLong()) } } } @@ -307,4 +293,4 @@ fun macToString(mac: Mac): String { } return result.joinToString(":") } - */ \ No newline at end of file + */ diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt index ca737f0..4c93617 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt @@ -103,12 +103,19 @@ class RouteTable() { fun getGateway(ip: Int): Int? { try { lock.readLock().lock() + var bestGw: Int? = null + var bestMaskLen = -1 for (item in routeInfo) { if ((ip and item.mask) == item.maskedAddr) { - return item.gw + val maskLen = Integer.bitCount(item.mask) + if (maskLen > bestMaskLen) { + bestMaskLen = maskLen + bestGw = item.gw + } } } - return null + TerminalLogger.debugf { "RouteTable LPM: ip=${com.jihe.punchnet.sdlan.utils.ipToString(ip)} -> bestGw=${bestGw?.let { com.jihe.punchnet.sdlan.utils.ipToString(it) } ?: "null"} (maskLen=$bestMaskLen)" } + return bestGw } finally { lock.readLock().unlock() } @@ -122,11 +129,17 @@ class RouteTable2( val routeInfo: Array = initRouteInfo.toTypedArray() fun getGeteway(ip: Int): Int? { + var bestGw: Int? = null + var bestMaskLen = -1 for (item in routeInfo) { if ((ip and item.mask) == item.maskedAddr) { - return item.gw + val maskLen = Integer.bitCount(item.mask) + if (maskLen > bestMaskLen) { + bestMaskLen = maskLen + bestGw = item.gw + } } } - return null + return bestGw } } \ No newline at end of file diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/utils/aes.kt b/app/src/main/java/com/jihe/punchnet/sdlan/utils/aes.kt index 54577b2..22ae1b2 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/utils/aes.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/utils/aes.kt @@ -2,7 +2,11 @@ package com.jihe.punchnet.sdlan.utils import com.jihe.punchnet.sdlan.config.AESConfig import com.jihe.punchnet.sdlan.logs.TerminalLogger +import org.bouncycastle.crypto.engines.ChaCha7539Engine +import org.bouncycastle.crypto.params.KeyParameter +import org.bouncycastle.crypto.params.ParametersWithIV import java.nio.ByteBuffer +import java.nio.ByteOrder import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -10,6 +14,8 @@ import javax.crypto.spec.SecretKeySpec class AES private constructor () { private var _secret: ByteArray = ByteArray(0) private var _iv: ByteArray = ByteArray(0) + private var _algorithm: String = "aes" + private var _regionId: Long = 0 companion object { private var instance: AES? = null get() { @@ -29,13 +35,17 @@ class AES private constructor () { return _secret.size != 0 } - fun setSecret(secret: ByteArray?) { + fun setSecret(secret: ByteArray?, algorithm: String = "aes", regionId: Long = 0) { if (secret == null) { _secret = ByteArray(0) _iv = ByteArray(0) + _algorithm = "aes" + _regionId = 0 } else { _secret = secret _iv = _secret.sliceArray(0..<16) + _algorithm = algorithm.lowercase() + _regionId = regionId } } @@ -43,14 +53,11 @@ class AES private constructor () { if (!isAuthorized()) { return null } - val cipher = Cipher.getInstance(AESConfig.CIPHER_ALGORITHM) - val keyspec = SecretKeySpec(_secret, AESConfig.KEY_SPEC) try { - cipher.init(Cipher.ENCRYPT_MODE, keyspec, IvParameterSpec(_iv)) - val output = ByteBuffer.allocate(cipher.getOutputSize(content.remaining())) - cipher.doFinal(content, output) - output.flip() - return output + val input = ByteArray(content.remaining()) + content.get(input) + val encrypted = encrypt(input) ?: return null + return ByteBuffer.wrap(encrypted) } catch (e: Exception) { println("failed to encrypt: ${e.toString()}") return null @@ -62,12 +69,15 @@ class AES private constructor () { TerminalLogger.errorf { "not authed, so not encrypting" } return null } - val cipher = Cipher.getInstance(AESConfig.CIPHER_ALGORITHM) - val keyspec = SecretKeySpec(_secret, AESConfig.KEY_SPEC) - cipher.init(Cipher.ENCRYPT_MODE, keyspec, IvParameterSpec(_iv)) try { - val encrypted = cipher.doFinal(content) - return encrypted + return when (_algorithm) { + "aes" -> aesCrypt(Cipher.ENCRYPT_MODE, content) + "chacha20" -> chacha20Crypt(content) + else -> { + TerminalLogger.errorf { "unsupported encryption algorithm: $_algorithm" } + null + } + } } catch (e: Exception) { TerminalLogger.errorf {"encrypt failed: $e"} return null @@ -78,14 +88,11 @@ class AES private constructor () { if (!isAuthorized()) { return null } - val cipher = Cipher.getInstance(AESConfig.CIPHER_ALGORITHM) - val keyspec = SecretKeySpec(_secret, AESConfig.KEY_SPEC) - cipher.init(Cipher.DECRYPT_MODE, keyspec, IvParameterSpec(_iv)) try { - val output = ByteBuffer.allocate(cipher.getOutputSize(ciphered.remaining())) - cipher.doFinal(ciphered, output) - output.flip() - return output + val input = ByteArray(ciphered.remaining()) + ciphered.get(input) + val decrypted = decrypt(input) ?: return null + return ByteBuffer.wrap(decrypted) } catch (e: Exception) { return null } @@ -95,16 +102,40 @@ class AES private constructor () { if (!isAuthorized()) { return null } - val cipher = Cipher.getInstance(AESConfig.CIPHER_ALGORITHM) - val keyspec = SecretKeySpec(_secret, AESConfig.KEY_SPEC) - cipher.init(Cipher.DECRYPT_MODE, keyspec, IvParameterSpec(_iv)) try { - val decrypted = cipher.doFinal(ciphered) - return decrypted + return when (_algorithm) { + "aes" -> aesCrypt(Cipher.DECRYPT_MODE, ciphered) + "chacha20" -> chacha20Crypt(ciphered) + else -> null + } } catch (e: Exception) { return null } } + + private fun aesCrypt(mode: Int, content: ByteArray): ByteArray { + val cipher = Cipher.getInstance(AESConfig.CIPHER_ALGORITHM) + val keyspec = SecretKeySpec(_secret, AESConfig.KEY_SPEC) + cipher.init(mode, keyspec, IvParameterSpec(_iv)) + return cipher.doFinal(content) + } + + private fun chacha20Crypt(content: ByteArray): ByteArray { + val engine = ChaCha7539Engine() + val nonce = chacha20Nonce() + engine.init(true, ParametersWithIV(KeyParameter(_secret), nonce)) + val output = ByteArray(content.size) + engine.processBytes(content, 0, content.size, output, 0) + return output + } + + private fun chacha20Nonce(): ByteArray { + return ByteBuffer.allocate(12) + .order(ByteOrder.BIG_ENDIAN) + .putLong(0L) + .putInt(_regionId.toInt()) + .array() + } } fun byteArray2Hex(array: ByteArray): String { @@ -113,4 +144,4 @@ fun byteArray2Hex(array: ByteArray): String { result.add(String.format("0x%02x", item.toInt() and 0xff)) } return result.joinToString(" ") -} \ No newline at end of file +} diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/utils/rsa.kt b/app/src/main/java/com/jihe/punchnet/sdlan/utils/rsa.kt index 0626ee1..5d62e1a 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/utils/rsa.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/utils/rsa.kt @@ -109,7 +109,6 @@ class RSA private constructor () { } pubkeyStr = pubFile.readText() - println("pub key is ${pubkeyStr}") } fun encrypt(input: ByteArray, use_private_key: Boolean = true): ByteArray {