Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
5b90fc0
Add tray icon and close-to-tray behavior in macOS/Windows, keep VPN r…
zaelgohary Sep 18, 2025
74356a9
feat(ui): redesign app structure, theme, and screens; fix Android bui…
zaelgohary Sep 18, 2025
b8fa1c2
WIP: finish all ui as figma
AlaaElattar Sep 18, 2025
2b58a1d
Add Peers input
AlaaElattar Sep 19, 2025
d029290
Add peers status to the app (#144)
AlaaElattar Sep 19, 2025
d40bd5f
handle mycelium restart
AlaaElattar Sep 19, 2025
a52bd8a
WIP: trying to handle peers
AlaaElattar Sep 19, 2025
99175e6
WIP: trying to integrate SOCKS5 tunneling as VPN
AlaaElattar Sep 19, 2025
9ed44aa
handle fetching peers && add users' peers to shared preferences
AlaaElattar Sep 19, 2025
abc3d79
handle removal for user peers only
AlaaElattar Sep 19, 2025
edc90aa
some fixes
AhmedHanafy725 Sep 19, 2025
4fc4c06
Socket forwarding not working
AhmedHanafy725 Sep 20, 2025
a930475
some updates
AhmedHanafy725 Sep 20, 2025
743d98d
Add geo location
AhmedHanafy725 Sep 20, 2025
5e8e6e5
Fixes
AhmedHanafy725 Sep 20, 2025
e0d2c2c
Fix the number of peers
AhmedHanafy725 Sep 20, 2025
62e17eb
Fix the traffic data
AhmedHanafy725 Sep 20, 2025
b36d4ff
Adjust colors and add animation
AhmedHanafy725 Sep 20, 2025
27bf8fb
Fix button colors
AhmedHanafy725 Sep 21, 2025
08473b8
Fix geo location
AhmedHanafy725 Sep 21, 2025
a28f2c8
Adjust the title spacing
AhmedHanafy725 Sep 21, 2025
bc60f3d
Adjust the add peer dialog and peer ip
AhmedHanafy725 Sep 21, 2025
c9b69a8
Change the country style
AhmedHanafy725 Sep 21, 2025
5fbd77a
Update the peers looking
AhmedHanafy725 Sep 21, 2025
a1e7975
Remove the container around the speed test
AhmedHanafy725 Sep 21, 2025
62d5bba
Fix start mycelium blocking
AhmedHanafy725 Sep 21, 2025
6079194
Add flag as windows doesn't support the emojis
AhmedHanafy725 Sep 21, 2025
eff1177
Merge remote-tracking branch 'origin/main_add_tray' into main_revamp_…
AhmedHanafy725 Sep 22, 2025
d663754
Fix merge conflict
AhmedHanafy725 Sep 22, 2025
4192279
Fix tray icon on windows
AhmedHanafy725 Sep 22, 2025
a485730
Fix tray icon on windows
AhmedHanafy725 Sep 22, 2025
22ae075
Merge pull request #145 from threefoldtech/main_revamp_design_fixes_tray
AhmedHanafy725 Sep 22, 2025
eaf0611
Add ip address in the settings
AhmedHanafy725 Sep 22, 2025
e302c9f
responsive
AhmedHanafy725 Sep 22, 2025
4176172
responsive fixes
AhmedHanafy725 Sep 22, 2025
0eb6010
Fix responsive on small screens
AhmedHanafy725 Sep 22, 2025
9116ec6
Fix ios
AlaaElattar Sep 24, 2025
29d7a72
Fix macos
AlaaElattar Sep 24, 2025
1c22eb3
Adjust the dark/light mode switch
AhmedHanafy725 Sep 25, 2025
6d839e6
Show the delete icon only when mycelium is stopped
AhmedHanafy725 Sep 25, 2025
bf10a3a
Hide socks5
AlaaElattar Sep 25, 2025
ca6833c
Update versions
AlaaElattar Sep 25, 2025
d955fa8
WIP: clean unused code
AhmedHanafy725 Sep 26, 2025
0519766
Cleanup
AhmedHanafy725 Sep 26, 2025
95b0c60
Fixing the layout errors
AhmedHanafy725 Sep 27, 2025
31eba25
Fix peers screen
AhmedHanafy725 Sep 27, 2025
edea7b2
Add link for mycelium read more
AhmedHanafy725 Sep 27, 2025
f2feb5a
Fix tray icon for macos
AlaaElattar Sep 28, 2025
08d1242
Adjust the time tickers for updates
AlaaElattar Sep 28, 2025
b2c5e7a
Update time tickers for mobile
AlaaElattar Sep 28, 2025
b1e2198
Adjust the animation
AlaaElattar Sep 28, 2025
8bfe5ea
Adjust the screen size for desktops
AlaaElattar Sep 28, 2025
f2c5ba6
Adjust colors
AlaaElattar Sep 28, 2025
3a46c1e
Hide the peer status when disconnected
AlaaElattar Sep 28, 2025
f0c7b5f
Adjust the logo
AlaaElattar Sep 28, 2025
c4aca3a
Allow adding/removing peers while mycelium is running
AlaaElattar Sep 28, 2025
4d9b737
Don't allow repeated peers
AlaaElattar Sep 28, 2025
c30f062
Reduce spaces
AlaaElattar Sep 28, 2025
4141c60
Fix card overflow
AlaaElattar Sep 28, 2025
f0f46bd
Adjust the home cards
AlaaElattar Sep 28, 2025
2e7deb3
udpate version
AlaaElattar Sep 28, 2025
857535c
Fix font size in traffic card
AhmedHanafy725 Sep 29, 2025
095824a
Update the version
AhmedHanafy725 Sep 29, 2025
155a6de
Remove test as the function are removed
AhmedHanafy725 Sep 29, 2025
3707218
Fix the analyze issues (#149)
AhmedHanafy725 Sep 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions android/.settings/org.eclipse.buildship.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
arguments=--init-script /var/folders/mq/q_rw798s6sdc7nz_7_dmsysm0000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/mq/q_rw798s6sdc7nz_7_dmsysm0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/Users/codescalerseg/.vscode/extensions/redhat.java-1.45.0-darwin-arm64/jre/21.0.8-macosx-aarch64
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
package tech.threefold.mycelium

import android.util.Log
import kotlinx.coroutines.*
import java.io.*
import java.net.*
import java.nio.charset.StandardCharsets

/**
* HTTP-to-SOCKS proxy server that accepts HTTP requests and forwards them through SOCKS5
* This bridges Android's HTTP proxy support with mycelium's SOCKS5 proxy
*/
class HttpToSocksProxy {
companion object {
private const val TAG = "HttpToSocksProxy"
private const val HTTP_PROXY_PORT = 8080
private const val SOCKS_HOST = "127.0.0.1"
private const val SOCKS_PORT = 1080
}

private var serverSocket: ServerSocket? = null
private var isRunning = false
private var proxyJob: Job? = null

/**
* Start the HTTP-to-SOCKS proxy server
*/
fun start() {
if (isRunning) {
Log.w(TAG, "HTTP-to-SOCKS proxy already running")
return
}

isRunning = true
Log.i(TAG, "Starting HTTP-to-SOCKS proxy on port $HTTP_PROXY_PORT")

// Test if mycelium SOCKS proxy is running
testSocksConnection()

proxyJob = CoroutineScope(Dispatchers.IO).launch {
try {
serverSocket = ServerSocket(HTTP_PROXY_PORT)
Log.i(TAG, "HTTP-to-SOCKS proxy listening on port $HTTP_PROXY_PORT")

while (isRunning) {
try {
val clientSocket = serverSocket?.accept()
if (clientSocket != null) {
Log.d(TAG, "New HTTP client connection from ${clientSocket.remoteSocketAddress}")
CoroutineScope(Dispatchers.IO).launch {
handleHttpClient(clientSocket)
}
}
} catch (e: Exception) {
if (isRunning) {
Log.e(TAG, "Error accepting HTTP client: ${e.message}")
}
}
}
} catch (e: Exception) {
Log.e(TAG, "HTTP-to-SOCKS proxy server error: ${e.message}")
}
}
}

/**
* Stop the HTTP-to-SOCKS proxy server
*/
fun stop() {
Log.i(TAG, "Stopping HTTP-to-SOCKS proxy")
isRunning = false
proxyJob?.cancel()

try {
serverSocket?.close()
} catch (e: Exception) {
Log.w(TAG, "Error closing server socket: ${e.message}")
}
serverSocket = null
}

private suspend fun handleHttpClient(clientSocket: Socket) {
try {
val clientInput = BufferedReader(InputStreamReader(clientSocket.getInputStream()))
val clientOutput = clientSocket.getOutputStream()

// Read HTTP request
val requestLine = clientInput.readLine()
if (requestLine == null) {
clientSocket.close()
return
}

Log.d(TAG, "HTTP request: $requestLine")

// Parse HTTP request
val parts = requestLine.split(" ")
if (parts.size < 3) {
sendHttpError(clientOutput, "400 Bad Request")
clientSocket.close()
return
}

val method = parts[0]
val url = parts[1]
val httpVersion = parts[2]

// Handle CONNECT method (HTTPS tunneling)
if (method == "CONNECT") {
handleHttpsConnect(url, clientInput, clientOutput, clientSocket)
} else {
// Handle regular HTTP requests
handleHttpRequest(method, url, httpVersion, clientInput, clientOutput, clientSocket)
}

} catch (e: Exception) {
Log.e(TAG, "Error handling HTTP client: ${e.message}")
} finally {
try {
clientSocket.close()
} catch (e: Exception) {
Log.w(TAG, "Error closing client socket: ${e.message}")
}
}
}

private suspend fun handleHttpsConnect(
hostPort: String,
clientInput: BufferedReader,
clientOutput: OutputStream,
clientSocket: Socket
) {
try {
// Parse host:port
val parts = hostPort.split(":")
val host = parts[0]
val port = if (parts.size > 1) parts[1].toInt() else 443

Log.d(TAG, "HTTPS CONNECT to $host:$port")

// Connect through SOCKS proxy
val socksSocket = connectThroughSocks(host, port)
if (socksSocket == null) {
sendHttpError(clientOutput, "502 Bad Gateway")
return
}

// Send 200 Connection Established
clientOutput.write("HTTP/1.1 200 Connection Established\r\n\r\n".toByteArray())
clientOutput.flush()

// Start bidirectional forwarding
val job1 = CoroutineScope(Dispatchers.IO).launch {
forwardData(clientSocket.getInputStream(), socksSocket.getOutputStream(), "client->socks")
}
val job2 = CoroutineScope(Dispatchers.IO).launch {
forwardData(socksSocket.getInputStream(), clientOutput, "socks->client")
}

// Wait for either direction to close
try {
job1.join()
} catch (e: Exception) {
// One direction closed, cancel the other
}

job1.cancel()
job2.cancel()
socksSocket.close()

} catch (e: Exception) {
Log.e(TAG, "Error in HTTPS CONNECT: ${e.message}")
}
}

private suspend fun handleHttpRequest(
method: String,
url: String,
httpVersion: String,
clientInput: BufferedReader,
clientOutput: OutputStream,
clientSocket: Socket
) {
try {
// Parse URL to extract host and port
val uri = URI(url)
val host = uri.host ?: return
val port = if (uri.port != -1) uri.port else 80

Log.d(TAG, "HTTP $method to $host:$port")

// Connect through SOCKS proxy
val socksSocket = connectThroughSocks(host, port)
if (socksSocket == null) {
sendHttpError(clientOutput, "502 Bad Gateway")
return
}

val socksOutput = socksSocket.getOutputStream()
val socksInput = socksSocket.getInputStream()

// Forward the original request
socksOutput.write("$method ${uri.path}${if (uri.query != null) "?" + uri.query else ""} $httpVersion\r\n".toByteArray())

// Forward headers
var line: String?
while (clientInput.readLine().also { line = it } != null && line!!.isNotEmpty()) {
socksOutput.write("$line\r\n".toByteArray())
}
socksOutput.write("\r\n".toByteArray())
socksOutput.flush()

// Forward response back to client
forwardData(socksInput, clientOutput, "socks->client")
socksSocket.close()

} catch (e: Exception) {
Log.e(TAG, "Error in HTTP request: ${e.message}")
}
}

private suspend fun connectThroughSocks(host: String, port: Int): Socket? {
return withContext(Dispatchers.IO) {
try {
val socksSocket = Socket(SOCKS_HOST, SOCKS_PORT)
socksSocket.soTimeout = 10000

val input = socksSocket.getInputStream()
val output = socksSocket.getOutputStream()

// SOCKS5 handshake
output.write(byteArrayOf(0x05, 0x01, 0x00)) // Version 5, 1 method, no auth
output.flush()

val authResponse = ByteArray(2)
if (input.read(authResponse) != 2 || authResponse[0] != 0x05.toByte() || authResponse[1] != 0x00.toByte()) {
Log.e(TAG, "SOCKS auth failed")
socksSocket.close()
return@withContext null
}

// SOCKS5 connect request
val hostBytes = host.toByteArray(StandardCharsets.UTF_8)
val request = ByteArrayOutputStream()
request.write(0x05) // Version
request.write(0x01) // Connect command
request.write(0x00) // Reserved
request.write(0x03) // Domain name address type
request.write(hostBytes.size) // Domain name length
request.write(hostBytes) // Domain name
request.write(port shr 8) // Port high byte
request.write(port and 0xFF) // Port low byte

output.write(request.toByteArray())
output.flush()

// Read connect response
val response = ByteArray(10)
val bytesRead = input.read(response)
if (bytesRead < 4 || response[0] != 0x05.toByte() || response[1] != 0x00.toByte()) {
Log.e(TAG, "SOCKS connect failed")
socksSocket.close()
return@withContext null
}

Log.d(TAG, "Successfully connected to $host:$port through SOCKS")
socksSocket
} catch (e: Exception) {
Log.e(TAG, "Failed to connect through SOCKS: ${e.message}")
null
}
}
}

private suspend fun forwardData(input: InputStream, output: OutputStream, direction: String) {
withContext(Dispatchers.IO) {
try {
val buffer = ByteArray(8192)
while (true) {
val bytesRead = input.read(buffer)
if (bytesRead <= 0) break
output.write(buffer, 0, bytesRead)
output.flush()
}
} catch (e: Exception) {
Log.d(TAG, "Data forwarding stopped ($direction): ${e.message}")
}
}
}

private fun sendHttpError(output: OutputStream, error: String) {
try {
val response = "HTTP/1.1 $error\r\nContent-Length: 0\r\n\r\n"
output.write(response.toByteArray())
output.flush()
} catch (e: Exception) {
Log.e(TAG, "Error sending HTTP error: ${e.message}")
}
}

private fun testSocksConnection() {
CoroutineScope(Dispatchers.IO).launch {
try {
Log.i(TAG, "Testing connection to mycelium SOCKS proxy at $SOCKS_HOST:$SOCKS_PORT")
val testSocket = Socket()
testSocket.connect(InetSocketAddress(SOCKS_HOST, SOCKS_PORT), 5000)
testSocket.close()
Log.i(TAG, "✅ Mycelium SOCKS proxy is reachable at $SOCKS_HOST:$SOCKS_PORT")
} catch (e: Exception) {
Log.e(TAG, "❌ Cannot connect to mycelium SOCKS proxy at $SOCKS_HOST:$SOCKS_PORT: ${e.message}")
Log.e(TAG, "Make sure mycelium is running and SOCKS proxy is enabled")
}
}
}

/**
* Get the port number this proxy is listening on
*/
fun getPort(): Int = HTTP_PROXY_PORT
}
Loading
Loading