Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 NetBird/Source/App/ViewModels/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class ViewModel: ObservableObject {
}
@Published var forceRelayConnection = true
@Published var showForceRelayAlert = false
@Published var disableIPv6 = false
@Published var connectOnDemand = false
@Published var showOnDemandAlert = false
@Published var showOnDemandConflictAlert = false
Expand Down Expand Up @@ -607,6 +608,18 @@ class ViewModel: ObservableObject {
loadRosenpassSettings()
}

func setDisableIPv6(disabled: Bool) {
self.disableIPv6 = disabled
configProvider.disableIPv6 = disabled
if !configProvider.commit() {
print("Failed to update IPv6 settings")
}
}

func loadIPv6Settings() {
self.disableIPv6 = configProvider.disableIPv6
}

func setForcedRelayConnection(isEnabled: Bool) {
let userDefaults = UserDefaults(suiteName: GlobalConstants.userPreferencesSuiteName)
userDefaults?.set(isEnabled, forKey: GlobalConstants.keyForceRelayConnection)
Expand Down
6 changes: 6 additions & 0 deletions NetBird/Source/App/Views/AdvancedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@ struct AdvancedView: View {
viewModel.setForcedRelayConnection(isEnabled: value)
}

Toggle("Disable IPv6", isOn: $viewModel.disableIPv6)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onChange(of: viewModel.disableIPv6) { value in
viewModel.setDisableIPv6(disabled: value)
}
}
}
.onAppear {
viewModel.loadRosenpassSettings()
viewModel.loadPreSharedKey()
viewModel.loadIPv6Settings()
}
.navigationTitle("Advanced")
.navigationBarTitleDisplayMode(.inline)
Expand Down
6 changes: 6 additions & 0 deletions NetBird/Source/App/Views/Components/PeerCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ struct PeerCard: View {
.font(.subheadline)
.foregroundColor(Color("TextSecondary"))
.lineLimit(1)
if let ipv6 = peer.ipv6, !ipv6.isEmpty {
Text(ipv6)
.font(.subheadline)
.foregroundColor(Color("TextSecondary"))
.lineLimit(1)
}
}
Spacer()
ConnectionIndicator(status: peer.connStatus)
Expand Down
4 changes: 4 additions & 0 deletions NetBird/Source/App/Views/Components/PeerDetailSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ struct PeerDetailSheet: View {
NavigationView {
List {
Section {
detailRow("IPv4", peer.ip)
if let ipv6 = peer.ipv6, !ipv6.isEmpty {
detailRow("IPv6", ipv6)
}
detailRow("Status", peer.connStatus)
detailRow("Last status update", relativeDateText)
detailRow("Connection type", peer.relayed ? "Relayed" : "P2P")
Expand Down
27 changes: 27 additions & 0 deletions NetBird/Source/App/Views/TV/TVSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,32 @@ struct TVSettingsView: View {
)
}

TVSettingsSection(title: "Network") {
TVSettingsToggleRow(
icon: "network",
title: "Disable IPv6",
subtitle: "Disable IPv6 overlay addressing on the tunnel",
isOn: Binding(
get: { viewModel.disableIPv6 },
set: { newValue in
viewModel.setDisableIPv6(disabled: newValue)
}
)
)

TVSettingsToggleRow(
icon: "arrow.triangle.branch",
title: "Force Relay",
subtitle: "Force all connections through relay servers",
isOn: Binding(
get: { viewModel.forceRelayConnection },
set: { newValue in
viewModel.setForcedRelayConnection(isEnabled: newValue)
}
)
)
}

TVSettingsSection(title: "Security") {
TVSettingsRow(
icon: "key.fill",
Expand Down Expand Up @@ -125,6 +151,7 @@ struct TVSettingsView: View {
// Load settings from storage to sync UI with actual values
viewModel.loadRosenpassSettings()
viewModel.loadPreSharedKey()
viewModel.loadIPv6Settings()
}
.sheet(isPresented: $showDocsQRCode) {
TVQRCodeSheet(
Expand Down
2 changes: 2 additions & 0 deletions NetBirdTVNetworkExtension/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {

let peerInfo = PeerInfo(
ip: peer.ip,
ipv6: peer.iPv6,
fqdn: peer.fqdn,
localIceCandidateEndpoint: peer.localIceCandidateEndpoint,
remoteIceCandidateEndpoint: peer.remoteIceCandidateEndpoint,
Expand All @@ -579,6 +580,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let clientState = adapter.clientState
let statusDetails = StatusDetails(
ip: statusDetailsMessage.getIP(),
ipv6: statusDetailsMessage.getIPv6(),
fqdn: statusDetailsMessage.getFQDN(),
managementStatus: clientState,
peerInfo: peerInfoArray
Expand Down
29 changes: 29 additions & 0 deletions NetbirdKit/ConfigurationProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ protocol ConfigurationProvider {
/// Whether Rosenpass permissive mode is enabled (allows non-Rosenpass peers)
var rosenpassPermissive: Bool { get set }

// MARK: - IPv6

/// Whether IPv6 overlay addressing is disabled
var disableIPv6: Bool { get set }

// MARK: - Pre-Shared Key

/// The current pre-shared key (empty string if not set)
Expand Down Expand Up @@ -86,6 +91,23 @@ final class iOSConfigurationProvider: ConfigurationProvider {
}
}

// MARK: - IPv6

var disableIPv6: Bool {
get {
var result = ObjCBool(false)
do {
try preferences.getDisableIPv6(&result)
} catch {
print("ConfigurationProvider: Failed to read disableIPv6 - \(error)")
}
return result.boolValue
}
set {
preferences.setDisableIPv6(newValue)
}
}

// MARK: - Pre-Shared Key

var preSharedKey: String {
Expand Down Expand Up @@ -143,6 +165,13 @@ final class tvOSConfigurationProvider: ConfigurationProvider {
set { updateJSONField(field: "RosenpassPermissive", value: newValue) }
}

// MARK: - IPv6

var disableIPv6: Bool {
get { extractJSONBool(field: "DisableIPv6") ?? false }
set { updateJSONField(field: "DisableIPv6", value: newValue) }
}

// MARK: - Pre-Shared Key

var preSharedKey: String {
Expand Down
9 changes: 8 additions & 1 deletion NetbirdKit/NetworkChangeListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@ class NetworkChangeListener: NSObject, NetBirdSDKNetworkChangeListenerProtocol {
guard let validIP = p0, !validIP.isEmpty else {
return
}

self.interfaceIP = validIP
self.tunnelManager.setInterfaceIP(interfaceIP: validIP)
}

func setInterfaceIPv6(_ p0: String?) {
guard let validIPv6 = p0, !validIPv6.isEmpty else {
return
}
self.tunnelManager.setInterfaceIPv6(interfaceIPv6: validIPv6)
}

func parseRoutesToNESettings(routesString: String) -> ([NEIPv4Route], [NEIPv6Route], Bool) {
var v4Routes : [NEIPv4Route] = []
Expand Down
8 changes: 7 additions & 1 deletion NetbirdKit/StatusDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Combine

struct StatusDetails: Codable {
var ip: String
var ipv6: String?
var fqdn: String
var managementStatus: ClientState
var peerInfo: [PeerInfo]
Expand All @@ -18,6 +19,7 @@ struct StatusDetails: Codable {
extension StatusDetails: Equatable {
static func == (lhs: StatusDetails, rhs: StatusDetails) -> Bool {
return lhs.ip == rhs.ip &&
lhs.ipv6 == rhs.ipv6 &&
lhs.fqdn == rhs.fqdn &&
lhs.managementStatus == rhs.managementStatus &&
lhs.peerInfo == rhs.peerInfo
Expand All @@ -27,6 +29,7 @@ extension StatusDetails: Equatable {
class PeerInfo: ObservableObject, Codable, Identifiable {
var id = UUID()
var ip: String
var ipv6: String?
var fqdn: String
var localIceCandidateEndpoint: String
var remoteIceCandidateEndpoint: String
Expand All @@ -45,11 +48,12 @@ class PeerInfo: ObservableObject, Codable, Identifiable {
var routes: [String]
var selected: Bool = false

init(ip: String, fqdn: String, localIceCandidateEndpoint: String, remoteIceCandidateEndpoint: String,
init(ip: String, ipv6: String? = nil, fqdn: String, localIceCandidateEndpoint: String, remoteIceCandidateEndpoint: String,
localIceCandidateType: String, remoteIceCandidateType: String, pubKey: String, latency: String,
bytesRx: Int64, bytesTx: Int64, connStatus: String, connStatusUpdate: String, direct: Bool,
lastWireguardHandshake: String, relayed: Bool, rosenpassEnabled: Bool, routes: [String]) {
self.ip = ip
self.ipv6 = ipv6
self.fqdn = fqdn
self.localIceCandidateEndpoint = localIceCandidateEndpoint
self.remoteIceCandidateEndpoint = remoteIceCandidateEndpoint
Expand All @@ -73,6 +77,7 @@ extension PeerInfo: Equatable {
static func == (lhs: PeerInfo, rhs: PeerInfo) -> Bool {
return lhs.id == rhs.id &&
lhs.ip == rhs.ip &&
lhs.ipv6 == rhs.ipv6 &&
lhs.fqdn == rhs.fqdn &&
lhs.localIceCandidateEndpoint == rhs.localIceCandidateEndpoint &&
lhs.remoteIceCandidateEndpoint == rhs.remoteIceCandidateEndpoint &&
Expand All @@ -95,6 +100,7 @@ extension PeerInfo: Equatable {
extension PeerInfo {
func update(from newInfo: PeerInfo) {
self.ip = newInfo.ip
self.ipv6 = newInfo.ipv6
self.fqdn = newInfo.fqdn
self.localIceCandidateEndpoint = newInfo.localIceCandidateEndpoint
self.remoteIceCandidateEndpoint = newInfo.remoteIceCandidateEndpoint
Expand Down
2 changes: 2 additions & 0 deletions NetbirdNetworkExtension/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {

let peerInfo = PeerInfo(
ip: peer.ip,
ipv6: peer.iPv6,
fqdn: peer.fqdn,
localIceCandidateEndpoint: peer.localIceCandidateEndpoint,
remoteIceCandidateEndpoint: peer.remoteIceCandidateEndpoint,
Expand All @@ -369,6 +370,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let clientState = adapter.clientState
let statusDetails = StatusDetails(
ip: statusDetailsMessage.getIP(),
ipv6: statusDetailsMessage.getIPv6(),
fqdn: statusDetailsMessage.getFQDN(),
managementStatus: clientState,
peerInfo: peerInfoArray
Expand Down
55 changes: 45 additions & 10 deletions NetbirdNetworkExtension/PacketTunnelProviderSettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ class PacketTunnelProviderSettingsManager {
private weak var packetTunnelProvider: PacketTunnelProvider?

private var interfaceIP: String?
private var interfaceIPv6: String?
private var ipv4Routes: [NEIPv4Route]?
private var ipv6Routes: [NEIPv6Route]?
private var dnsSettings: NEDNSSettings?
private var needFallbackNS: Bool = false

private var containsDefaultRoute: Bool = false

// Link-local dummy IPv6 used to satisfy NEIPv6Settings when the
// interface has no IPv6 address but we still need a ::/0 blackhole route
// to prevent IPv6 leaks while the IPv4 default route is in the tunnel.
private static let ipv6BlackholeAddress = "fe80::1"
private static let ipv6BlackholePrefix: NSNumber = 64

init(with packetTunnelProvider: PacketTunnelProvider) {
self.packetTunnelProvider = packetTunnelProvider
}

func setRoutes(v4Routes: [NEIPv4Route], v6Routes: [NEIPv6Route], containsDefault: Bool) {
self.needFallbackNS = containsDefault
self.containsDefaultRoute = containsDefault
self.ipv4Routes = v4Routes
self.ipv6Routes = v6Routes
self.updateTunnel()
Expand Down Expand Up @@ -57,7 +66,11 @@ class PacketTunnelProviderSettingsManager {
func setInterfaceIP(interfaceIP: String) {
self.interfaceIP = interfaceIP
}


func setInterfaceIPv6(interfaceIPv6: String) {
self.interfaceIPv6 = interfaceIPv6
}

func getInterfaceIP() -> String? {
return self.interfaceIP
}
Expand Down Expand Up @@ -87,12 +100,25 @@ class PacketTunnelProviderSettingsManager {
}
tunnelNetworkSettings.ipv4Settings = ipv4Settings

let ipv6Settings = NEIPv6Settings(addresses: [], networkPrefixLengths: [])

if self.ipv6Routes != nil {
ipv6Settings.includedRoutes = self.ipv6Routes
var v6Addresses: [String] = []
var v6PrefixLengths: [NSNumber] = []
var v6Routes: [NEIPv6Route] = []

if let ipv6CIDR = self.interfaceIPv6,
let (v6Addr, v6Prefix) = extractIPv6AddressAndPrefix(from: ipv6CIDR) {
v6Addresses.append(v6Addr)
v6PrefixLengths.append(NSNumber(value: v6Prefix))
v6Routes = self.ipv6Routes ?? []
} else if self.containsDefaultRoute {
v6Addresses.append(Self.ipv6BlackholeAddress)
v6PrefixLengths.append(Self.ipv6BlackholePrefix)
v6Routes = [NEIPv6Route(destinationAddress: "::", networkPrefixLength: 0)]
}

let ipv6Settings = NEIPv6Settings(addresses: v6Addresses, networkPrefixLengths: v6PrefixLengths)
if !v6Routes.isEmpty {
ipv6Settings.includedRoutes = v6Routes
}

tunnelNetworkSettings.ipv6Settings = ipv6Settings

tunnelNetworkSettings.mtu = 1280
Expand All @@ -104,8 +130,17 @@ class PacketTunnelProviderSettingsManager {
return tunnelNetworkSettings
}
}

return nil
}


private func extractIPv6AddressAndPrefix(from cidr: String) -> (String, Int)? {
let parts = cidr.split(separator: "/")
guard parts.count == 2,
let prefix = Int(parts[1]) else {
return nil
}
return (String(parts[0]), prefix)
}

}
2 changes: 1 addition & 1 deletion netbird-core
Submodule netbird-core updated 294 files
Loading