diff --git a/interviews/match-ip-addresses/README.md b/interviews/match-ip-addresses/README.md new file mode 100644 index 0000000..f172ddb --- /dev/null +++ b/interviews/match-ip-addresses/README.md @@ -0,0 +1,9 @@ +# IP Blacklist + +Handle a IP blacklist structure in which you receive IP Addresses in the form of: + +1. Single IP: `10.0.0.1` +2. IP Range: `11.0.0.1-11.0.0.255` +3. IP CIDR: `12.0.0.0/16` + +And then you can check if a given IP matches any of those rules. diff --git a/interviews/match-ip-addresses/main.go b/interviews/match-ip-addresses/main.go new file mode 100644 index 0000000..dd8a4e2 --- /dev/null +++ b/interviews/match-ip-addresses/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "encoding/binary" + "fmt" + "net" + "strings" +) + +func netIPToUint32(ip []byte) uint32 { + if len(ip) == 16 { + return binary.BigEndian.Uint32(ip[12:16]) + } + return binary.BigEndian.Uint32(ip) +} + +type IPRange struct { + Start uint32 + End uint32 +} + +type IPMatcher struct { + ranges []IPRange +} + +func int2ip(nn uint32) net.IP { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, nn) + return ip +} + +func (ipm *IPMatcher) Set(rules []string) error { + var ranges []IPRange + + for _, rule := range rules { + if strings.Contains(rule, "/") { + // CIDR + _, network, err := net.ParseCIDR(rule) + if err != nil { + return fmt.Errorf("error parsing rules for %s: %s", rule, err) + } + ranges = append(ranges, IPRange{ + Start: netIPToUint32(network.IP), + End: (netIPToUint32(network.IP) & netIPToUint32(network.Mask)) | (netIPToUint32(network.Mask) ^ 0xffffffff), + }) + } else if strings.Contains(rule, "-") { + // Range + parts := strings.Split(rule, "-") + ranges = append(ranges, IPRange{ + Start: netIPToUint32(net.ParseIP(parts[0])), + End: netIPToUint32(net.ParseIP(parts[1])), + }) + } else { + // Single IP + ranges = append(ranges, + IPRange{ + Start: netIPToUint32(net.ParseIP(rule)), + End: netIPToUint32(net.ParseIP(rule)), + }) + } + } + + // TODO: Sort ranges + // TODO: Find overlapping ranges + ipm.ranges = ranges + + return nil +} + +func (ipm *IPMatcher) Matches(ip net.IP) bool { + incomingIp := netIPToUint32(ip) + for _, ipRange := range ipm.ranges { + if (ipRange.Start <= incomingIp) && (incomingIp <= ipRange.End) { + return true + } + } + return false +} + +func main() { + matcher := IPMatcher{} + matcher.Set([]string{ + "11.0.0.0/24", + "10.0.0.1", + "12.0.0.1-12.0.255.255", + }) + + assert(matcher.Matches(net.ParseIP("10.0.0.2")), false, "10.0.0.2 should not match") + assert(matcher.Matches(net.ParseIP("10.0.0.1")), true, "10.0.0.1 should match") + assert(matcher.Matches(net.ParseIP("11.0.0.50")), true, "11.0.0.50 should match") + assert(matcher.Matches(net.ParseIP("11.0.0.255")), true, "11.0.0.255 should match") + assert(matcher.Matches(net.ParseIP("12.0.0.0")), false, "12.0.0.0 should not match") + assert(matcher.Matches(net.ParseIP("12.0.50.1")), true, "12.0.50.1 should match") + fmt.Println("done!") +} + +func assert(result, expected bool, description string) { + if result != expected { + fmt.Println(description) + } +}