Handle captive portal names after coldstart

This commit is contained in:
Frank Denis 2021-01-01 21:39:17 +01:00
parent c308727d15
commit f245189f02
4 changed files with 125 additions and 43 deletions

View File

@ -11,9 +11,9 @@ import (
"github.com/miekg/dns"
)
type CaptivePortalEntryips []net.IP
type CaptivePortalEntryIPs []net.IP
type CaptivePortalMap map[string]CaptivePortalEntryips
type CaptivePortalMap map[string]CaptivePortalEntryIPs
type CaptivePortalHandler struct {
cancelChannels []chan struct{}
@ -26,6 +26,75 @@ func (captivePortalHandler *CaptivePortalHandler) Stop() {
}
}
func (ipsMap *CaptivePortalMap) GetEntry(msg *dns.Msg) (*dns.Question, *CaptivePortalEntryIPs) {
if len(msg.Question) != 1 {
return nil, nil
}
question := &msg.Question[0]
name, err := NormalizeQName(question.Name)
if err != nil {
return nil, nil
}
ips, ok := (*ipsMap)[name]
if !ok {
return nil, nil
}
if question.Qclass != dns.ClassINET {
return nil, nil
}
return question, &ips
}
func HandleCaptivePortalQuery(msg *dns.Msg, question *dns.Question, ips *CaptivePortalEntryIPs) *dns.Msg {
respMsg := EmptyResponseFromMessage(msg)
ttl := uint32(1)
if question.Qtype == dns.TypeA {
for _, xip := range *ips {
if ip := xip.To4(); ip != nil {
rr := new(dns.A)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}
rr.A = ip
respMsg.Answer = append(respMsg.Answer, rr)
}
}
} else if question.Qtype == dns.TypeAAAA {
for _, xip := range *ips {
if ip := xip.To16(); ip != nil {
rr := new(dns.AAAA)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
rr.AAAA = ip
respMsg.Answer = append(respMsg.Answer, rr)
}
}
} else if question.Qtype == dns.TypeHTTPS {
rr := new(dns.HTTPS)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeHTTPS, Class: dns.ClassINET, Ttl: ttl}
v4 := new(dns.SVCBIPv4Hint)
v6 := new(dns.SVCBIPv6Hint)
for _, xip := range *ips {
if ip := xip.To4(); ip != nil {
v4.Hint = append(v4.Hint, ip)
} else if ip := xip.To16(); ip != nil {
v6.Hint = append(v6.Hint, ip)
}
}
if len(v4.Hint) > 0 {
rr.Value = append(rr.Value, v4)
}
if len(v6.Hint) > 0 {
rr.Value = append(rr.Value, v6)
}
respMsg.Answer = []dns.RR{rr}
}
qType, ok := dns.TypeToString[question.Qtype]
if !ok {
qType = fmt.Sprint(question.Qtype)
}
dlog.Noticef("Query for captive portal detection: [%v] (%v)", question.Name, qType)
return respMsg
}
func handleColdStartClient(clientPc *net.UDPConn, cancelChannel chan struct{}, ipsMap *CaptivePortalMap) bool {
buffer := make([]byte, MaxDNSPacketSize-1)
clientPc.SetDeadline(time.Now().Add(time.Duration(1) * time.Second))
@ -51,53 +120,17 @@ func handleColdStartClient(clientPc *net.UDPConn, cancelChannel chan struct{}, i
if err := msg.Unpack(packet); err != nil {
return false
}
if len(msg.Question) != 1 {
question, ips := ipsMap.GetEntry(msg)
if ips == nil {
return false
}
question := msg.Question[0]
qType, ok := dns.TypeToString[question.Qtype]
if !ok {
qType = string(qType)
}
name, err := NormalizeQName(question.Name)
if err != nil {
respMsg := HandleCaptivePortalQuery(msg, question, ips)
if respMsg == nil {
return false
}
ips, ok := (*ipsMap)[name]
if !ok {
dlog.Infof("Coldstart query: [%v] (%v)", name, qType)
return false
}
dlog.Noticef("Coldstart query for captive portal detection: [%v] (%v)", name, qType)
if question.Qclass != dns.ClassINET {
return false
}
respMsg := EmptyResponseFromMessage(msg)
ttl := uint32(1)
if question.Qtype == dns.TypeA {
for _, xip := range ips {
if ip := xip.To4(); ip != nil {
rr := new(dns.A)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}
rr.A = ip
respMsg.Answer = []dns.RR{rr}
break
}
}
} else if question.Qtype == dns.TypeAAAA {
for _, xip := range ips {
if ip := xip.To16(); ip != nil {
rr := new(dns.AAAA)
rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
rr.AAAA = ip
respMsg.Answer = []dns.RR{rr}
break
}
}
}
if response, err := respMsg.Pack(); err == nil {
clientPc.WriteTo(response, clientAddr)
dlog.Noticef("Coldstart query synthesized: [%v] (%v)", name, qType)
dlog.Notice("Response to coldstart captive portal query synthesized")
}
return false
}
@ -171,5 +204,6 @@ func ColdStart(proxy *Proxy) (*CaptivePortalHandler, error) {
captivePortalHandler := CaptivePortalHandler{
cancelChannels: cancelChannels,
}
proxy.captivePortalMap = &ipsMap
return &captivePortalHandler, nil
}

View File

@ -0,0 +1,44 @@
package main
import (
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
)
type PluginCaptivePortal struct {
captivePortalMap *CaptivePortalMap
}
func (plugin *PluginCaptivePortal) Name() string {
return "captive portal handlers"
}
func (plugin *PluginCaptivePortal) Description() string {
return "Handle test queries operating systems make to detect Wi-Fi captive portal"
}
func (plugin *PluginCaptivePortal) Init(proxy *Proxy) error {
plugin.captivePortalMap = proxy.captivePortalMap
return nil
}
func (plugin *PluginCaptivePortal) Drop() error {
return nil
}
func (plugin *PluginCaptivePortal) Reload() error {
return nil
}
func (plugin *PluginCaptivePortal) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
question, ips := plugin.captivePortalMap.GetEntry(msg)
if ips == nil {
return nil
}
if synth := HandleCaptivePortalQuery(msg, question, ips); synth != nil {
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth
dlog.Notice("Response to captive portal query synthesized")
}
return nil
}

View File

@ -93,6 +93,9 @@ type PluginsState struct {
func (proxy *Proxy) InitPluginsGlobals() error {
queryPlugins := &[]Plugin{}
if proxy.captivePortalMap != nil {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginCaptivePortal)))
}
if len(proxy.queryMeta) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginQueryMeta)))
}

View File

@ -40,6 +40,7 @@ type Proxy struct {
dohCreds *map[string]DOHClientCreds
allWeeklyRanges *map[string]WeeklyRanges
routes *map[string][]string
captivePortalMap *CaptivePortalMap
nxLogFormat string
localDoHCertFile string
localDoHCertKeyFile string