diff --git a/repique/features/dns/nodes/dnscrypt.go b/repique/features/dns/nodes/dnscrypt.go index 57f4172..3bf6fbc 100644 --- a/repique/features/dns/nodes/dnscrypt.go +++ b/repique/features/dns/nodes/dnscrypt.go @@ -21,18 +21,34 @@ type dnscryptnode struct { relayaddr *atomic.Value //*EPRing dailFn common.DialFn bs_relays []*common.Endpoint + randomSvrPK bool } +const regulation_cycle = 5 * time.Second func (n *dnscryptnode) boost(o *node) interface{} { - if o.status&status_outdated == status_outdated || n.GetDefaultExpiration().Before(time.Now()) { - relays := n.bs_relays - if _, err := dnscrypt.RetrieveServicesInfo(false, n.Resolver, n.dailFn, n.Network, n.ipaddr, &relays); err == nil { - n.bs2epring(relays) - expired := n.GetDefaultExpiration() - return &expired - } else { - dlog.Debugf("dnscrypt boost failed, %v", err) + relays := n.bs_relays + if _, err := dnscrypt.RetrieveServicesInfo(false, n.Resolver, n.dailFn, n.Network, n.ipaddr, &relays); err == nil { + n.bs2epring(relays) + if n.randomSvrPK { + if s := n.GetDefaultServices(); len(s) > 1 { + expired := time.Unix(int64(s[0].DtFrom), 0).Add(time.Duration(s[1].Regular) * time.Hour).Add(regulation_cycle) + return &expired + } + } + expired := n.GetDefaultExpiration() + return &expired + } else { + if n.randomSvrPK && time.Now().Before(n.GetDefaultExpiration()) { + if s := n.GetDefaultServices(); len(s) > 1 { + expired := time.Unix(int64(s[0].DtFrom), 0).Add(time.Duration(s[1].Regular) * time.Hour) + if time.Since(expired) > time.Minute { + dlog.Debugf("abort dnscrypt early regulation boost") + return n.GetDefaultExpiration() + } + return time.Now().Add(regulation_cycle) + } } + dlog.Debugf("dnscrypt boost failed, %v", err) } return nil } @@ -51,33 +67,45 @@ func (n *dnscryptnode) material() marshaling { return n } -const dnscryptmarshalfmt = "%d %d %d %d %d %x %x" + eol +const dnscryptmarshalfmt = "%d %d %d %d %d %d %x %x" + eol func (n *dnscryptnode) marshal() *struct{c uint8; v string} { - s := n.GetDefaultService() + ss := n.GetDefaultServices() + var count uint8 = 0 var c strings.Builder - fmt.Fprintf(&c, dnscryptmarshalfmt, s.Version, s.Minor, s.Serial, s.DtFrom, s.DtTo, s.MagicQuery, s.ServerPk) - return &struct{c uint8; v string} {1,c.String()} + for _, s := range ss { + if time.Now().After(time.Unix(int64(s.DtTo), 0)) { + break + } + count++ + fmt.Fprintf(&c, dnscryptmarshalfmt, s.Version, s.Minor, s.Serial, s.DtFrom, s.DtTo, s.Regular, s.MagicQuery, s.ServerPk) + } + return &struct{c uint8; v string} {count, c.String()} } -func (n *dnscryptnode) unmarshal(ss *struct{c uint8; v string}) *time.Time { - s := &dnscrypt.ServiceInfo{Service:&dnscrypt.Service{ServerKey:&dnscrypt.ServerKey{}}} - c := strings.NewReader(ss.v) - var mq, sk []byte - if _, err := fmt.Fscanf(c, dnscryptmarshalfmt, - &s.Version, &s.Minor, &s.Serial, &s.DtFrom, &s.DtTo, &mq, &sk); err != nil { - panic(err) - } - copy(s.MagicQuery[:], mq) - copy(s.ServerPk[:], sk) - s.Name = n.name() - if s.Version == dnscrypt.XSalsa20Poly1305 { - n.V1_Services = append(n.V1_Services, s) - } else { - n.V2_Services = append(n.V2_Services, s) +func (n *dnscryptnode) unmarshal(ss *struct{c uint8; v string}) (to *time.Time) { + for i := ss.c; i > 0; i -- { + s := &dnscrypt.ServiceInfo{Service:&dnscrypt.Service{ServerKey:&dnscrypt.ServerKey{}}} + c := strings.NewReader(ss.v) + var mq, sk []byte + if _, err := fmt.Fscanf(c, dnscryptmarshalfmt, + &s.Version, &s.Minor, &s.Serial, &s.DtFrom, &s.DtTo, &s.Regular, &mq, &sk); err != nil { + panic(err) + } + copy(s.MagicQuery[:], mq) + copy(s.ServerPk[:], sk) + s.Name = n.name() + if s.Version == dnscrypt.XSalsa20Poly1305 { + n.V1_Services = append(n.V1_Services, s) + } else { + n.V2_Services = append(n.V2_Services, s) + } + defer func() { + t := time.Unix(int64(s.DtTo), 0) + to = &t + }() } n.bs2epring(n.bs_relays) - to := time.Unix(int64(s.DtTo), 0) - return &to + return } func (_ *dnscryptnode) proto() string { @@ -95,5 +123,8 @@ func (n *dnscryptnode) exchange(bin *[]byte, _ ...interface{}) (*[]byte, error) relayAddr = ep.Endpoint n.relayaddr.Store(ep.Next()) } + if n.randomSvrPK { + return dnscrypt.Query(n.dailFn, n.Network, n.GetRandomService().Service, bin, n.ipaddr, relayAddr) + } return dnscrypt.Query(n.dailFn, n.Network, n.GetDefaultService().Service, bin, n.ipaddr, relayAddr) } diff --git a/repique/features/dns/nodes/node_manager.go b/repique/features/dns/nodes/node_manager.go index bd7b294..d1d19f7 100644 --- a/repique/features/dns/nodes/node_manager.go +++ b/repique/features/dns/nodes/node_manager.go @@ -41,14 +41,15 @@ const ( //Predefined Well-known Tag Name const ( - Well_Known_Tag_NO_PROXY = "NO-PROXY" // intranet - Well_Known_Tag_HTTP_USE_GET = "HTTP-USE-GET" - Well_Known_Tag_DNS_BOOST_GROUP = "DNS-BOOST-GROUP" // shared across all servers - Well_Known_Tag_DNSSEC_PRIME_GROUP = "DNSSEC-PRIME-GROUP" // shared across all servers - Well_Known_Tag_DNSCRPT_OBTAIN_FAST_KEY = "DNSCRPT-OBTAIN-FAST-KEY" // works with cfg:credentialpath when import_credential=false - Well_Known_Tag_TIMEOUT1 = "TIMEOUT1" //1s - Well_Known_Tag_TIMEOUT2 = "TIMEOUT2" //2s - Well_Known_Tag_TIMEOUT3 = "TIMEOUT3" //3s + Well_Known_Tag_NO_PROXY = "NO-PROXY" // intranet + Well_Known_Tag_HTTP_USE_GET = "HTTP-USE-GET" + Well_Known_Tag_DNS_BOOST_GROUP = "DNS-BOOST-GROUP" // shared across all servers + Well_Known_Tag_DNSSEC_PRIME_GROUP = "DNSSEC-PRIME-GROUP" // shared across all servers + Well_Known_Tag_DNSCRPT_OBTAIN_FAST_KEY = "DNSCRPT-OBTAIN-FAST-KEY" // works with cfg:credentialpath when import_credential=false + Well_Known_Tag_DNSCRPT_EARLY_REGULATION = "DNSCRPT-EARLY-REGULATION" // catch up 'key rotation' and use 1o2 random server PK + Well_Known_Tag_TIMEOUT1 = "TIMEOUT1" //1s + Well_Known_Tag_TIMEOUT2 = "TIMEOUT2" //2s + Well_Known_Tag_TIMEOUT3 = "TIMEOUT3" //3s ) type connectivity interface { @@ -387,6 +388,7 @@ func (mgr *NodesMgr) Init(cfg *Config, routes *AnonymizedDNSConfig, sum []byte, Resolver:r, ipaddr:ep, bs_relays:newroutes(name), + randomSvrPK:hasTag(Well_Known_Tag_DNSCRPT_EARLY_REGULATION, svr.Name), } node.dailFn = func(network, address string) (net.Conn, error) { if network == "udp" && !node.Proxies.UDPProxies() { diff --git a/repique/protocols/dnscrypt/dnscrypt.go b/repique/protocols/dnscrypt/dnscrypt.go index cce7e4a..02c43c8 100644 --- a/repique/protocols/dnscrypt/dnscrypt.go +++ b/repique/protocols/dnscrypt/dnscrypt.go @@ -85,6 +85,7 @@ type Resolver struct { type ServiceInfo struct { *Service Minor uint16 + Regular uint16 //A.K.A period of key rotation in hours if exists Serial uint32 DtFrom uint32 DtTo uint32 @@ -102,11 +103,34 @@ type ServerKey struct { ServerPk [PublicKeySize]byte } -func (r *Resolver) GetDefaultService() (s *ServiceInfo) { +func (r *Resolver) GetDefaultServices() []*ServiceInfo { if len(r.V2_Services) != 0 { - s = r.V2_Services[0] + return r.V2_Services } else if len(r.V1_Services) != 0 { - s = r.V1_Services[0] + return r.V1_Services + } + return nil +} + +func (r *Resolver) GetDefaultService() (s *ServiceInfo) { + if s := r.GetDefaultServices(); s != nil { + return s[0] + } + return +} + +func (r *Resolver) GetRandomService() (s *ServiceInfo) { + if s := r.GetDefaultServices(); s != nil { + if len(s) > 1 && time.Now().Before(time.Unix(int64(s[1].DtTo), 0)) { + var c chan int + defer close(c) + select { + case c <- 0: + case c <- 1: + } + return s[<-c] + } + return s[0] } return } @@ -284,33 +308,46 @@ RowLoop: } if len(resolver.V1_Services) != 0 || len(resolver.V2_Services) != 0 || len(resolver.VN_Services) != 0 { deleted := make(map[ServerKey]interface{}) - visitFn := func(sis []*ServiceInfo, sis0 []*ServiceInfo) { + visitFn := func(sis []*ServiceInfo, sis0 *[]*ServiceInfo) { for _, si := range sis { if _, found := keys[*si.ServerKey]; !found { deleted[*si.ServerKey] = nil - sis0 = append(sis0, si) + *sis0 = append(*sis0, si) } else { delete(keys, *si.ServerKey) } } } - visitFn(resolver.V1_Services, v1_Services) - visitFn(resolver.V2_Services, v2_Services) - visitFn(resolver.VN_Services, vn_Services) + visitFn(resolver.V1_Services, &v1_Services) + visitFn(resolver.V2_Services, &v2_Services) + visitFn(resolver.VN_Services, &vn_Services) dlog.Infof("[%s] public key re-engage: added=%d deleted=%d", *resolver.Name, len(keys), len(deleted)) } sortF := func(sis []*ServiceInfo) { sort.Slice(sis, func(i, j int) bool { - return sis[i].Serial > sis[j].Serial + return sis[i].Serial > sis[j].Serial || (sis[i].Serial == sis[j].Serial && sis[i].DtTo > sis[j].DtTo ) }) - for _, si := range sis { + for idx, si := range sis { from := time.Unix(int64(si.DtFrom), 0).UTC() to := time.Unix(int64(si.DtTo), 0).UTC() - dlog.Infof("[%s] public key info: ver=%d.%d serial=%d from=UTC%v-%d-%v+%.2v:%.2v to=UTC%v-%d-%v+%.2v:%.2v len_ext=%d", *resolver.Name, + d := to.Sub(from) + checkdt := func(d time.Duration) { + if d <= 0 || d > 15 * 24 * time.Hour { + panic("stop using it! malformed datetime represented of " + *resolver.Name) + } + } + checkdt(d) + if idx == 0 { + checkdt(time.Until(to)) + } + if idx > 0 { + si.Regular = uint16((time.Duration(si.DtTo - sis[idx-1].DtFrom) * time.Second).Truncate(time.Hour).Hours()) + } + dlog.Infof("[%s] public key info: ver=%d.%d serial=%d from=UTC%v-%d-%v+%.2v:%.2v to=UTC%v-%d-%v+%.2v:%.2v len_ext=%d R=%dH", *resolver.Name, si.Version, si.Minor, si.Serial, from.Year(), from.Month(), from.Day(), from.Hour(), from.Minute(), to.Year(), to.Month(), to.Day(), to.Hour(), to.Minute(), - len(si.Ext)) + len(si.Ext), si.Regular) } } sortF(v1_Services)