From 4295e63c98cdf37a54482e8f462b5f9af188fb2a Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 1 May 2019 11:29:10 +0200 Subject: [PATCH 1/5] Read relevant routing table --- TunnelKit.xcodeproj/project.pbxproj | 30 ++ TunnelKit/Sources/Core/RoutingTable.h | 43 ++ TunnelKit/Sources/Core/RoutingTable.m | 162 +++++++ TunnelKit/Sources/Core/RoutingTableEntry.h | 46 ++ TunnelKit/Sources/Core/RoutingTableEntry.m | 537 +++++++++++++++++++++ TunnelKit/Sources/Core/module.modulemap | 1 + TunnelKitTests/RoutingTests.swift | 96 ++++ 7 files changed, 915 insertions(+) create mode 100644 TunnelKit/Sources/Core/RoutingTable.h create mode 100644 TunnelKit/Sources/Core/RoutingTable.m create mode 100644 TunnelKit/Sources/Core/RoutingTableEntry.h create mode 100644 TunnelKit/Sources/Core/RoutingTableEntry.m create mode 100644 TunnelKitTests/RoutingTests.swift diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 53a878b..fb0e2ae 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -32,6 +32,12 @@ 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */; }; 0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; 0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */; }; + 0E0B203C227886AD007A3CB9 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */; }; + 0E0B203D227886AD007A3CB9 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */; }; + 0E0B20402278A85C007A3CB9 /* RoutingTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */; }; + 0E0B20412278A85C007A3CB9 /* RoutingTableEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */; }; + 0E0B20422278A85C007A3CB9 /* RoutingTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */; }; + 0E0B20432278A85C007A3CB9 /* RoutingTableEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */; }; 0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2123212ED29D008AB282 /* SessionError.swift */; }; 0E0C2126212ED29D008AB282 /* SessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2123212ED29D008AB282 /* SessionError.swift */; }; 0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */; }; @@ -177,6 +183,10 @@ 0EF5CF262141E142004FF1BD /* PacketMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; 0EF5CF272141E15B004FF1BD /* PacketMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; 0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */; }; + 0EFB902922788511006405E4 /* RoutingTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFB902722788511006405E4 /* RoutingTable.h */; }; + 0EFB902A22788511006405E4 /* RoutingTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFB902722788511006405E4 /* RoutingTable.h */; }; + 0EFB902B22788512006405E4 /* RoutingTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB902822788511006405E4 /* RoutingTable.m */; }; + 0EFB902C22788512006405E4 /* RoutingTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB902822788511006405E4 /* RoutingTable.m */; }; 0EFEB4552006D3C800F81029 /* SessionProxy+EncryptionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */; }; 0EFEB4562006D3C800F81029 /* SessionProxy+SessionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */; }; 0EFEB4582006D3C800F81029 /* MSS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFEB42D2006D3C800F81029 /* MSS.h */; }; @@ -290,6 +300,9 @@ 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCBC.h; sourceTree = ""; }; 0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAEAD.h; sourceTree = ""; }; 0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoAEAD.m; sourceTree = ""; }; + 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingTests.swift; sourceTree = ""; }; + 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingTableEntry.h; sourceTree = ""; }; + 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoutingTableEntry.m; sourceTree = ""; }; 0E0C2123212ED29D008AB282 /* SessionError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionError.swift; sourceTree = ""; }; 0E0C2124212ED29D008AB282 /* SessionProxy+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+Configuration.swift"; sourceTree = ""; }; 0E11089A1F77B9E800A92462 /* TunnelKitTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TunnelKitTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -372,6 +385,8 @@ 0EE7A79D20F6488400B42E6A /* DataPathCrypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataPathCrypto.h; sourceTree = ""; }; 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPathEncryptionTests.swift; sourceTree = ""; }; 0EEC49DB20B5E732008FEB91 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + 0EFB902722788511006405E4 /* RoutingTable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingTable.h; sourceTree = ""; }; + 0EFB902822788511006405E4 /* RoutingTable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoutingTable.m; sourceTree = ""; }; 0EFEB42A2006D3C800F81029 /* SessionProxy+EncryptionBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+EncryptionBridge.swift"; sourceTree = ""; }; 0EFEB42B2006D3C800F81029 /* SessionProxy+SessionKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionProxy+SessionKey.swift"; sourceTree = ""; }; 0EFEB42D2006D3C800F81029 /* MSS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSS.h; sourceTree = ""; }; @@ -489,6 +504,7 @@ 0E245D682135972800B012A2 /* PushTests.swift */, 0EB2B45620F0BD16004233D7 /* RandomTests.swift */, 0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */, + 0E0B203B227886AD007A3CB9 /* RoutingTests.swift */, 0E041D0B2152E80A0025FE3C /* StaticKeyTests.swift */, 0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */, 0E749F612178911C00BB2701 /* pia-2048.pem */, @@ -678,6 +694,10 @@ 0EFEB4382006D3C800F81029 /* ProtocolMacros.swift */, 0EFEB4392006D3C800F81029 /* ReplayProtector.h */, 0EFEB4482006D3C800F81029 /* ReplayProtector.m */, + 0EFB902722788511006405E4 /* RoutingTable.h */, + 0EFB902822788511006405E4 /* RoutingTable.m */, + 0E0B203E2278A85B007A3CB9 /* RoutingTableEntry.h */, + 0E0B203F2278A85B007A3CB9 /* RoutingTableEntry.m */, 0EFEB4372006D3C800F81029 /* SecureRandom.swift */, 0E0C2123212ED29D008AB282 /* SessionError.swift */, 0EFEB43C2006D3C800F81029 /* SessionProxy.swift */, @@ -765,12 +785,14 @@ 0E07596E20EF79B400F38FD8 /* CryptoCBC.h in Headers */, 0E58BF3522405410006FB157 /* lzodefs.h in Headers */, 0E58BF502240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */, + 0E0B20402278A85C007A3CB9 /* RoutingTableEntry.h in Headers */, 0E07596320EF733F00F38FD8 /* CryptoMacros.h in Headers */, 0EFEB46E2006D3C800F81029 /* TLSBox.h in Headers */, 0E411B9B2271F90700E0852C /* DNS.h in Headers */, 0E07596B20EF79AB00F38FD8 /* Crypto.h in Headers */, 0EFEB46B2006D3C800F81029 /* CryptoBox.h in Headers */, 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */, + 0EFB902922788511006405E4 /* RoutingTable.h in Headers */, 0EFEB4582006D3C800F81029 /* MSS.h in Headers */, 0E58BF3322405410006FB157 /* lzoconf.h in Headers */, 0E245D6C2137F73600B012A2 /* CompressionFramingNative.h in Headers */, @@ -795,12 +817,14 @@ 0E07596F20EF79B400F38FD8 /* CryptoCBC.h in Headers */, 0E58BF3622405410006FB157 /* lzodefs.h in Headers */, 0E58BF512240F98F006FB157 /* CompressionAlgorithmNative.h in Headers */, + 0E0B20412278A85C007A3CB9 /* RoutingTableEntry.h in Headers */, 0E07596420EF733F00F38FD8 /* CryptoMacros.h in Headers */, 0EEC49EA20B5F7F6008FEB91 /* ZeroingData.h in Headers */, 0E411B9C2271F90700E0852C /* DNS.h in Headers */, 0E07596C20EF79AB00F38FD8 /* Crypto.h in Headers */, 0EEC49E120B5F7EA008FEB91 /* Allocation.h in Headers */, 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */, + 0EFB902A22788511006405E4 /* RoutingTable.h in Headers */, 0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */, 0E58BF3422405410006FB157 /* lzoconf.h in Headers */, 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */, @@ -1185,6 +1209,7 @@ 0E041D0C2152E80A0025FE3C /* StaticKeyTests.swift in Sources */, 0E245D692135972800B012A2 /* PushTests.swift in Sources */, 0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */, + 0E0B203C227886AD007A3CB9 /* RoutingTests.swift in Sources */, 0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */, 0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */, 0EB2B45D20F0BF41004233D7 /* RawPerformanceTests.swift in Sources */, @@ -1215,6 +1240,7 @@ 0EEC49DC20B5E732008FEB91 /* Utils.swift in Sources */, 0EFEB4562006D3C800F81029 /* SessionProxy+SessionKey.swift in Sources */, 0EC1BBA520D71190007C4C7B /* DNSResolver.swift in Sources */, + 0E0B20422278A85C007A3CB9 /* RoutingTableEntry.m in Sources */, 0E58BF5922411FEF006FB157 /* LZO.m in Sources */, 0EFEB4AB200760EC00F81029 /* MemoryDestination.swift in Sources */, 0E12B29E21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, @@ -1233,6 +1259,7 @@ 0EFEB45C2006D3C800F81029 /* ZeroingData.m in Sources */, 0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */, 0EFEB4AC200760EC00F81029 /* InterfaceObserver.swift in Sources */, + 0EFB902B22788512006405E4 /* RoutingTable.m in Sources */, 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */, 0EFEB47B2006D3C800F81029 /* TunnelKitProvider.swift in Sources */, 0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */, @@ -1285,6 +1312,7 @@ 0EE7A79920F6296F00B42E6A /* PacketMacros.m in Sources */, 0EEC49DD20B5E732008FEB91 /* Utils.swift in Sources */, 0EFEB4B12007627700F81029 /* MemoryDestination.swift in Sources */, + 0E0B20432278A85C007A3CB9 /* RoutingTableEntry.m in Sources */, 0E58BF5A22411FEF006FB157 /* LZO.m in Sources */, 0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */, 0E12B29F21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift in Sources */, @@ -1303,6 +1331,7 @@ 0EFEB4982006D7F300F81029 /* ZeroingData.swift in Sources */, 0EFEB4A32006D7F300F81029 /* Errors.m in Sources */, 0EFEB4A22006D7F300F81029 /* CoreConfiguration.swift in Sources */, + 0EFB902C22788512006405E4 /* RoutingTable.m in Sources */, 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */, 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */, 0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */, @@ -1358,6 +1387,7 @@ 0EA82A302190B2B9007960EB /* ControlChannelTests.swift in Sources */, 0EA82A312190B2B9007960EB /* DataManipulationTests.swift in Sources */, 0EA82A382190B2B9007960EB /* PacketTests.swift in Sources */, + 0E0B203D227886AD007A3CB9 /* RoutingTests.swift in Sources */, 0EA82A3B2190B2B9007960EB /* RawPerformanceTests.swift in Sources */, 0EA82A362190B2B9007960EB /* EncryptionTests.swift in Sources */, 0EA82A3D2190B2B9007960EB /* TestUtils.swift in Sources */, diff --git a/TunnelKit/Sources/Core/RoutingTable.h b/TunnelKit/Sources/Core/RoutingTable.h new file mode 100644 index 0000000..2824905 --- /dev/null +++ b/TunnelKit/Sources/Core/RoutingTable.h @@ -0,0 +1,43 @@ +// +// RoutingTable.h +// TunnelKit +// +// Created by Davide De Rosa on 4/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +#import + +#import "RoutingTableEntry.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RoutingTable : NSObject + +- (NSArray *)ipv4; +- (NSArray *)ipv6; +- (nullable RoutingTableEntry *)defaultGateway4; +- (nullable RoutingTableEntry *)defaultGateway6; +- (nullable RoutingTableEntry *)broadestRoute4MatchingDestination:(NSString *)destination; +- (nullable RoutingTableEntry *)broadestRoute6MatchingDestination:(NSString *)destination; + +@end + +NS_ASSUME_NONNULL_END diff --git a/TunnelKit/Sources/Core/RoutingTable.m b/TunnelKit/Sources/Core/RoutingTable.m new file mode 100644 index 0000000..3bc241b --- /dev/null +++ b/TunnelKit/Sources/Core/RoutingTable.m @@ -0,0 +1,162 @@ +// +// RoutingTable.m +// TunnelKit +// +// Created by Davide De Rosa on 4/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +#import +#import + +#import "RoutingTable.h" +#import "Allocation.h" + +// adapted from: https://github.com/jianpx/ios-cabin + +@interface RoutingTableEntry () + +- (instancetype)initWithRTM:(const struct rt_msghdr2 *)rtm; + +@end + +#pragma mark - + +@interface RoutingTable () + +@property (nonatomic, strong) NSArray *ipv4; +@property (nonatomic, strong) NSArray *ipv6; + +@end + +@implementation RoutingTable + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + int mib[] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_DUMP2, 0 }; + const int mibLen = sizeof(mib) / sizeof(int); + size_t len; + if (sysctl(mib, mibLen, NULL, &len, NULL, 0) || (len <= 0)) { + return nil; + } + + char *buf = allocate_safely(len); + if (!buf) { + return nil; + } + if (sysctl(mib, mibLen, buf, &len, NULL, 0)) { + free(buf); + return nil; + } + + NSMutableArray *entries4 = [[NSMutableArray alloc] init]; + NSMutableArray *entries6 = [[NSMutableArray alloc] init]; + + for (const char *ptr = buf; ptr < buf + len;) { + const struct rt_msghdr2 *rtm = (struct rt_msghdr2 *)ptr; + + if (rtm->rtm_addrs & RTA_DST) { + struct sockaddr *dst_sa = (struct sockaddr *)(rtm + 1); // XXX: why +1 ?!? + + if (((dst_sa->sa_family == AF_INET) || (dst_sa->sa_family == AF_INET6)) && !((rtm->rtm_flags & RTF_WASCLONED) && (rtm->rtm_parentflags & RTF_PRCLONING))) { + RoutingTableEntry *entry = [[RoutingTableEntry alloc] initWithRTM:rtm]; + if (!entry) { + continue; + } + if (dst_sa->sa_family == AF_INET) { + [entries4 addObject:entry]; + } else if (dst_sa->sa_family == AF_INET6) { + [entries6 addObject:entry]; + } + } + } + + ptr += rtm->rtm_msglen; + } + + free(buf); + + self.ipv4 = entries4; + self.ipv6 = entries6; + + return self; +} + +- (RoutingTableEntry *)defaultGateway4 +{ + for (RoutingTableEntry *entry in self.ipv4) { + if ([entry isDefault]) { + return entry; + } + } + return nil; +} + +- (RoutingTableEntry *)defaultGateway6 +{ + for (RoutingTableEntry *entry in self.ipv6) { + if ([entry isDefault]) { + return entry; + } + } + return nil; +} + +- (RoutingTableEntry *)broadestRoute4MatchingDestination:(NSString *)destination +{ + RoutingTableEntry *defaultRoute; + RoutingTableEntry *minRoute; + NSInteger minPrefix = 32 + 1; + for (RoutingTableEntry *route in self.ipv4) { + if ([route isDefault]) { // leave last + defaultRoute = route; + continue; + } + if ([route matchesDestination:destination] && route.prefix < minPrefix) { + minRoute = route; + minPrefix = route.prefix; + } + } + return minRoute ?: defaultRoute; +} + +- (RoutingTableEntry *)broadestRoute6MatchingDestination:(NSString *)destination +{ + RoutingTableEntry *defaultRoute; + RoutingTableEntry *minRoute; + NSInteger minPrefix = 128 + 1; + for (RoutingTableEntry *route in self.ipv6) { + if ([route isDefault]) { // leave last + defaultRoute = route; + continue; + } + if ([route matchesDestination:destination] && route.prefix < minPrefix) { + minRoute = route; + minPrefix = route.prefix; + } + } + return minRoute ?: defaultRoute; +} + +@end diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.h b/TunnelKit/Sources/Core/RoutingTableEntry.h new file mode 100644 index 0000000..f0f2b46 --- /dev/null +++ b/TunnelKit/Sources/Core/RoutingTableEntry.h @@ -0,0 +1,46 @@ +// +// RoutingTableEntry.h +// TunnelKit +// +// Created by Davide De Rosa on 4/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RoutingTableEntry : NSObject + +- (instancetype)initWithIPv4Network:(NSString *)network gateway:(nullable NSString *)gateway networkInterface:(NSString *)networkInterface; +- (instancetype)initWithIPv6Network:(NSString *)network gateway:(nullable NSString *)gateway networkInterface:(NSString *)networkInterface; + +- (BOOL)isIPv6; +- (NSString *)network; +- (NSInteger)prefix; +- (nullable NSString *)gateway; +- (NSString *)networkInterface; + +- (BOOL)isDefault; +- (BOOL)matchesDestination:(NSString *)destination; + +@end + +NS_ASSUME_NONNULL_END diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.m b/TunnelKit/Sources/Core/RoutingTableEntry.m new file mode 100644 index 0000000..952e857 --- /dev/null +++ b/TunnelKit/Sources/Core/RoutingTableEntry.m @@ -0,0 +1,537 @@ +// +// RoutingTableEntry.m +// TunnelKit +// +// Created by Davide De Rosa on 4/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +#import +#import +#import +#import + +#import "RoutingTableEntry.h" + +// adapted from: https://github.com/jianpx/ios-cabin + +#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t)) + +typedef union { + uint32_t dummy; + struct sockaddr u_sa; + u_short u_data[128]; +} sa_u; + +static inline uint32_t RoutingTableEntryAddress4(NSString *string); +static inline NSData *RoutingTableEntryAddress6(NSString *string); +static NSString *RoutingTableEntryName(struct sockaddr *sa, struct sockaddr *mask, int flags); + +#pragma mark - + +@interface RoutingTableEntry () + +@property (nonatomic, assign) BOOL isIPv6; +@property (nonatomic, copy) NSString *network; +@property (nonatomic, assign) NSInteger prefix; +@property (nonatomic, copy) NSString *gateway; +@property (nonatomic, copy) NSString *networkInterface; + +@end + +@implementation RoutingTableEntry + +- (instancetype)initWithNetwork:(NSString *)network prefix:(NSInteger)prefix gateway:(NSString *)gateway networkInterface:(NSString *)networkInterface +{ + if (!(self = [super init])) { + return nil; + } + + self.network = network; + self.prefix = prefix; + self.gateway = gateway; + self.networkInterface = networkInterface; + + return self; +} + +- (instancetype)initWithIPv4Network:(NSString *)network gateway:(NSString *)gateway networkInterface:(NSString *)networkInterface +{ + NSInteger prefix = 0; + + NSArray *networkComps = [network componentsSeparatedByString:@"/"]; + network = networkComps.firstObject; + if (networkComps.count == 2) { + prefix = [networkComps.lastObject integerValue]; + } else { + prefix = 32; + } + + NSMutableArray *groups = [[network componentsSeparatedByString:@"."] mutableCopy]; + if (![network isEqualToString:@"default"]) { + if (prefix == 32) { + prefix = 8 * groups.count; + } + for (NSInteger i = groups.count; i < 4; ++i) { + [groups addObject:@"0"]; + } + network = [groups componentsJoinedByString:@"."]; + } + + if (!(self = [self initWithNetwork:network prefix:prefix gateway:gateway networkInterface:networkInterface])) { + return nil; + } + self.isIPv6 = NO; + return self; +} + +- (instancetype)initWithIPv6Network:(NSString *)network gateway:(NSString *)gateway networkInterface:(NSString *)networkInterface +{ + NSInteger prefix = 0; + + NSArray *networkComps = [network componentsSeparatedByString:@"/"]; + network = networkComps.firstObject; + if (networkComps.count == 2) { + prefix = [networkComps.lastObject integerValue]; + } else { + prefix = 128; + } + network = [[network componentsSeparatedByString:@"%"] firstObject]; + gateway = [[gateway componentsSeparatedByString:@"%"] firstObject]; + + if (!(self = [self initWithNetwork:network prefix:prefix gateway:gateway networkInterface:networkInterface])) { + return nil; + } + self.isIPv6 = YES; + return self; +} + +- (instancetype)initWithRTM:(const struct rt_msghdr2 *)rtm +{ + NSParameterAssert(rtm); + + NSString *network; + NSString *gateway; + NSString *networkInterface; + + struct sockaddr *rti_info[RTAX_MAX]; + struct sockaddr *sa = (struct sockaddr *)(rtm + 1); + for (int i = 0; i < RTAX_MAX; ++i) { + if (rtm->rtm_addrs & (1 << i)) { + rti_info[i] = sa; + sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa); + } else { + rti_info[i] = NULL; + } + } + + // network + sa_u destinationStruct, destinationNetmask; + bzero(&destinationStruct, sizeof(destinationStruct)); + if (rtm->rtm_addrs & RTA_DST) { + bcopy(rti_info[RTAX_DST], &destinationStruct, rti_info[RTAX_DST]->sa_len); + } + bzero(&destinationNetmask, sizeof(destinationNetmask)); + if (rtm->rtm_addrs & RTA_NETMASK) { + bcopy(rti_info[RTAX_NETMASK], &destinationNetmask, rti_info[RTAX_NETMASK]->sa_len); + } + network = RoutingTableEntryName(&destinationStruct.u_sa, &destinationNetmask.u_sa, rtm->rtm_flags); + + // gateway + sa_u gatewayStruct; + bzero(&gatewayStruct, sizeof(gatewayStruct)); + if (rtm->rtm_addrs & RTA_GATEWAY) { + bcopy(rti_info[RTAX_GATEWAY], &gatewayStruct, rti_info[RTAX_GATEWAY]->sa_len); + } + gateway = RoutingTableEntryName(rti_info[RTAX_GATEWAY], NULL, RTF_HOST); + + // network interface + char networkInterfaceStr[IF_NAMESIZE]; + const char *networkInterfaceName = if_indextoname(rtm->rtm_index, networkInterfaceStr); + if (networkInterfaceName) { + networkInterface = [NSString stringWithCString:networkInterfaceName encoding:NSASCIIStringEncoding]; + } + + if (rti_info[RTAX_DST]->sa_family == AF_INET6) { + return [self initWithIPv6Network:network gateway:gateway networkInterface:networkInterface]; + } else { + return [self initWithIPv4Network:network gateway:gateway networkInterface:networkInterface]; + } +} + +- (BOOL)isDefault +{ + return [self.network isEqualToString:@"default"]; +} + +- (BOOL)matchesDestination:(NSString *)destination +{ + NSParameterAssert(destination); + + if ([self isDefault]) { + return YES; + } + + if (self.isIPv6) { + NSData *networkAddress = RoutingTableEntryAddress6(self.network); + NSData *destinationAddress = RoutingTableEntryAddress6(destination); + +// NSLog(@"network: %@ = %@", networkAddress, self.network); +// NSLog(@"destination: %@ = %@", destinationAddress, destination); + + const uint8_t *networkPtr = networkAddress.bytes; + const uint8_t *destinationPtr = destinationAddress.bytes; + + NSInteger leftBits = self.prefix; +// NSLog(@"\tprefix = %u", (int)self.prefix); + for (NSInteger i = 0; leftBits > 0; ++i) { + uint8_t networkMask; + if (leftBits >= 8) { + networkMask = 0xff; + } else { + networkMask = ~((1 << (8 - leftBits)) - 1); + } +// NSLog(@"\tnetworkMask[%u] = %x", (int)i, networkMask); + if (((networkPtr[i] ^ destinationPtr[i]) & networkMask) != 0) { + return NO; + } + leftBits -= 8; + } +// NSLog(@"\tMATCH"); + return YES; + } + else { + const uint32_t networkAddress = RoutingTableEntryAddress4(self.network); + const uint32_t destinationAddress = RoutingTableEntryAddress4(destination); + const uint32_t networkMask = ~((1 << (32 - self.prefix)) - 1); + +// NSLog(@"network: %x = %@", networkAddress, self.network); +// NSLog(@"destination: %x = %@", destinationAddress, destination); +// NSLog(@"mask: %x", networkMask); + + return ((networkAddress ^ destinationAddress) & networkMask) == 0; + } +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"{%@/%ld -> %@ via %@}", self.network, self.prefix, self.gateway ?: @"nil", self.networkInterface]; +} + +@end + +#pragma mark - + +static char *netname(uint32_t in, uint32_t mask); +static char *netname6(struct sockaddr_in6 *sa6, struct sockaddr *sam); +static char *routename(uint32_t in); +static char *routename6(struct sockaddr_in6 *sa6); +static uint32_t forgemask(uint32_t a); +static void domask(char *dst, uint32_t addr, uint32_t mask); +static void trimdomain(char *cp); + +static inline uint32_t RoutingTableEntryAddress4(NSString *string) +{ + struct in_addr addr; + if (inet_pton(AF_INET, [string cStringUsingEncoding:NSASCIIStringEncoding], &addr) <= 0) { + return UINT32_MAX; + } + return CFSwapInt32BigToHost(addr.s_addr); +} + +static inline NSData *RoutingTableEntryAddress6(NSString *string) +{ + struct in6_addr addr; + if (inet_pton(AF_INET6, [string cStringUsingEncoding:NSASCIIStringEncoding], &addr) <= 0) { + return nil; + } + NSMutableData *data = [[NSMutableData alloc] initWithLength:16]; + memcpy(data.mutableBytes, (void *)&addr, data.length); + return data; +} + +static NSString *RoutingTableEntryName(struct sockaddr *sa, struct sockaddr *mask, int flags) +{ + char *cp = NULL; + switch (sa->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + if ((sin->sin_addr.s_addr == INADDR_ANY) && mask && (ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr) == 0L || mask->sa_len == 0)) { + cp = "default"; + } else if (flags & RTF_HOST) { + cp = routename(sin->sin_addr.s_addr); + } else if (mask) { + cp = netname(sin->sin_addr.s_addr, ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr)); + } else { + cp = netname(sin->sin_addr.s_addr, 0L); + } + break; + } + case AF_INET6: { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa; + struct in6_addr *in6 = &sa6->sin6_addr; + + /* + * XXX: This is a special workaround for KAME kernels. + * sin6_scope_id field of SA should be set in the future. + */ + if (IN6_IS_ADDR_LINKLOCAL(in6) || + IN6_IS_ADDR_MC_NODELOCAL(in6) || + IN6_IS_ADDR_MC_LINKLOCAL(in6)) { + + /* XXX: override is ok? */ + sa6->sin6_scope_id = (u_int32_t)ntohs(*(u_short *)&in6->s6_addr[2]); + *(u_short *)&in6->s6_addr[2] = 0; + } + + if (flags & RTF_HOST) { + cp = routename6(sa6); + } else if (mask) { + cp = netname6(sa6, mask); + } else { + cp = netname6(sa6, NULL); + } + break; + } + default: + break; + } + if (!cp) { + return nil; + } + return [NSString stringWithCString:cp encoding:NSASCIIStringEncoding]; +} + +char *routename(uint32_t in) +{ + static char line[MAXHOSTNAMELEN]; + +#define C(x) ((x) & 0xff) + in = ntohl(in); + snprintf(line, sizeof(line), "%u.%u.%u.%u", C(in >> 24), C(in >> 16), C(in >> 8), C(in)); + + return (line); +} + +char *routename6(struct sockaddr_in6 *sa6) +{ + static char line[MAXHOSTNAMELEN]; + int flag = NI_NUMERICHOST; + /* use local variable for safety */ + struct sockaddr_in6 sa6_local = {sizeof(sa6_local), AF_INET6, }; + + sa6_local.sin6_addr = sa6->sin6_addr; + sa6_local.sin6_scope_id = sa6->sin6_scope_id; + + getnameinfo((struct sockaddr *)&sa6_local, sa6_local.sin6_len, line, sizeof(line), NULL, 0, flag); + + return line; +} +/* + * Return the name of the network whose address is given. + * The address is assumed to be that of a net or subnet, not a host. + */ +char *netname(uint32_t in, uint32_t mask) +{ + char *cp = 0; + static char line[MAXHOSTNAMELEN]; + struct netent *np = 0; + uint32_t net, omask, dmask; + uint32_t i; + + i = ntohl(in); + dmask = forgemask(i); + omask = mask; + // if (!nflag && i) { + if (i) { + net = i & dmask; + if (!(np = getnetbyaddr(i, AF_INET)) && net != i) + np = getnetbyaddr(net, AF_INET); + if (np) { + cp = np->n_name; + trimdomain(cp); + } + } + if (cp) { + strncpy(line, cp, sizeof(line) - 1); + } else { + switch (dmask) { + case IN_CLASSA_NET: + if ((i & IN_CLASSA_HOST) == 0) { + snprintf(line, sizeof(line), "%u", C(i >> 24)); + break; + } + /* FALLTHROUGH */ + case IN_CLASSB_NET: + if ((i & IN_CLASSB_HOST) == 0) { + snprintf(line, sizeof(line), "%u.%u", + C(i >> 24), C(i >> 16)); + break; + } + /* FALLTHROUGH */ + case IN_CLASSC_NET: + if ((i & IN_CLASSC_HOST) == 0) { + snprintf(line, sizeof(line), "%u.%u.%u", + C(i >> 24), C(i >> 16), C(i >> 8)); + break; + } + /* FALLTHROUGH */ + default: + snprintf(line, sizeof(line), "%u.%u.%u.%u", + C(i >> 24), C(i >> 16), C(i >> 8), C(i)); + break; + } + } + domask(line+strlen(line), i, omask); + return (line); +} + + +char *netname6(struct sockaddr_in6 *sa6, struct sockaddr *sam) +{ + static char line[MAXHOSTNAMELEN + 10]; + u_char *lim; + int masklen, illegal = 0, flag = NI_NUMERICHOST; + struct in6_addr *mask = sam ? &((struct sockaddr_in6 *)sam)->sin6_addr : 0; + + if (sam && sam->sa_len == 0) { + masklen = 0; + } else if (mask) { + u_char *p = (u_char *)mask; + for (masklen = 0, lim = p + 16; p < lim; p++) { + switch (*p) { + case 0xff: + masklen += 8; + break; + case 0xfe: + masklen += 7; + break; + case 0xfc: + masklen += 6; + break; + case 0xf8: + masklen += 5; + break; + case 0xf0: + masklen += 4; + break; + case 0xe0: + masklen += 3; + break; + case 0xc0: + masklen += 2; + break; + case 0x80: + masklen += 1; + break; + case 0x00: + break; + default: + illegal ++; + break; + } + } + if (illegal) + fprintf(stderr, "illegal prefixlen\n"); + } else { + masklen = 128; + } + if (masklen == 0 && IN6_IS_ADDR_UNSPECIFIED(&sa6->sin6_addr)) { + return("default"); + } + + getnameinfo((struct sockaddr *)sa6, sa6->sin6_len, line, sizeof(line), NULL, 0, flag); + + if (masklen > 0) { + sprintf(line, "%s/%u", line, masklen); + } + + return line; +} + +uint32_t forgemask(uint32_t a) +{ + uint32_t m; + + if (IN_CLASSA(a)) + m = IN_CLASSA_NET; + else if (IN_CLASSB(a)) + m = IN_CLASSB_NET; + else + m = IN_CLASSC_NET; + return (m); +} + +void domask(char *dst, uint32_t addr, uint32_t mask) +{ + int b, i; + + if (!mask || (forgemask(addr) == mask)) { + *dst = '\0'; + return; + } + i = 0; + for (b = 0; b < 32; b++) { + if (mask & (1 << b)) { + int bb; + + i = b; + for (bb = b+1; bb < 32; bb++) + if (!(mask & (1 << bb))) { + i = -1; /* noncontig */ + break; + } + break; + } + } + if (i == -1) { + snprintf(dst, sizeof(dst), "&0x%x", mask); + } else { + snprintf(dst, sizeof(dst), "/%d", 32-i); + } +} + +void trimdomain(char *cp) +{ + static char domain[MAXHOSTNAMELEN + 1]; + static int first = 1; + char *s; + + if (first) { + first = 0; + if (gethostname(domain, MAXHOSTNAMELEN) == 0 && + (s = strchr(domain, '.'))) + (void) strcpy(domain, s + 1); + else + domain[0] = 0; + } + + if (domain[0]) { + while ((cp = strchr(cp, '.'))) { + if (!strcasecmp(cp + 1, domain)) { + *cp = 0; /* hit it */ + break; + } else { + cp++; + } + } + } +} diff --git a/TunnelKit/Sources/Core/module.modulemap b/TunnelKit/Sources/Core/module.modulemap index 416d448..c4b6b66 100644 --- a/TunnelKit/Sources/Core/module.modulemap +++ b/TunnelKit/Sources/Core/module.modulemap @@ -51,5 +51,6 @@ module __TunnelKitNative { header "DataPathCrypto.h" header "DNS.h" header "LZO.h" + header "RoutingTable.h" export * } diff --git a/TunnelKitTests/RoutingTests.swift b/TunnelKitTests/RoutingTests.swift new file mode 100644 index 0000000..be48038 --- /dev/null +++ b/TunnelKitTests/RoutingTests.swift @@ -0,0 +1,96 @@ +// +// RoutingTests.swift +// TunnelKitTests +// +// Created by Davide De Rosa on 4/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import XCTest +import __TunnelKitNative + +class RoutingTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testEntryMatch4() { + let entry24 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0") + for i in 0x0...0xff { + XCTAssertTrue(entry24.matchesDestination("192.168.1.\(i)")) + } + for i in 0x0...0xff { + XCTAssertFalse(entry24.matchesDestination("192.168.2.\(i)")) + } + + let entry28 = RoutingTableEntry(iPv4Network: "192.168.1.0/28", gateway: nil, networkInterface: "en0") + for i in 0x0...0xf { + XCTAssertTrue(entry28.matchesDestination("192.168.1.\(i)")) + } + for i in 0x10...0x1f { + XCTAssertFalse(entry28.matchesDestination("192.168.1.\(i)")) + } + } + + func testEntryMatch6() { + let entry24 = RoutingTableEntry(iPv6Network: "abcd:efef:1234::/46", gateway: nil, networkInterface: "en0") + for i in 0x0...0xf { + XCTAssertTrue(entry24.matchesDestination("abcd:efef:1234::\(i)")) + } + for i in 0x0...0xf { + XCTAssertFalse(entry24.matchesDestination("abcd:efef:1233::\(i)")) + } + } + + func testFindGatewayLAN4() { + let table = RoutingTable() + + for entry in table.ipv4() { + print(entry) + } + + if let defaultGateway = table.defaultGateway4()?.gateway() { + print("Default gateway: \(defaultGateway)") + if let lan = table.broadestRoute4(matchingDestination: defaultGateway) { + print("Gateway LAN: \(lan.network())/\(lan.prefix())") + } + } + } + + func testFindGatewayLAN6() { + let table = RoutingTable() + + for entry in table.ipv6() { + print(entry) + } + + if let defaultGateway = table.defaultGateway6()?.gateway() { + print("Default gateway: \(defaultGateway)") + if let lan = table.broadestRoute6(matchingDestination: defaultGateway) { + print("Gateway LAN: \(lan.network())/\(lan.prefix())") + } + } + } +} From 03a1eb22034233c0237c2dc2b8248209bdd3d521 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 1 May 2019 16:05:50 +0200 Subject: [PATCH 2/5] Return IPv4 network mask for a route --- TunnelKit/Sources/Core/RoutingTableEntry.h | 1 + TunnelKit/Sources/Core/RoutingTableEntry.m | 8 ++++++++ TunnelKitTests/RoutingTests.swift | 2 ++ 3 files changed, 11 insertions(+) diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.h b/TunnelKit/Sources/Core/RoutingTableEntry.h index f0f2b46..da44425 100644 --- a/TunnelKit/Sources/Core/RoutingTableEntry.h +++ b/TunnelKit/Sources/Core/RoutingTableEntry.h @@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isIPv6; - (NSString *)network; - (NSInteger)prefix; +- (nullable NSString *)networkMask; // nil if IPv6 - (nullable NSString *)gateway; - (NSString *)networkInterface; diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.m b/TunnelKit/Sources/Core/RoutingTableEntry.m index 952e857..5807a3a 100644 --- a/TunnelKit/Sources/Core/RoutingTableEntry.m +++ b/TunnelKit/Sources/Core/RoutingTableEntry.m @@ -176,6 +176,14 @@ static NSString *RoutingTableEntryName(struct sockaddr *sa, struct sockaddr *mas } } +- (NSString *)networkMask +{ + struct in_addr mask; + mask.s_addr = htonl(~((1 << (32 - self.prefix)) - 1)); + const char *address = inet_ntoa(mask); + return [NSString stringWithCString:address encoding:NSASCIIStringEncoding]; +} + - (BOOL)isDefault { return [self.network isEqualToString:@"default"]; diff --git a/TunnelKitTests/RoutingTests.swift b/TunnelKitTests/RoutingTests.swift index be48038..3fbaa3c 100644 --- a/TunnelKitTests/RoutingTests.swift +++ b/TunnelKitTests/RoutingTests.swift @@ -38,6 +38,7 @@ class RoutingTests: XCTestCase { func testEntryMatch4() { let entry24 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0") + print(entry24.networkMask()!) for i in 0x0...0xff { XCTAssertTrue(entry24.matchesDestination("192.168.1.\(i)")) } @@ -46,6 +47,7 @@ class RoutingTests: XCTestCase { } let entry28 = RoutingTableEntry(iPv4Network: "192.168.1.0/28", gateway: nil, networkInterface: "en0") + print(entry28.networkMask()!) for i in 0x0...0xf { XCTAssertTrue(entry28.matchesDestination("192.168.1.\(i)")) } From 13cae06a4947c71e82c42e1a193ae82b46ad1669 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 1 May 2019 17:07:38 +0200 Subject: [PATCH 3/5] Add method to partition a subnet --- TunnelKit/Sources/Core/RoutingTableEntry.h | 1 + TunnelKit/Sources/Core/RoutingTableEntry.m | 46 +++++++++++++++++++++- TunnelKitTests/RoutingTests.swift | 21 ++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.h b/TunnelKit/Sources/Core/RoutingTableEntry.h index da44425..a621a07 100644 --- a/TunnelKit/Sources/Core/RoutingTableEntry.h +++ b/TunnelKit/Sources/Core/RoutingTableEntry.h @@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isDefault; - (BOOL)matchesDestination:(NSString *)destination; +- (NSArray *)partitioned; @end diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.m b/TunnelKit/Sources/Core/RoutingTableEntry.m index 5807a3a..99fd1b3 100644 --- a/TunnelKit/Sources/Core/RoutingTableEntry.m +++ b/TunnelKit/Sources/Core/RoutingTableEntry.m @@ -40,8 +40,8 @@ typedef union { u_short u_data[128]; } sa_u; -static inline uint32_t RoutingTableEntryAddress4(NSString *string); -static inline NSData *RoutingTableEntryAddress6(NSString *string); +static uint32_t RoutingTableEntryAddress4(NSString *string); +static NSData *RoutingTableEntryAddress6(NSString *string); static NSString *RoutingTableEntryName(struct sockaddr *sa, struct sockaddr *mask, int flags); #pragma mark - @@ -238,6 +238,48 @@ static NSString *RoutingTableEntryName(struct sockaddr *sa, struct sockaddr *mas } } +- (NSArray *)partitioned +{ + NSMutableArray *segments = [[NSMutableArray alloc] init]; + const int halfPrefix = (int)(self.prefix + 1); + if (self.isIPv6) { + struct in6_addr saddr1, saddr2; + char addr[INET6_ADDRSTRLEN]; + NSData *addressData = RoutingTableEntryAddress6(self.network); + memcpy(&saddr1, addressData.bytes, addressData.length); + NSMutableData *addressData2 = [addressData mutableCopy]; + + uint8_t *addressBytes2 = (uint8_t *)addressData2.bytes; + const uint8_t mask2 = 1 << (8 - halfPrefix % 8); + addressBytes2[halfPrefix / 8] |= mask2; + + memcpy(&saddr2, addressData2.bytes, addressData2.length); + + inet_ntop(AF_INET6, &saddr1, addr, INET6_ADDRSTRLEN); + NSString *network1 = [NSString stringWithFormat:@"%s/%d", addr, halfPrefix]; + inet_ntop(AF_INET6, &saddr2, addr, INET6_ADDRSTRLEN); + NSString *network2 = [NSString stringWithFormat:@"%s/%d", addr, halfPrefix]; + + [segments addObject:[[RoutingTableEntry alloc] initWithIPv6Network:network1 gateway:self.gateway networkInterface:self.networkInterface]]; + [segments addObject:[[RoutingTableEntry alloc] initWithIPv6Network:network2 gateway:self.gateway networkInterface:self.networkInterface]]; + } else { + struct in_addr saddr1, saddr2; + const uint32_t address = RoutingTableEntryAddress4(self.network); + saddr1.s_addr = htonl(address); + saddr2.s_addr = htonl(address | (1 << (32 - halfPrefix))); + + // XXX: inet_ntoa returns pointer to static variable, copy before next call + const char *address1 = inet_ntoa(saddr1); + NSString *network1 = [NSString stringWithFormat:@"%s/%d", address1, halfPrefix]; + const char *address2 = inet_ntoa(saddr2); + NSString *network2 = [NSString stringWithFormat:@"%s/%d", address2, halfPrefix]; + + [segments addObject:[[RoutingTableEntry alloc] initWithIPv4Network:network1 gateway:self.gateway networkInterface:self.networkInterface]]; + [segments addObject:[[RoutingTableEntry alloc] initWithIPv4Network:network2 gateway:self.gateway networkInterface:self.networkInterface]]; + } + return segments; +} + - (NSString *)description { return [NSString stringWithFormat:@"{%@/%ld -> %@ via %@}", self.network, self.prefix, self.gateway ?: @"nil", self.networkInterface]; diff --git a/TunnelKitTests/RoutingTests.swift b/TunnelKitTests/RoutingTests.swift index 3fbaa3c..ed312b3 100644 --- a/TunnelKitTests/RoutingTests.swift +++ b/TunnelKitTests/RoutingTests.swift @@ -95,4 +95,25 @@ class RoutingTests: XCTestCase { } } } + + func testPartitioning() { + let v4 = RoutingTableEntry(iPv4Network: "192.168.1.0/24", gateway: nil, networkInterface: "en0") + let v6 = RoutingTableEntry(iPv6Network: "abcd:efef:120::/46", gateway: nil, networkInterface: "en0") + + let v4parts = v4.partitioned() + let v4parts1 = v4parts[0] + let v4parts2 = v4parts[1] + XCTAssertEqual(v4parts1.network(), "192.168.1.0") + XCTAssertEqual(v4parts1.prefix(), 25) + XCTAssertEqual(v4parts2.network(), "192.168.1.128") + XCTAssertEqual(v4parts2.prefix(), 25) + + let v6parts = v6.partitioned() + let v6parts1 = v6parts[0] + let v6parts2 = v6parts[1] + XCTAssertEqual(v6parts1.network(), "abcd:efef:120::") + XCTAssertEqual(v6parts1.prefix(), 47) + XCTAssertEqual(v6parts2.network(), "abcd:efef:122::") + XCTAssertEqual(v6parts2.prefix(), 47) + } } From a693075e9088fe381582daa54ecba97d94f10787 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 1 May 2019 17:15:41 +0200 Subject: [PATCH 4/5] Block LAN when redirect-gateway block-local Fixes #81 --- .../AppExtension/TunnelKitProvider.swift | 37 +++++++++++++++++++ .../Sources/Core/ConfigurationParser.swift | 3 ++ .../Core/SessionProxy+Configuration.swift | 3 ++ 3 files changed, 43 insertions(+) diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 183445b..be1b5d7 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -648,6 +648,43 @@ extension TunnelKitProvider: SessionProxyDelegate { // only set if there is a proxy (proxySettings set to non-nil above) proxySettings?.exceptionList = cfg.sessionConfiguration.proxyBypassDomains ?? reply.options.proxyBypassDomains + // block LAN if desired + if routingPolicies?.contains(.blockLocal) ?? false { + let table = RoutingTable() + if isIPv4Gateway, + let gateway = table.defaultGateway4()?.gateway(), + let route = table.broadestRoute4(matchingDestination: gateway) { + + route.partitioned().forEach { + let destination = $0.network() + guard let netmask = $0.networkMask() else { + return + } + + log.info("Block local: Suppressing IPv4 route \(destination)/\($0.prefix())") + + let included = NEIPv4Route(destinationAddress: destination, subnetMask: netmask) + included.gatewayAddress = reply.options.ipv4?.defaultGateway + ipv4Settings?.includedRoutes?.append(included) + } + } + if isIPv6Gateway, + let gateway = table.defaultGateway6()?.gateway(), + let route = table.broadestRoute6(matchingDestination: gateway) { + + route.partitioned().forEach { + let destination = $0.network() + let prefix = $0.prefix() + + log.info("Block local: Suppressing IPv6 route \(destination)/\($0.prefix())") + + let included = NEIPv6Route(destinationAddress: destination, networkPrefixLength: prefix as NSNumber) + included.gatewayAddress = reply.options.ipv6?.defaultGateway + ipv6Settings?.includedRoutes?.append(included) + } + } + } + let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) newSettings.ipv4Settings = ipv4Settings newSettings.ipv6Settings = ipv6Settings diff --git a/TunnelKit/Sources/Core/ConfigurationParser.swift b/TunnelKit/Sources/Core/ConfigurationParser.swift index 3766100..1ca745e 100644 --- a/TunnelKit/Sources/Core/ConfigurationParser.swift +++ b/TunnelKit/Sources/Core/ConfigurationParser.swift @@ -723,6 +723,9 @@ public class ConfigurationParser { case .ipv6: policies.insert(.IPv6) + + case .blockLocal: + policies.insert(.blockLocal) default: // TODO: handle [auto]local and block-* diff --git a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift index 63a70b3..fdade2a 100644 --- a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift @@ -154,6 +154,9 @@ extension SessionProxy { /// All IPv6 traffic goes through the VPN. case IPv6 + + /// Block LAN while connected. + case blockLocal } /// :nodoc: From 273007cc593e3a23990115a0c023428950b72554 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 1 May 2019 17:19:10 +0200 Subject: [PATCH 5/5] Copy route.h from macOS Missing on iOS. --- TunnelKit/Sources/Core/RoutingTable.m | 2 +- TunnelKit/Sources/Core/RoutingTableEntry.m | 2 +- TunnelKit/Sources/Core/route.h | 257 +++++++++++++++++++++ 3 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 TunnelKit/Sources/Core/route.h diff --git a/TunnelKit/Sources/Core/RoutingTable.m b/TunnelKit/Sources/Core/RoutingTable.m index 3bc241b..ab37bea 100644 --- a/TunnelKit/Sources/Core/RoutingTable.m +++ b/TunnelKit/Sources/Core/RoutingTable.m @@ -24,7 +24,7 @@ // #import -#import +#import "route.h" #import "RoutingTable.h" #import "Allocation.h" diff --git a/TunnelKit/Sources/Core/RoutingTableEntry.m b/TunnelKit/Sources/Core/RoutingTableEntry.m index 99fd1b3..c5b8f8d 100644 --- a/TunnelKit/Sources/Core/RoutingTableEntry.m +++ b/TunnelKit/Sources/Core/RoutingTableEntry.m @@ -26,7 +26,7 @@ #import #import #import -#import +#import "route.h" #import "RoutingTableEntry.h" diff --git a/TunnelKit/Sources/Core/route.h b/TunnelKit/Sources/Core/route.h new file mode 100644 index 0000000..49a4d46 --- /dev/null +++ b/TunnelKit/Sources/Core/route.h @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2000-2017 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1980, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)route.h 8.3 (Berkeley) 4/19/94 + * $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $ + */ + +#ifndef _NET_ROUTE_H_ +#define _NET_ROUTE_H_ +#include +#include +#include +#include + +/* + * These numbers are used by reliable protocols for determining + * retransmission behavior and are included in the routing structure. + */ +struct rt_metrics { + u_int32_t rmx_locks; /* Kernel leaves these values alone */ + u_int32_t rmx_mtu; /* MTU for this path */ + u_int32_t rmx_hopcount; /* max hops expected */ + int32_t rmx_expire; /* lifetime for route, e.g. redirect */ + u_int32_t rmx_recvpipe; /* inbound delay-bandwidth product */ + u_int32_t rmx_sendpipe; /* outbound delay-bandwidth product */ + u_int32_t rmx_ssthresh; /* outbound gateway buffer limit */ + u_int32_t rmx_rtt; /* estimated round trip time */ + u_int32_t rmx_rttvar; /* estimated rtt variance */ + u_int32_t rmx_pksent; /* packets sent using this route */ + u_int32_t rmx_state; /* route state */ + u_int32_t rmx_filler[3]; /* will be used for T/TCP later */ +}; + +/* + * rmx_rtt and rmx_rttvar are stored as microseconds; + */ +#define RTM_RTTUNIT 1000000 /* units for rtt, rttvar, as units per sec */ + + + +#define RTF_UP 0x1 /* route usable */ +#define RTF_GATEWAY 0x2 /* destination is a gateway */ +#define RTF_HOST 0x4 /* host entry (net otherwise) */ +#define RTF_REJECT 0x8 /* host or net unreachable */ +#define RTF_DYNAMIC 0x10 /* created dynamically (by redirect) */ +#define RTF_MODIFIED 0x20 /* modified dynamically (by redirect) */ +#define RTF_DONE 0x40 /* message confirmed */ +#define RTF_DELCLONE 0x80 /* delete cloned route */ +#define RTF_CLONING 0x100 /* generate new routes on use */ +#define RTF_XRESOLVE 0x200 /* external daemon resolves name */ +#define RTF_LLINFO 0x400 /* DEPRECATED - exists ONLY for backward + * compatibility */ +#define RTF_LLDATA 0x400 /* used by apps to add/del L2 entries */ +#define RTF_STATIC 0x800 /* manually added */ +#define RTF_BLACKHOLE 0x1000 /* just discard pkts (during updates) */ +#define RTF_NOIFREF 0x2000 /* not eligible for RTF_IFREF */ +#define RTF_PROTO2 0x4000 /* protocol specific routing flag */ +#define RTF_PROTO1 0x8000 /* protocol specific routing flag */ + +#define RTF_PRCLONING 0x10000 /* protocol requires cloning */ +#define RTF_WASCLONED 0x20000 /* route generated through cloning */ +#define RTF_PROTO3 0x40000 /* protocol specific routing flag */ + /* 0x80000 unused */ +#define RTF_PINNED 0x100000 /* future use */ +#define RTF_LOCAL 0x200000 /* route represents a local address */ +#define RTF_BROADCAST 0x400000 /* route represents a bcast address */ +#define RTF_MULTICAST 0x800000 /* route represents a mcast address */ +#define RTF_IFSCOPE 0x1000000 /* has valid interface scope */ +#define RTF_CONDEMNED 0x2000000 /* defunct; no longer modifiable */ +#define RTF_IFREF 0x4000000 /* route holds a ref to interface */ +#define RTF_PROXY 0x8000000 /* proxying, no interface scope */ +#define RTF_ROUTER 0x10000000 /* host is a router */ +#define RTF_DEAD 0x20000000 /* Route entry is being freed */ + /* 0x40000000 and up unassigned */ + +#define RTPRF_OURS RTF_PROTO3 /* set on routes we manage */ +#define RTF_BITS \ + "\020\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE" \ + "\10DELCLONE\11CLONING\12XRESOLVE\13LLINFO\14STATIC\15BLACKHOLE" \ + "\16NOIFREF\17PROTO2\20PROTO1\21PRCLONING\22WASCLONED\23PROTO3" \ + "\25PINNED\26LOCAL\27BROADCAST\30MULTICAST\31IFSCOPE\32CONDEMNED" \ + "\33IFREF\34PROXY\35ROUTER" + +#define IS_DIRECT_HOSTROUTE(rt) \ + (((rt)->rt_flags & (RTF_HOST | RTF_GATEWAY)) == RTF_HOST) +/* + * Routing statistics. + */ +struct rtstat { + short rts_badredirect; /* bogus redirect calls */ + short rts_dynamic; /* routes created by redirects */ + short rts_newgateway; /* routes modified by redirects */ + short rts_unreach; /* lookups which failed */ + short rts_wildcard; /* lookups satisfied by a wildcard */ + short rts_badrtgwroute; /* route to gateway is not direct */ +}; + +/* + * Structures for routing messages. + */ +struct rt_msghdr { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + pid_t rtm_pid; /* identify sender */ + int rtm_seq; /* for sender to identify action */ + int rtm_errno; /* why failed */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + +struct rt_msghdr2 { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + int32_t rtm_refcnt; /* reference count */ + int rtm_parentflags; /* flags of the parent route */ + int rtm_reserved; /* reserved field set to 0 */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + + +#define RTM_VERSION 5 /* Up the ante and ignore older versions */ + +/* + * Message types. + */ +#define RTM_ADD 0x1 /* Add Route */ +#define RTM_DELETE 0x2 /* Delete Route */ +#define RTM_CHANGE 0x3 /* Change Metrics or flags */ +#define RTM_GET 0x4 /* Report Metrics */ +#define RTM_LOSING 0x5 /* RTM_LOSING is no longer generated by xnu + * and is deprecated */ +#define RTM_REDIRECT 0x6 /* Told to use different route */ +#define RTM_MISS 0x7 /* Lookup failed on this address */ +#define RTM_LOCK 0x8 /* fix specified metrics */ +#define RTM_OLDADD 0x9 /* caused by SIOCADDRT */ +#define RTM_OLDDEL 0xa /* caused by SIOCDELRT */ +#define RTM_RESOLVE 0xb /* req to resolve dst to LL addr */ +#define RTM_NEWADDR 0xc /* address being added to iface */ +#define RTM_DELADDR 0xd /* address being removed from iface */ +#define RTM_IFINFO 0xe /* iface going up/down etc. */ +#define RTM_NEWMADDR 0xf /* mcast group membership being added to if */ +#define RTM_DELMADDR 0x10 /* mcast group membership being deleted */ +#define RTM_IFINFO2 0x12 /* */ +#define RTM_NEWMADDR2 0x13 /* */ +#define RTM_GET2 0x14 /* */ + +/* + * Bitmask values for rtm_inits and rmx_locks. + */ +#define RTV_MTU 0x1 /* init or lock _mtu */ +#define RTV_HOPCOUNT 0x2 /* init or lock _hopcount */ +#define RTV_EXPIRE 0x4 /* init or lock _expire */ +#define RTV_RPIPE 0x8 /* init or lock _recvpipe */ +#define RTV_SPIPE 0x10 /* init or lock _sendpipe */ +#define RTV_SSTHRESH 0x20 /* init or lock _ssthresh */ +#define RTV_RTT 0x40 /* init or lock _rtt */ +#define RTV_RTTVAR 0x80 /* init or lock _rttvar */ + +/* + * Bitmask values for rtm_addrs. + */ +#define RTA_DST 0x1 /* destination sockaddr present */ +#define RTA_GATEWAY 0x2 /* gateway sockaddr present */ +#define RTA_NETMASK 0x4 /* netmask sockaddr present */ +#define RTA_GENMASK 0x8 /* cloning mask sockaddr present */ +#define RTA_IFP 0x10 /* interface name sockaddr present */ +#define RTA_IFA 0x20 /* interface addr sockaddr present */ +#define RTA_AUTHOR 0x40 /* sockaddr for author of redirect */ +#define RTA_BRD 0x80 /* for NEWADDR, broadcast or p-p dest addr */ + +/* + * Index offsets for sockaddr array for alternate internal encoding. + */ +#define RTAX_DST 0 /* destination sockaddr present */ +#define RTAX_GATEWAY 1 /* gateway sockaddr present */ +#define RTAX_NETMASK 2 /* netmask sockaddr present */ +#define RTAX_GENMASK 3 /* cloning mask sockaddr present */ +#define RTAX_IFP 4 /* interface name sockaddr present */ +#define RTAX_IFA 5 /* interface addr sockaddr present */ +#define RTAX_AUTHOR 6 /* sockaddr for author of redirect */ +#define RTAX_BRD 7 /* for NEWADDR, broadcast or p-p dest addr */ +#define RTAX_MAX 8 /* size of array to allocate */ + +struct rt_addrinfo { + int rti_addrs; + struct sockaddr *rti_info[RTAX_MAX]; +}; + + +#endif /* _NET_ROUTE_H_ */