2016-04-25 23:02:08 +00:00
/ *
Copyright 2016 The Camlistore Authors
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
// The camnetdns server serves camlistore.net's DNS server and its
// DNS challenges
package main
import (
"errors"
"flag"
"log"
"net"
2016-10-18 22:51:01 +00:00
"strings"
2016-04-25 23:02:08 +00:00
"camlistore.org/pkg/sorted"
2016-10-03 23:55:58 +00:00
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/datastore"
"cloud.google.com/go/logging"
2016-04-25 23:02:08 +00:00
"github.com/miekg/dns"
2016-04-25 23:25:31 +00:00
"go4.org/cloud/cloudlaunch"
compute "google.golang.org/api/compute/v1"
2016-04-25 23:02:08 +00:00
)
2016-10-05 21:35:34 +00:00
var flagServerIP = flag . String ( "server_ip" , "104.154.231.160" , "The IP address of the authoritative name server for camlistore.net, i.e. the address where this program will run." )
// TODO(mpl): pass the server ip to the launchConfig, so we create the instance
// with this specific IP. Which means, we'll have to book it as a static address in
// Google Cloud I suppose?
// Or, we hope we're lucky and we never have to destroy the camnet-dns VM (and lose
// its current IP)?
2016-04-25 23:25:31 +00:00
var launchConfig = & cloudlaunch . Config {
Name : "camnetdns" ,
BinaryBucket : "camlistore-dnsserver-resource" ,
2016-04-25 23:38:04 +00:00
GCEProjectID : "camlistore-website" ,
2016-04-25 23:25:31 +00:00
Scopes : [ ] string {
compute . ComputeScope ,
logging . Scope ,
datastore . ScopeDatastore ,
} ,
}
2016-04-25 23:02:08 +00:00
// DefaultResponseTTL is the record TTL in seconds
const DefaultResponseTTL = 300
var ErrRecordNotFound = errors . New ( "record not found" )
2016-04-25 23:38:04 +00:00
func defaultListenAddr ( ) string {
if metadata . OnGCE ( ) {
return ":53"
}
return ":5300"
}
2016-04-25 23:02:08 +00:00
// DNSServer implements the dns.Handler interface to serve A and AAAA
// records using a sorted.KeyValue for the lookups.
type DNSServer struct {
dataSource sorted . KeyValue
}
func NewDNSServer ( src sorted . KeyValue ) * DNSServer {
return & DNSServer {
dataSource : src ,
}
}
func ( ds * DNSServer ) HandleLookup ( name string ) ( string , error ) {
2016-10-18 22:51:01 +00:00
// Lowercase it all, to satisfy https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00
return ds . dataSource . Get ( strings . ToLower ( name ) )
2016-04-25 23:02:08 +00:00
}
2016-10-18 22:51:01 +00:00
const (
domain = "camlistore.net."
authorityNS = "camnetdns.camlistore.org."
// Increment after every change with format YYYYMMDDnn.
2016-10-21 17:14:31 +00:00
soaSerial = 2016102101
2016-10-18 22:51:01 +00:00
)
2016-10-05 21:35:34 +00:00
var (
authoritySection = & dns . NS {
2016-10-18 22:51:01 +00:00
Ns : authorityNS ,
2016-10-05 21:35:34 +00:00
Hdr : dns . RR_Header {
2016-10-18 22:51:01 +00:00
Name : domain ,
2016-10-05 21:35:34 +00:00
Rrtype : dns . TypeNS ,
Class : dns . ClassINET ,
Ttl : DefaultResponseTTL ,
} ,
}
additionalSection = & dns . A {
A : net . ParseIP ( * flagServerIP ) ,
Hdr : dns . RR_Header {
2016-10-18 22:51:01 +00:00
Name : authorityNS ,
2016-10-05 21:35:34 +00:00
Rrtype : dns . TypeA ,
Class : dns . ClassINET ,
Ttl : DefaultResponseTTL ,
} ,
}
2016-10-18 22:51:01 +00:00
startOfAuthoritySection = & dns . SOA {
Hdr : dns . RR_Header {
Name : domain ,
Rrtype : dns . TypeSOA ,
Class : dns . ClassINET ,
Ttl : DefaultResponseTTL ,
} ,
Ns : authorityNS ,
Mbox : "admin.camlistore.org." ,
Serial : soaSerial ,
Refresh : 3600 , // TODO(mpl): set them lower once we got everything right.
Retry : 3600 ,
Expire : 86500 ,
Minttl : DefaultResponseTTL ,
}
2016-10-05 21:35:34 +00:00
)
2016-10-18 22:51:01 +00:00
func commonHeader ( q dns . Question ) dns . RR_Header {
return dns . RR_Header {
Name : q . Name ,
Rrtype : q . Qtype ,
Class : dns . ClassINET ,
Ttl : DefaultResponseTTL ,
}
}
2016-04-25 23:02:08 +00:00
func ( ds * DNSServer ) ServeDNS ( rw dns . ResponseWriter , mes * dns . Msg ) {
resp := new ( dns . Msg )
2016-10-18 22:51:01 +00:00
if mes . IsEdns0 ( ) != nil {
// Because apparently, if we're not going to handle EDNS
// properly, i.e. by returning an OPT section as well, we should
// return an RcodeFormatError. Not seen in the RFC, but doing that
// addresses some of the warnings from
// http://dnsviz.net/d/granivo.re/dnssec/
log . Print ( "unhandled EDNS message\n" )
resp . SetRcode ( mes , dns . RcodeFormatError )
if err := rw . WriteMsg ( resp ) ; err != nil {
log . Printf ( "error responding to DNS query: %s" , err )
}
return
}
2016-10-21 17:14:31 +00:00
resp . SetReply ( mes )
// TODO(mpl): Should we make sure that at least q.Name ends in
// "camlistore.net" before claiming we're authoritative on that response?
resp . Authoritative = true
2016-04-25 23:02:08 +00:00
for _ , q := range mes . Question {
2016-04-25 23:53:50 +00:00
log . Printf ( "DNS request from %s: %s" , rw . RemoteAddr ( ) , & q )
2016-10-21 17:14:31 +00:00
answer , err := ds . HandleLookup ( q . Name )
if err == sorted . ErrNotFound {
resp . SetRcode ( mes , dns . RcodeNameError )
if err := rw . WriteMsg ( resp ) ; err != nil {
log . Printf ( "error responding to DNS query: %s" , err )
}
return
}
if err != nil {
log . Printf ( "error looking up %q: %v" , q . Name , err )
continue
}
2016-10-18 22:51:01 +00:00
if q . Qclass != dns . ClassINET {
log . Printf ( "error: got invalid DNS question class %d\n" , q . Qclass )
continue
}
2016-04-25 23:02:08 +00:00
switch q . Qtype {
2016-10-18 22:51:01 +00:00
// As long as we send a reply (even an empty one), we apparently
// look compliant. Or at least more than if we replied with
// RcodeNotImplemented.
case dns . TypeDNSKEY , dns . TypeTXT , dns . TypeMX :
break
case dns . TypeSOA :
resp . Answer = [ ] dns . RR { startOfAuthoritySection }
resp . Extra = [ ] dns . RR { additionalSection }
case dns . TypeNS :
resp . Answer = [ ] dns . RR { authoritySection }
resp . Extra = [ ] dns . RR { additionalSection }
case dns . TypeCAA :
header := commonHeader ( q )
rr := & dns . CAA {
Hdr : header ,
Flag : 1 ,
Tag : "issue" ,
Value : "letsencrypt.org" ,
2016-04-25 23:02:08 +00:00
}
2016-10-18 22:51:01 +00:00
resp . Answer = [ ] dns . RR { rr }
2016-04-25 23:02:08 +00:00
2016-10-18 22:51:01 +00:00
case dns . TypeA , dns . TypeAAAA :
2016-10-21 17:14:31 +00:00
val := answer
2016-10-18 22:51:01 +00:00
ip := net . ParseIP ( val )
// TODO(mpl): maybe we should have a distinct memstore for each type?
isIP6 := strings . Contains ( ip . String ( ) , ":" )
header := commonHeader ( q )
2016-04-25 23:02:08 +00:00
var rr dns . RR
if q . Qtype == dns . TypeA {
2016-10-18 22:51:01 +00:00
if isIP6 {
break
}
2016-04-25 23:02:08 +00:00
rr = & dns . A {
2016-10-18 22:51:01 +00:00
A : ip ,
2016-04-25 23:02:08 +00:00
Hdr : header ,
}
} else if q . Qtype == dns . TypeAAAA {
2016-10-18 22:51:01 +00:00
if ! isIP6 {
break
}
2016-04-25 23:02:08 +00:00
rr = & dns . AAAA {
2016-10-18 22:51:01 +00:00
AAAA : ip ,
2016-04-25 23:02:08 +00:00
Hdr : header ,
}
} else {
panic ( "unreachable" )
}
2016-10-05 21:35:34 +00:00
resp . Answer = [ ] dns . RR { rr }
2016-10-18 22:51:01 +00:00
// Not necessary, but I think they help.
resp . Ns = [ ] dns . RR { authoritySection }
resp . Extra = [ ] dns . RR { additionalSection }
2016-04-25 23:02:08 +00:00
default :
log . Printf ( "unhandled qtype: %d\n" , q . Qtype )
2016-10-05 21:35:34 +00:00
resp . SetRcode ( mes , dns . RcodeNotImplemented )
2016-10-18 22:51:01 +00:00
if err := rw . WriteMsg ( resp ) ; err != nil {
log . Printf ( "error responding to DNS query: %s" , err )
}
2016-10-05 21:35:34 +00:00
return
2016-04-25 23:02:08 +00:00
}
2016-10-05 21:35:34 +00:00
break
2016-04-25 23:02:08 +00:00
}
2016-10-05 21:35:34 +00:00
2016-04-25 23:02:08 +00:00
if err := rw . WriteMsg ( resp ) ; err != nil {
log . Printf ( "error responding to DNS query: %s" , err )
}
}
func main ( ) {
2016-04-25 23:25:31 +00:00
launchConfig . MaybeDeploy ( )
2016-04-25 23:38:04 +00:00
addr := flag . String ( "addr" , defaultListenAddr ( ) , "specify address for server to listen on" )
2016-04-25 23:02:08 +00:00
flag . Parse ( )
memkv := sorted . NewMemoryKeyValue ( )
if err := memkv . Set ( "6401800c.camlistore.net." , "159.203.246.79" ) ; err != nil {
panic ( err )
}
2016-10-18 22:51:01 +00:00
if err := memkv . Set ( domain , * flagServerIP ) ; err != nil {
2016-04-25 23:25:31 +00:00
panic ( err )
}
2016-10-05 21:35:34 +00:00
if err := memkv . Set ( "www.camlistore.net." , * flagServerIP ) ; err != nil {
2016-04-25 23:53:50 +00:00
panic ( err )
}
2016-10-18 22:51:01 +00:00
if err := memkv . Set ( "wip.camlistore.net." , "104.199.42.193" ) ; err != nil {
panic ( err )
}
2016-04-25 23:02:08 +00:00
ds := NewDNSServer ( memkv )
log . Printf ( "serving DNS on %s\n" , * addr )
2016-10-18 22:51:01 +00:00
tcperr := make ( chan error , 1 )
udperr := make ( chan error , 1 )
go func ( ) {
tcperr <- dns . ListenAndServe ( * addr , "tcp" , ds )
} ( )
go func ( ) {
udperr <- dns . ListenAndServe ( * addr , "udp" , ds )
} ( )
select {
case err := <- tcperr :
log . Fatalf ( "DNS over TCP error: %v" , err )
case err := <- udperr :
log . Fatalf ( "DNS error: %v" , err )
2016-04-25 23:02:08 +00:00
}
}