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  }