github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/subnet.go (about) 1 /* 2 * Copyright (c) 2016, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package common 21 22 import ( 23 "bufio" 24 "bytes" 25 "encoding/binary" 26 "net" 27 "sort" 28 "strings" 29 30 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 31 ) 32 33 // SubnetLookup provides an efficient lookup for individual 34 // IPv4 addresses within a list of subnets. 35 type SubnetLookup []net.IPNet 36 37 // NewSubnetLookup creates a SubnetLookup from a list of 38 // subnet CIDRs. 39 func NewSubnetLookup(CIDRs []string) (SubnetLookup, error) { 40 41 subnets := make([]net.IPNet, len(CIDRs)) 42 43 for i, CIDR := range CIDRs { 44 _, network, err := net.ParseCIDR(CIDR) 45 if err != nil { 46 return nil, errors.Trace(err) 47 } 48 subnets[i] = *network 49 } 50 51 lookup := SubnetLookup(subnets) 52 sort.Sort(lookup) 53 54 return lookup, nil 55 } 56 57 // NewSubnetLookupFromRoutes creates a SubnetLookup from text routes 58 // data. The input format is expected to be text lines where each line 59 // is, e.g., "1.2.3.0\t255.255.255.0\n" 60 func NewSubnetLookupFromRoutes(routesData []byte) (SubnetLookup, error) { 61 62 // Parse text routes data 63 var subnets []net.IPNet 64 scanner := bufio.NewScanner(bytes.NewReader(routesData)) 65 scanner.Split(bufio.ScanLines) 66 for scanner.Scan() { 67 s := strings.Split(scanner.Text(), "\t") 68 if len(s) != 2 { 69 continue 70 } 71 72 ip := parseIPv4(s[0]) 73 mask := parseIPv4Mask(s[1]) 74 if ip == nil || mask == nil { 75 continue 76 } 77 78 subnets = append(subnets, net.IPNet{IP: ip.Mask(mask), Mask: mask}) 79 } 80 if len(subnets) == 0 { 81 return nil, errors.TraceNew("Routes data contains no networks") 82 } 83 84 lookup := SubnetLookup(subnets) 85 sort.Sort(lookup) 86 87 return lookup, nil 88 } 89 90 func parseIPv4(s string) net.IP { 91 ip := net.ParseIP(s) 92 if ip == nil { 93 return nil 94 } 95 return ip.To4() 96 } 97 98 func parseIPv4Mask(s string) net.IPMask { 99 ip := parseIPv4(s) 100 if ip == nil { 101 return nil 102 } 103 mask := net.IPMask(ip) 104 if bits, size := mask.Size(); bits == 0 || size == 0 { 105 return nil 106 } 107 return mask 108 } 109 110 // Len implements Sort.Interface 111 func (lookup SubnetLookup) Len() int { 112 return len(lookup) 113 } 114 115 // Swap implements Sort.Interface 116 func (lookup SubnetLookup) Swap(i, j int) { 117 lookup[i], lookup[j] = lookup[j], lookup[i] 118 } 119 120 // Less implements Sort.Interface 121 func (lookup SubnetLookup) Less(i, j int) bool { 122 return binary.BigEndian.Uint32(lookup[i].IP) < binary.BigEndian.Uint32(lookup[j].IP) 123 } 124 125 // ContainsIPAddress performs a binary search on the sorted subnet 126 // list to find a network containing the candidate IP address. 127 func (lookup SubnetLookup) ContainsIPAddress(addr net.IP) bool { 128 129 // Search criteria 130 // 131 // The following conditions are satisfied when address_IP is in the network: 132 // 1. address_IP ^ network_mask == network_IP ^ network_mask 133 // 2. address_IP >= network_IP. 134 // We are also assuming that network ranges do not overlap. 135 // 136 // For an ascending array of networks, the sort.Search returns the smallest 137 // index idx for which condition network_IP > address_IP is satisfied, so we 138 // are checking whether or not adrress_IP belongs to the network[idx-1]. 139 140 // Edge conditions check 141 // 142 // idx == 0 means that address_IP is lesser than the first (smallest) network_IP 143 // thus never satisfies search condition 2. 144 // idx == array_length means that address_IP is larger than the last (largest) 145 // network_IP so we need to check the last element for condition 1. 146 147 ipv4 := addr.To4() 148 if ipv4 == nil { 149 return false 150 } 151 addrValue := binary.BigEndian.Uint32(ipv4) 152 index := sort.Search(len(lookup), func(i int) bool { 153 networkValue := binary.BigEndian.Uint32(lookup[i].IP) 154 return networkValue > addrValue 155 }) 156 return index > 0 && lookup[index-1].IP.Equal(addr.Mask(lookup[index-1].Mask)) 157 }