diff --git a/server/http.go b/server/http.go index e0eb40e9..34c929be 100644 --- a/server/http.go +++ b/server/http.go @@ -1,30 +1,77 @@ package weavedns import ( + "errors" "fmt" "log" "net" "net/http" + "strings" ) +// Parse a URL of the form PUT /name// +func parseUrl(url string) (identifier string, ipaddr string, err error) { + parts := strings.Split(url, "/") + if len(parts) != 4 { + return "", "", errors.New("Unable to parse url: " + url) + } + return parts[2], parts[3], nil +} + func ListenHttp(db Zone, port int) { - http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) { - name := r.FormValue("name") - addr := r.FormValue("ip") - weave_cidr := r.FormValue("weave_cidr") - if name != "" && addr != "" && weave_cidr != "" { - ip := net.ParseIP(addr) - weave_ip, subnet, err := net.ParseCIDR(weave_cidr) - if err == nil && ip != nil { - log.Printf("Adding %s (%s) -> %s", name, addr, weave_cidr) - db.AddRecord(name, ip, weave_ip, subnet) - } else if err != nil { - log.Printf("Invalid CIDR in request: %s", weave_cidr) - } else { - log.Printf("Invalid IP in request: %s", addr) + http.HandleFunc("/name/", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "PUT": + identifier, weave_ipstr, err := parseUrl(r.URL.Path) + name := r.FormValue("fqdn") + prefix := r.FormValue("routing_prefix") + local_ip := r.FormValue("local_ip") + if identifier == "" || weave_ipstr == "" || name == "" || prefix == "" || local_ip == "" { + log.Printf("Invalid request: %s, %s", r.URL, r.Form) + http.Error(w, "Invalid Request", http.StatusBadRequest) + return } - } else { - log.Printf("Invalid request: %s, %s, %s", name, addr, weave_cidr) + ip := net.ParseIP(local_ip) + if ip == nil { + log.Printf("Invalid IP in request: %s", local_ip) + http.Error(w, "Invalid IP in request", http.StatusBadRequest) + return + } + weave_cidr := weave_ipstr + "/" + prefix + weave_ip, subnet, err := net.ParseCIDR(weave_cidr) + if err != nil { + log.Printf("Invalid CIDR in request: %s", weave_cidr) + http.Error(w, fmt.Sprintf("Invalid CIDR: %s", weave_cidr), http.StatusBadRequest) + return + } + log.Printf("Adding %s (%s) -> %s", name, local_ip, weave_cidr) + err = db.AddRecord(identifier, name, ip, weave_ip, subnet) + if err != nil { + http.Error(w, err.Error(), http.StatusConflict) + return + } + case "DELETE": + identifier, weave_ipstr, err := parseUrl(r.URL.Path) + if identifier == "" || weave_ipstr == "" { + log.Printf("Invalid request: %s, %s", r.URL, r.Form) + http.Error(w, "Invalid Request", http.StatusBadRequest) + return + } + weave_ip := net.ParseIP(weave_ipstr) + if weave_ip == nil { + log.Printf("Invalid IP in request: %s", weave_ipstr) + http.Error(w, "Invalid IP in request", http.StatusBadRequest) + return + } + log.Printf("Deleting %s (%s)", identifier, weave_ipstr) + err = db.DeleteRecord(identifier, weave_ip) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + default: + log.Println("Unexpected http method", r.Method) + http.Error(w, "Unexpected http method: "+r.Method, http.StatusBadRequest) } }) address := fmt.Sprintf(":%d", port) diff --git a/server/http_test.go b/server/http_test.go index 7a1cfe78..483dc920 100644 --- a/server/http_test.go +++ b/server/http_test.go @@ -6,12 +6,20 @@ import ( "net" "net/http" "net/url" + "strings" "testing" "time" ) +func genForm(method string, url string, data url.Values) (resp *http.Response, err error) { + req, err := http.NewRequest(method, url, strings.NewReader(data.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return http.DefaultClient.Do(req) +} + func TestHttp(t *testing.T) { var ( + container_id = "deadbeef" success_test_name = "test1.weave." test_addr1 = "10.0.2.1/24" docker_ip = "9.8.7.6" @@ -24,25 +32,51 @@ func TestHttp(t *testing.T) { time.Sleep(100 * time.Millisecond) // Allow for http server to get going - resp, err := http.PostForm(fmt.Sprintf("http://localhost:%d/add", port), - url.Values{"name": {success_test_name}, "ip": {docker_ip}, "weave_cidr": {test_addr1}}) + // Ask the http server to add our test address into the database + addr_parts := strings.Split(test_addr1, "/") + addr_url := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, container_id, addr_parts[0]) + resp, err := genForm("PUT", addr_url, + url.Values{"fqdn": {success_test_name}, "local_ip": {docker_ip}, "routing_prefix": {addr_parts[1]}}) if err != nil { t.Fatal(err) } if resp.StatusCode != 200 { - t.Log("Unexpected http response", resp.Status) - t.Fail() + t.Fatal("Unexpected http response", resp.Status) } + // Check that the address is now there. ip, err := zone.MatchLocal(success_test_name) if err != nil { - t.Log(zone) t.Fatal(err) } weave_ip, _, _ := net.ParseCIDR(test_addr1) if !ip.Equal(weave_ip) { - t.Log("Unexpected result for", success_test_name, ip) - t.Fail() + t.Fatal("Unexpected result for", success_test_name, ip) + } + + // Now try adding the same address again - should fail + resp, err = genForm("PUT", addr_url, + url.Values{"fqdn": {success_test_name}, "local_ip": {docker_ip}, "routing_prefix": {addr_parts[1]}}) + if err != nil { + t.Fatal(err) + } + if resp.StatusCode != http.StatusConflict { + t.Fatal("Unexpected http response", resp.Status) + } + + // Delete the address + resp, err = genForm("DELETE", addr_url, nil) + if err != nil { + t.Fatal(err) + } + if resp.StatusCode != http.StatusOK { + t.Fatal("Unexpected http response", resp.Status) + } + + // Check that the address is not there now. + _, err = zone.MatchLocal(success_test_name) + if _, ok := err.(LookupError); !ok { + t.Fatal(err) } // Would like to shut down the http server at the end of this test diff --git a/server/mdns_server_test.go b/server/mdns_server_test.go index 68e2f387..aafe821e 100644 --- a/server/mdns_server_test.go +++ b/server/mdns_server_test.go @@ -9,7 +9,8 @@ import ( ) var ( - test_addr1 = "10.0.2.1/24" + container_id = "deadbeef" + test_addr1 = "10.0.2.1/24" ) func sendQuery(name string, querytype uint16) error { @@ -33,7 +34,7 @@ func TestServerSimpleQuery(t *testing.T) { var zone = new(ZoneDb) docker_ip := net.ParseIP("9.8.7.6") weave_ip, subnet, _ := net.ParseCIDR(test_addr1) - zone.AddRecord("test.weave.", docker_ip, weave_ip, subnet) + zone.AddRecord(container_id, "test.weave.", docker_ip, weave_ip, subnet) mdnsServer, err := NewMDNSServer(zone) if err != nil { diff --git a/server/zone.go b/server/zone.go index 824dd8d8..d3173949 100644 --- a/server/zone.go +++ b/server/zone.go @@ -8,11 +8,13 @@ import ( ) type Zone interface { - AddRecord(string, net.IP, net.IP, *net.IPNet) error + AddRecord(string, string, net.IP, net.IP, *net.IPNet) error + DeleteRecord(ident string, weave_ip net.IP) error MatchLocal(string) (net.IP, error) } type Record struct { + Ident string Name string Ip net.IP WeaveIp net.IP @@ -27,7 +29,17 @@ type ZoneDb struct { type LookupError string func (ops LookupError) Error() string { - return string(ops) + return "Unable to find " + string(ops) +} + +type DuplicateError struct { + Name string + WeaveIp net.IP + Ident string +} + +func (err DuplicateError) Error() string { + return "Duplicate " + err.Name + "," + err.WeaveIp.String() + " in container " + err.Ident } // Stop gap. @@ -41,15 +53,49 @@ func (zone *ZoneDb) match(name string) (net.IP, error) { return nil, LookupError(name) } +func (zone *ZoneDb) indexOfNameAddr(name string, addr net.IP) int { + for i, r := range zone.recs { + if r.Name == name && r.WeaveIp.Equal(addr) { + return i + } + } + return -1 +} + +func (zone *ZoneDb) indexOfIdentAddr(ident string, addr net.IP) int { + for i, r := range zone.recs { + if r.Ident == ident && r.WeaveIp.Equal(addr) { + return i + } + } + return -1 +} + func (zone *ZoneDb) MatchLocal(name string) (net.IP, error) { zone.mx.Lock() defer zone.mx.Unlock() return zone.match(name) } -func (zone *ZoneDb) AddRecord(name string, ip net.IP, weave_ip net.IP, weave_subnet *net.IPNet) error { +func (zone *ZoneDb) AddRecord(identifier string, name string, ip net.IP, weave_ip net.IP, weave_subnet *net.IPNet) error { zone.mx.Lock() defer zone.mx.Unlock() - zone.recs = append(zone.recs, Record{dns.Fqdn(name), ip, weave_ip, weave_subnet}) + fqdn := dns.Fqdn(name) + if index := zone.indexOfNameAddr(fqdn, weave_ip); index != -1 { + return DuplicateError{fqdn, weave_ip, zone.recs[index].Ident} + } + zone.recs = append(zone.recs, Record{identifier, fqdn, ip, weave_ip, weave_subnet}) + return nil +} + +func (zone *ZoneDb) DeleteRecord(ident string, weave_ip net.IP) error { + zone.mx.Lock() + defer zone.mx.Unlock() + if index := zone.indexOfIdentAddr(ident, weave_ip); index == -1 { + return LookupError(ident) + } else { + zone.recs = append(zone.recs[:index], zone.recs[index+1:]...) + } + return nil } diff --git a/server/zone_test.go b/server/zone_test.go new file mode 100644 index 00000000..a3c7b252 --- /dev/null +++ b/server/zone_test.go @@ -0,0 +1,51 @@ +package weavedns + +import ( + "net" + "testing" +) + +func TestZone(t *testing.T) { + var ( + container_id = "deadbeef" + success_test_name = "test1.weave." + test_addr1 = "10.0.2.1/24" + docker_ip = "9.8.7.6" + ) + + var zone = new(ZoneDb) + + ip := net.ParseIP(docker_ip) + weave_ip, subnet, _ := net.ParseCIDR(test_addr1) + err := zone.AddRecord(container_id, success_test_name, ip, weave_ip, subnet) + if err != nil { + t.Fatal(err) + } + + // Check that the address is now there. + found_ip, err := zone.MatchLocal(success_test_name) + if err != nil { + t.Fatal(err) + } + if !found_ip.Equal(weave_ip) { + t.Fatal("Unexpected result for", success_test_name, ip) + } + + // Now try to add the same address again + err = zone.AddRecord(container_id, success_test_name, ip, weave_ip, subnet) + if _, ok := err.(DuplicateError); !ok { + t.Fatal(err) + } + + // Now delete the record + err = zone.DeleteRecord(container_id, weave_ip) + if err != nil { + t.Fatal(err) + } + + // Check that the address is not there now. + _, err = zone.MatchLocal(success_test_name) + if _, ok := err.(LookupError); !ok { + t.Fatal("Unexpected result when deleting record", success_test_name, err) + } +}