@ -29,6 +29,7 @@ import (
"unicode"
"unicode/utf8"
"github.com/kortschak/wol"
"golang.org/x/net/dns/dnsmessage"
"inet.af/netaddr"
"tailscale.com/client/tailscale/apitype"
@ -563,6 +564,9 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case "/v0/dnsfwd" :
h . handleServeDNSFwd ( w , r )
return
case "/v0/wol" :
h . handleWakeOnLAN ( w , r )
return
}
who := h . peerUser . DisplayName
fmt . Fprintf ( w , ` < html >
@ -646,6 +650,11 @@ func (h *peerAPIHandler) canDebug() bool {
return h . isSelf || h . peerHasCap ( tailcfg . CapabilityDebugPeer )
}
// canWakeOnLAN reports whether h can send a Wake-on-LAN packet from this node.
func ( h * peerAPIHandler ) canWakeOnLAN ( ) bool {
return h . isSelf || h . peerHasCap ( tailcfg . CapabilityWakeOnLAN )
}
func ( h * peerAPIHandler ) peerHasCap ( wantCap string ) bool {
for _ , hasCap := range h . ps . b . PeerCaps ( h . remoteAddr . IP ( ) ) {
if hasCap == wantCap {
@ -836,8 +845,8 @@ func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Reque
}
func ( h * peerAPIHandler ) handleServeDNSFwd ( w http . ResponseWriter , r * http . Request ) {
if ! h . isSelf {
http . Error ( w , " not owner ", http . StatusForbidden )
if ! h . canDebug ( ) {
http . Error ( w , " denied; no debug access ", http . StatusForbidden )
return
}
dh := health . DebugHandler ( "dnsfwd" )
@ -848,6 +857,62 @@ func (h *peerAPIHandler) handleServeDNSFwd(w http.ResponseWriter, r *http.Reques
dh . ServeHTTP ( w , r )
}
func ( h * peerAPIHandler ) handleWakeOnLAN ( w http . ResponseWriter , r * http . Request ) {
if ! h . canWakeOnLAN ( ) {
http . Error ( w , "no WoL access" , http . StatusForbidden )
return
}
if r . Method != "POST" {
http . Error ( w , "bad method" , http . StatusMethodNotAllowed )
return
}
macStr := r . FormValue ( "mac" )
if macStr == "" {
http . Error ( w , "missing 'mac' param" , http . StatusBadRequest )
return
}
mac , err := net . ParseMAC ( macStr )
if err != nil {
http . Error ( w , "bad 'mac' param" , http . StatusBadRequest )
return
}
var password [ ] byte // TODO(bradfitz): support?
st , err := interfaces . GetState ( )
if err != nil {
http . Error ( w , "failed to get interfaces state" , http . StatusInternalServerError )
return
}
var res struct {
SentTo [ ] string
Errors [ ] string
}
for ifName , ips := range st . InterfaceIPs {
for _ , ip := range ips {
if ip . IP ( ) . IsLoopback ( ) || ip . IP ( ) . Is6 ( ) {
continue
}
ipa := ip . IP ( ) . IPAddr ( )
local := & net . UDPAddr {
IP : ipa . IP ,
Port : 0 ,
}
remote := & net . UDPAddr {
IP : net . IPv4bcast ,
Port : 0 ,
}
if err := wol . Wake ( mac , password , local , remote ) ; err != nil {
res . Errors = append ( res . Errors , err . Error ( ) )
} else {
res . SentTo = append ( res . SentTo , ifName )
}
break // one per interface is enough
}
}
sort . Strings ( res . SentTo )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ) . Encode ( res )
}
func ( h * peerAPIHandler ) replyToDNSQueries ( ) bool {
if h . isSelf {
// If the peer is owned by the same user, just allow it