work blyattt

This commit is contained in:
2026-05-27 20:49:34 +03:00
parent f05b8dc72d
commit fead9d4b20
7 changed files with 428 additions and 70 deletions

View File

@ -68,6 +68,7 @@ tasks.register("generateTestServerConfig") {
{ {
"type": "server", "type": "server",
"address": "10.1.0.1", "address": "10.1.0.1",
"outInterface": "enp9s0",
"port": 9093 "port": 9093
} }
""".trimIndent()) """.trimIndent())

View File

@ -1,5 +1,15 @@
#include <jni.h> #include <jni.h>
#include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <linux/if_tun.h> #include <linux/if_tun.h>
#include <linux/if.h> #include <linux/if.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -10,6 +20,81 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <stdlib.h>
JNIEXPORT jint JNICALL
Java_su_sonoma_TunDevice_addDefaultRoute(
JNIEnv *env,
jobject obj,
jstring ifname
) {
struct {
struct nlmsghdr nlh;
struct rtmsg rtm;
char buf[4096];
} req;
memset(&req, 0, sizeof(req));
const char *c_ifname = (*env)->GetStringUTFChars(env, ifname, 0);
if (c_ifname == NULL) {
return -1;
}
int ifindex = if_nametoindex(c_ifname);
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.nlh.nlmsg_type = RTM_NEWROUTE;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
req.nlh.nlmsg_seq = 1;
req.nlh.nlmsg_pid = getpid();
req.rtm.rtm_family = AF_INET;
req.rtm.rtm_table = RT_TABLE_MAIN;
req.rtm.rtm_protocol = RTPROT_STATIC;
req.rtm.rtm_scope = RT_SCOPE_UNIVERSE;
req.rtm.rtm_type = RTN_UNICAST;
req.rtm.rtm_dst_len = 0;
struct rtattr *rta = (struct rtattr *) (((char *) &req) + NLMSG_ALIGN(req.nlh.nlmsg_len));
rta->rta_type = RTA_OIF;
rta->rta_len = RTA_LENGTH(sizeof(int));
*(int *) RTA_DATA(rta) = ifindex;
req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + RTA_LENGTH(sizeof(int));
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) {
perror("socket");
return -1;
}
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
struct sockaddr_nl kernel;
memset(&kernel, 0, sizeof(kernel));
kernel.nl_family = AF_NETLINK;
if (sendto(sock, &req, req.nlh.nlmsg_len, 0,
(struct sockaddr *)&kernel, sizeof(kernel)) < 0) {
perror("sendto");
close(sock);
return -1;
}
close(sock);
return 0;
}
static int tun_set_up(const char *ifname) { static int tun_set_up(const char *ifname) {
struct ifreq ifr; struct ifreq ifr;
@ -99,3 +184,188 @@ Java_su_sonoma_TunDevice_ioctlTun(
return result; return result;
} }
static int add_route(const char *ifname, const char *cidr) {
struct {
struct nlmsghdr nlh;
struct rtmsg rtm;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
int ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
perror("if_nametoindex");
return -1;
}
char ip[64];
int prefix;
if (sscanf(cidr, "%63[^/]/%d", ip, &prefix) != 2) {
fprintf(stderr, "Invalid CIDR\n");
return -1;
}
struct in_addr dst;
if (inet_pton(AF_INET, ip, &dst) != 1) {
perror("inet_pton");
return -1;
}
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.nlh.nlmsg_type = RTM_NEWROUTE;
req.nlh.nlmsg_flags =
NLM_F_REQUEST |
NLM_F_CREATE |
NLM_F_EXCL;
req.rtm.rtm_family = AF_INET;
req.rtm.rtm_table = RT_TABLE_MAIN;
req.rtm.rtm_protocol = RTPROT_STATIC;
req.rtm.rtm_scope = RT_SCOPE_LINK;
req.rtm.rtm_type = RTN_UNICAST;
req.rtm.rtm_dst_len = prefix;
struct rtattr *rta;
rta = (struct rtattr *)
(((char *)&req) + NLMSG_ALIGN(req.nlh.nlmsg_len));
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(sizeof(dst));
memcpy(RTA_DATA(rta), &dst, sizeof(dst));
req.nlh.nlmsg_len =
NLMSG_ALIGN(req.nlh.nlmsg_len) +
RTA_LENGTH(sizeof(dst));
rta = (struct rtattr *)
(((char *)&req) + NLMSG_ALIGN(req.nlh.nlmsg_len));
rta->rta_type = RTA_OIF;
rta->rta_len = RTA_LENGTH(sizeof(int));
memcpy(RTA_DATA(rta), &ifindex, sizeof(int));
req.nlh.nlmsg_len =
NLMSG_ALIGN(req.nlh.nlmsg_len) +
RTA_LENGTH(sizeof(int));
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) {
perror("socket");
return -1;
}
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
if (sendto(
sock,
&req,
req.nlh.nlmsg_len,
0,
(struct sockaddr *)&nladdr,
sizeof(nladdr)
) < 0) {
perror("sendto");
close(sock);
return -1;
}
close(sock);
return 0;
}
JNIEXPORT jint JNICALL
Java_su_sonoma_TunDevice_addRoute(
JNIEnv *env,
jobject obj,
jstring ifname,
jstring cidr
) {
const char *c_ifname =
(*env)->GetStringUTFChars(env, ifname, 0);
const char *c_cidr =
(*env)->GetStringUTFChars(env, cidr, 0);
if (!c_ifname || !c_cidr) {
return -1;
}
int result = add_route(c_ifname, c_cidr);
(*env)->ReleaseStringUTFChars(env, ifname, c_ifname);
(*env)->ReleaseStringUTFChars(env, cidr, c_cidr);
return result;
}
JNIEXPORT jint JNICALL
Java_su_sonoma_TunDevice_enableIpForwarding(
JNIEnv *env,
jobject obj
) {
int fd = open(
"/proc/sys/net/ipv4/ip_forward",
O_WRONLY
);
if (fd < 0) {
return -1;
}
const char *value = "1";
ssize_t result = write(fd, value, 1);
close(fd);
return result == 1 ? 0 : -1;
}
JNIEXPORT jint JNICALL
Java_su_sonoma_TunDevice_enableMasquerade(
JNIEnv *env,
jobject obj,
jstring iface
) {
const char *c_iface =
(*env)->GetStringUTFChars(env, iface, 0);
if (c_iface == NULL) {
return -1;
}
char command[256];
snprintf(
command,
sizeof(command),
"iptables -t nat -A POSTROUTING -o %s -j MASQUERADE",
c_iface
);
int result = system(command);
(*env)->ReleaseStringUTFChars(env, iface, c_iface);
return result;
}

View File

@ -1,48 +1,75 @@
package su.sonoma package su.sonoma
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.DataInputStream import java.io.DataInputStream
import java.net.Socket import java.net.Socket
fun startClient(host: String, address: String, port: Int) = runBlocking { fun startClient(host: String, address: String, port: Int, tun: String) = runBlocking {
println("Client started") println("Client started")
val tun = TunDevice(address) val tun = TunDevice(address, tun)
tun.addDefaultRoute()
println("Client trying to connect - $host:$port") println("Client trying to connect - $host:$port")
val socket = Socket(host, port) val socket = Socket(host, port)
println("Client trying connected - $host:$port")
val input = socket.getInputStream() launch(Dispatchers.IO) {
val output = socket.getOutputStream() try {
handleClient(socket, tun)
println("Client trying to pair") } catch (e: Exception) {
e.printStackTrace()
launch {
val buffer = ByteArray(32768)
println("Client connected - $host:$port")
while (true) {
val len = tun.input.read(buffer)
if (len > 0) {
output.write(len shr 8)
output.write(len)
output.write(buffer, 0, len)
}
} }
} }
launch { launch(Dispatchers.IO) {
val buffer = ByteArray(32768) try {
forwardClient(socket, tun)
while (true) { } catch (e: Exception) {
val hi = input.read() e.printStackTrace()
val lo = input.read()
val len = (hi shl 8) or lo
DataInputStream(input).readFully(buffer, 0, len)
tun.output.write(buffer, 0, len)
} }
} }
} }
fun forwardClient(socket: Socket, tun: TunDevice) {
val buffer = ByteArray(1500)
val input = BufferedInputStream(socket.getInputStream())
while (true) {
val hi = input.read()
if (hi == -1) break
val lo = input.read()
if (lo == -1) break
val len = (hi shl 8) or lo
//println("Message length: $len")
var read = 0
while (read < len) {
val r = input.read(buffer, read, len - read)
if (r == -1) break
read += r
}
tun.output.write(buffer, 0, read)
tun.output.flush()
}
}
fun handleClient(socket: Socket, tun: TunDevice) {
val output = BufferedOutputStream(socket.getOutputStream())
val buffer = ByteArray(1500)
while (true) {
val len = tun.input.read(buffer)
if (len <= 0) continue
output.write((len shr 8) and 0xFF)
output.write(len and 0xFF)
output.write(buffer, 0, len)
output.flush()
}
}

View File

@ -8,6 +8,8 @@ import java.io.File
data class Config( data class Config(
val type: String, val type: String,
val host: String? = null, val host: String? = null,
val tun: String = "awake0",
val outInterface: String = "eth0",
val address: String, val address: String,
val port: Int, val port: Int,
) )

View File

@ -7,8 +7,8 @@ fun main(args: Array<String>) {
val config = loadConfig(File(configArg)) val config = loadConfig(File(configArg))
when (config.type) { when (config.type) {
"server" -> startServer(config.address, config.port) "server" -> startServer(config.address, config.port, config.tun, config.outInterface)
"client" -> startClient(config.host ?: error("Invalid config"), config.address, config.port) "client" -> startClient(config.host ?: error("Invalid config"), config.address, config.port, config.tun)
else -> { else -> {
error("Invalid type") error("Invalid type")
} }

View File

@ -1,12 +1,17 @@
package su.sonoma package su.sonoma
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.io.DataInputStream import java.io.DataInputStream
fun startServer(address: String, port: Int) = runBlocking { fun startServer(address: String, port: Int, tun: String, outIntefrace: String) = runBlocking {
val tun = TunDevice(address) val tun = TunDevice(address, "awake0")
tun.addRoute()
tun.enableIpForwarding()
tun.enableMasquerade(outIntefrace)
val server = ServerSocket(port) val server = ServerSocket(port)
println("Server started on $port") println("Server started on $port")
@ -21,55 +26,80 @@ fun startServer(address: String, port: Int) = runBlocking {
try { try {
handle(client, tun) handle(client, tun)
} catch (e: Exception) { } catch (e: Exception) {
println("${client.inetAddress} Client disconnected: ${e.message}") e.printStackTrace()
}
}
launch(Dispatchers.IO) {
try {
forward(client, tun)
} catch (e: Exception) {
e.printStackTrace()
} }
} }
} }
} }
suspend fun handle(socket: Socket, tun: TunDevice) { fun handle(socket: Socket, tun: TunDevice) {
val input = withContext(Dispatchers.IO) {
socket.getInputStream()
}
val output = withContext(Dispatchers.IO) {
socket.getOutputStream()
}
val buffer = ByteArray(32768) val input = BufferedInputStream(socket.getInputStream())
val buffer = ByteArray(1500)
withContext(Dispatchers.IO) {
while (true) { while (true) {
val hi = input.read() val hi = input.read()
val lo = input.read() val lo = input.read()
if (hi == -1 || lo == -1) {
println("${socket.inetAddress} Client disconnected")
break
}
val len = (hi shl 8) or lo val len = (hi shl 8) or lo
DataInputStream(input).readFully(buffer, 0, len) //println("Message length: $len")
val packet = buffer.copyOf(len) if (len <= 0 || len > buffer.size) {
println("Invalid packet size: $len")
break
}
val response = forwardPacket(packet) var offset = 0
while (offset < len) {
val r = input.read(buffer, offset, len - offset)
//output.write(response.size shr 8) //println("Read $r from client")
//output.write(response.size)
//output.write(response) if (r == -1) {
tun.output.write(packet, 0, len) println("${socket.inetAddress} Client closed stream")
return
}
offset += r
}
runCatching {
tun.output.write(buffer, 0, len)
tun.output.flush()
}
//println("Write in tun")
}
}
fun forward(socket: Socket, tun: TunDevice) {
val readBuffer = ByteArray(1500)
val output = BufferedOutputStream(socket.getOutputStream())
while (true) {
val responseLen = tun.input.read(readBuffer)
if (responseLen > 0) {
output.write((responseLen shr 8) and 0xFF)
output.write(responseLen and 0xFF)
output.write(readBuffer, 0, responseLen)
output.flush()
} }
} }
} }
fun forwardPacket(packet: ByteArray): ByteArray {
val host = "localhost"
val port = 1080
val socket = Socket(host, port)
socket.getOutputStream().write(packet)
val response = socket.getInputStream().readBytes()
socket.close()
return response
}

View File

@ -5,11 +5,13 @@ import java.io.FileDescriptor
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.nio.channels.DatagramChannel
import java.nio.channels.FileChannel
class TunDevice( class TunDevice(
private val ip: String private val ip: String,
private val name: String
) { ) {
val name: String = "tun0"
companion object { companion object {
init { init {
@ -46,4 +48,30 @@ class TunDevice(
ip: String, ip: String,
flags: Int flags: Int
): Int ): Int
private external fun addDefaultRoute(
name: String,
): Int
fun addDefaultRoute() {
addDefaultRoute(name)
}
private external fun addRoute(ifName: String, cidr: String): Int
fun addRoute() {
addRoute(name, toCidr24(ip))
}
fun toCidr24(ip: String): String {
val parts = ip.split(".")
require(parts.size == 4)
return "${parts[0]}.${parts[1]}.${parts[2]}.0/24"
}
external fun enableIpForwarding(): Int
external fun enableMasquerade(iface: String): Int
} }