Skip to content

Commit

Permalink
Guard IP connections to the host address #292
Browse files Browse the repository at this point in the history
  • Loading branch information
pyamsoft committed May 3, 2024
1 parent 6aad598 commit 37ad62b
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 291 deletions.
1 change: 0 additions & 1 deletion server/src/main/java/com/pyamsoft/tetherfi/server/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.pyamsoft.tetherfi.server


/**
* What the fuck is this
* https://stackoverflow.com/questions/10006459/regular-expression-for-ip-address-validation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,180 +22,171 @@ import androidx.compose.runtime.Stable
import com.pyamsoft.tetherfi.core.Timber
import com.pyamsoft.tetherfi.server.IP_ADDRESS_REGEX
import com.pyamsoft.tetherfi.server.Server
import com.pyamsoft.tetherfi.server.clients.TetherClient
import com.pyamsoft.tetherfi.server.status.RunningStatus
import kotlinx.coroutines.flow.Flow

interface BroadcastNetworkStatus : Server {

@CheckResult
fun onGroupInfoChanged(): Flow<GroupInfo>
@CheckResult fun onGroupInfoChanged(): Flow<GroupInfo>

@CheckResult
fun onConnectionInfoChanged(): Flow<ConnectionInfo>
@CheckResult fun onConnectionInfoChanged(): Flow<ConnectionInfo>

@CheckResult
fun getCurrentProxyStatus(): RunningStatus
@CheckResult fun getCurrentProxyStatus(): RunningStatus

@CheckResult fun onProxyStatusChanged(): Flow<RunningStatus>

@Stable
@Immutable
sealed interface GroupInfo {

data object Unchanged : GroupInfo

data class Connected
internal constructor(
val ssid: String,
val password: String,
) : GroupInfo

data object Empty : GroupInfo

data class Error
internal constructor(
val error: Throwable,
) : GroupInfo

@CheckResult
fun onProxyStatusChanged(): Flow<RunningStatus>

@Stable
@Immutable
sealed interface GroupInfo {

data object Unchanged : GroupInfo

data class Connected
internal constructor(
val ssid: String,
val password: String,
) : GroupInfo

data object Empty : GroupInfo

data class Error
internal constructor(
val error: Throwable,
) : GroupInfo

@CheckResult
fun update(onUpdate: (Connected) -> Connected): GroupInfo {
return when (this) {
is Connected -> onUpdate(this)
is Empty -> this
is Error -> this
is Unchanged -> this
}
}
fun update(onUpdate: (Connected) -> Connected): GroupInfo {
return when (this) {
is Connected -> onUpdate(this)
is Empty -> this
is Error -> this
is Unchanged -> this
}
}
}

@Stable
@Immutable
sealed interface ConnectionInfo {
data object Unchanged : ConnectionInfo

data class Connected
internal constructor(
val hostName: String,
) : ConnectionInfo {

private data class NotAnIpAddressException(val hostName: String) :
IllegalStateException("Not an IP address: $hostName")

// Is this Connection info from the server an IP address or a DNS hostname?
// It's almost always an IP address
val isIpAddress by lazy { IP_ADDRESS_REGEX.matches(hostName) }

/**
* Split up an IP address
*
* 192.168.49.1 -> [ 192, 168, 49, 1 ]
*/
private val splitUpIp by lazy {
ensureIpAddress()
hostName.split(".")
}

/**
* IP first block
*
* 192.168.49.1 -> 192
*/
private val ipFirstBlock by lazy {
ensureIpAddress()
splitUpIp[0]
}

/**
* IP second block
*
* 192.168.49.1 -> 168
*/
private val ipSecondBlock by lazy {
ensureIpAddress()
splitUpIp[1]
}

/**
* IP third block
*
* 192.168.49.1 -> 49
*/
private val ipThirdBlock by lazy {
ensureIpAddress()
splitUpIp[2]
}

/** If this is not an IP address, you've done something wrong */
private fun ensureIpAddress() {
if (!isIpAddress) {
throw NotAnIpAddressException(hostName)
}
}

/**
* If the server is an IP address, then we should check that this client is also IP
* addressable.
*
* If the client is a hostname client (do we have any of these ever?), then this method does
* not apply
*/
@CheckResult
fun isClientWithinAddressableIpRange(ip: String): Boolean {
val splitUpAddress = ip.split(".")

// Needs to be 4 sections
if (splitUpAddress.size != 4) {
Timber.w { "Split up IP address was wrong format: $ip $splitUpAddress" }
return false
}

@Stable
@Immutable
sealed interface ConnectionInfo {
data object Unchanged : ConnectionInfo

data class Connected
internal constructor(
val hostName: String,
) : ConnectionInfo {

private data class NotAnIpAddressException(val hostName: String) :
IllegalStateException("Not an IP address: $hostName")

// Is this Connection info from the server an IP address or a DNS hostname?
// It's almost always an IP address
val isIpAddress by lazy { IP_ADDRESS_REGEX.matches(hostName) }

/**
* Split up an IP address
*
* 192.168.49.1 -> [ 192, 168, 49, 1 ]
*/
private val splitUpIp by lazy {
ensureIpAddress()
hostName.split(".")
}

/**
* IP first block
*
* 192.168.49.1 -> 192
*/
private val ipFirstBlock by lazy {
ensureIpAddress()
splitUpIp[0]
}

/**
* IP second block
*
* 192.168.49.1 -> 168
*/
private val ipSecondBlock by lazy {
ensureIpAddress()
splitUpIp[1]
}

/**
* IP third block
*
* 192.168.49.1 -> 49
*/
private val ipThirdBlock by lazy {
ensureIpAddress()
splitUpIp[2]
}

/**
* If this is not an IP address, you've done something wrong
*/
private fun ensureIpAddress() {
if (!isIpAddress) {
throw NotAnIpAddressException(hostName)
}
}

/**
* If the server is an IP address, then we should check that this client is
* also IP addressable.
*
* If the client is a hostname client (do we have any of these ever?), then
* this method does not apply
*/
@CheckResult
fun isClientWithinAddressableIpRange(client: TetherClient.IpAddress): Boolean {
val ip = client.ip
val splitUpAddress = ip.split(".")

// Needs to be 4 sections
if (splitUpAddress.size != 4) {
Timber.w { "Split up IP address was wrong format: $ip $splitUpAddress" }
return false
}

val first = splitUpAddress[0]
val second = splitUpAddress[1]
val third = splitUpAddress[2]

if (first != ipFirstBlock) {
Timber.w { "Mismatch IP first block: $ip $hostName" }
return false
}

if (second != ipSecondBlock) {
Timber.w { "Mismatch IP second block: $ip $hostName" }
return false
}

if (third != ipThirdBlock) {
Timber.w { "Mismatch IP third block: $ip $hostName" }
return false
}

// This is a "matching" IP of
// XXX.YYY.ZZZ.???
// which is good enough for us
return true
}
val first = splitUpAddress[0]
val second = splitUpAddress[1]
val third = splitUpAddress[2]

if (first != ipFirstBlock) {
Timber.w { "Mismatch IP first block: $ip $hostName" }
return false
}

data object Empty : ConnectionInfo

data class Error
internal constructor(
val error: Throwable,
) : ConnectionInfo

@CheckResult
fun update(onUpdate: (Connected) -> Connected): ConnectionInfo {
return when (this) {
is Connected -> onUpdate(this)
is Empty -> this
is Error -> this
is Unchanged -> this
}
if (second != ipSecondBlock) {
Timber.w { "Mismatch IP second block: $ip $hostName" }
return false
}

if (third != ipThirdBlock) {
Timber.w { "Mismatch IP third block: $ip $hostName" }
return false
}

// This is a "matching" IP of
// XXX.YYY.ZZZ.???
// which is good enough for us
return true
}
}

data object Empty : ConnectionInfo

data class Error
internal constructor(
val error: Throwable,
) : ConnectionInfo

@CheckResult
fun update(onUpdate: (Connected) -> Connected): ConnectionInfo {
return when (this) {
is Connected -> onUpdate(this)
is Empty -> this
is Error -> this
is Unchanged -> this
}
}
}
}
Loading

0 comments on commit 37ad62b

Please sign in to comment.