2020-03-27 12:09:54 +00:00
package main
import (
2021-03-07 15:06:53 +00:00
"encoding/json"
2020-04-25 21:53:09 +00:00
"errors"
2020-03-27 12:09:54 +00:00
"fmt"
"io/ioutil"
"log"
2020-04-25 21:53:09 +00:00
"net"
2020-03-27 12:09:54 +00:00
"net/http"
2021-03-05 14:14:45 +00:00
"strings"
2020-03-27 12:09:54 +00:00
"time"
"github.com/miekg/dns"
"github.com/sparrc/go-ping"
2020-03-27 13:57:14 +00:00
"git.nemunai.re/lectures/adlin/libadlin"
2020-03-27 12:09:54 +00:00
)
2021-03-05 14:13:59 +00:00
const (
DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1"
)
2020-04-02 14:06:47 +00:00
var verbose = false
2020-04-02 14:05:37 +00:00
2020-03-27 12:09:54 +00:00
// ICMP
func check_ping ( ip string , cb func ( pkt * ping . Packet ) ) ( err error ) {
var pinger * ping . Pinger
pinger , err = ping . NewPinger ( ip )
if err != nil {
return
}
2021-03-05 14:13:34 +00:00
defer pinger . Stop ( )
2020-03-27 12:09:54 +00:00
pinger . Timeout = time . Second * 5
pinger . Count = 1
pinger . OnRecv = cb
pinger . SetPrivileged ( true )
pinger . Run ( )
return
}
// PORT 53
2020-04-25 21:53:09 +00:00
func get_GLUE ( domain string ) ( aaaa net . IP , err error ) {
client := dns . Client { Net : "tcp" , Timeout : time . Second * 5 }
m := new ( dns . Msg )
m . SetQuestion ( domain , dns . TypeNS )
m . RecursionDesired = false
m . SetEdns0 ( 4096 , true )
var r * dns . Msg
2021-03-05 14:13:59 +00:00
r , _ , err = client . Exchange ( m , "[2a01:e0a:2b:2250::b]:53" )
2020-04-25 21:53:09 +00:00
if err != nil {
return
}
if r == nil {
return nil , errors . New ( "response is nil" )
}
if r . Rcode != dns . RcodeSuccess {
return nil , errors . New ( "failed to get a valid answer" )
}
for _ , extra := range r . Extra {
if t , ok := extra . ( * dns . AAAA ) ; ok {
aaaa = t . AAAA
}
}
return
}
func check_dns ( domain , ip string ) ( aaaa net . IP , err error ) {
2020-03-27 12:09:54 +00:00
client := dns . Client { Timeout : time . Second * 5 }
m := new ( dns . Msg )
2020-04-25 21:53:09 +00:00
m . SetQuestion ( domain , dns . TypeAAAA )
var r * dns . Msg
r , _ , err = client . Exchange ( m , fmt . Sprintf ( "[%s]:53" , ip ) )
if err != nil {
return
}
if r == nil {
err = errors . New ( "response is nil" )
}
if r . Rcode != dns . RcodeSuccess {
err = errors . New ( "failed to get a valid answer" )
}
2021-03-04 00:04:58 +00:00
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . AAAA ) ; ok {
aaaa = t . AAAA
}
2020-04-25 21:53:09 +00:00
}
2020-03-27 12:09:54 +00:00
return
}
// PORT 80
2021-03-05 14:14:45 +00:00
func check_http ( ip , dn string ) ( err error ) {
2020-04-25 21:53:09 +00:00
client := & http . Client {
CheckRedirect : func ( req * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
2021-03-05 14:14:45 +00:00
req , errr := http . NewRequest ( "GET" , fmt . Sprintf ( "http://[%s]/" , ip ) , nil )
if errr != nil {
return errr
}
if dn != "" {
req . Header . Add ( "Host" , strings . TrimSuffix ( dn , "." ) )
}
2020-03-27 12:09:54 +00:00
var resp * http . Response
2021-03-05 14:14:45 +00:00
resp , err = client . Do ( req )
2020-03-27 12:09:54 +00:00
if err != nil {
return
}
defer resp . Body . Close ( )
2021-03-04 01:01:26 +00:00
2021-03-05 14:14:45 +00:00
if dn != "" && resp . StatusCode >= 400 {
2021-03-04 01:01:26 +00:00
return fmt . Errorf ( "Bad status, got: %d (%s)" , resp . StatusCode , resp . Status )
}
2020-03-27 12:09:54 +00:00
_ , err = ioutil . ReadAll ( resp . Body )
return
}
// PORT 443
func check_https ( domain , ip string ) ( err error ) {
var resp * http . Response
2021-03-05 14:14:45 +00:00
resp , err = http . Get ( fmt . Sprintf ( "https://%s/" , strings . TrimSuffix ( domain , "." ) ) )
2020-03-27 12:09:54 +00:00
if err != nil {
return
}
defer resp . Body . Close ( )
2021-03-04 01:01:26 +00:00
2021-03-05 14:14:45 +00:00
if resp . StatusCode >= 300 && resp . StatusCode < 400 {
loc := resp . Header . Get ( "Location" )
if loc != "" && strings . HasSuffix ( dns . Fqdn ( loc ) , domain ) {
if dns . Fqdn ( loc ) == domain {
return fmt . Errorf ( "Redirection loop %s redirect to %s" , domain , loc )
} else if err = check_https ( dns . Fqdn ( loc ) , ip ) ; err != nil {
return fmt . Errorf ( "Error after following redirection to %s: %w" , loc , err )
} else {
return
}
}
}
2021-03-04 01:01:26 +00:00
if resp . StatusCode >= 300 {
return fmt . Errorf ( "Bad status, got: %d (%s)" , resp . StatusCode , resp . Status )
}
2020-03-27 12:09:54 +00:00
_ , err = ioutil . ReadAll ( resp . Body )
return
}
2021-03-07 15:06:53 +00:00
// MATRIX
type matrix_result struct {
WellKnownResult struct {
Result string ` json:"result" `
}
DNSResult struct {
SRVError * struct {
Message string
}
}
ConnectionReports map [ string ] struct {
Errors [ ] string
}
ConnectionErrors map [ string ] struct {
Message string
}
Version struct {
Name string ` json:"name" `
Version string ` json:"version" `
}
FederationOK bool
}
func check_matrix ( domain string ) ( err error ) {
var resp * http . Response
resp , err = http . Get ( fmt . Sprintf ( "https://federation-tester.adlin.nemunai.re/api/report?server_name=%s" , strings . TrimSuffix ( domain , "." ) ) )
if err != nil {
return
}
defer resp . Body . Close ( )
if resp . StatusCode >= 300 {
return nil
}
var federationTest matrix_result
if federationTest . FederationOK {
return nil
} else if err = json . NewDecoder ( resp . Body ) . Decode ( & federationTest ) ; err != nil {
log . Printf ( "Error in chech_matrix, when decoding json:" , err . Error ( ) )
return nil
} else if federationTest . DNSResult . SRVError != nil && federationTest . WellKnownResult . Result != "" {
return fmt . Errorf ( "%s OR %s" , federationTest . DNSResult . SRVError . Message , federationTest . WellKnownResult . Result )
} else if len ( federationTest . ConnectionErrors ) > 0 {
var msg strings . Builder
for srv , cerr := range federationTest . ConnectionErrors {
if msg . Len ( ) > 0 {
msg . WriteString ( "; " )
}
msg . WriteString ( srv )
msg . WriteString ( ": " )
msg . WriteString ( cerr . Message )
}
return fmt . Errorf ( "Connection errors: %s" , msg . String ( ) )
} else {
return fmt . Errorf ( "An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s" , domain )
}
}
2020-03-27 12:09:54 +00:00
// Main
2021-03-07 11:39:38 +00:00
func minTunnelVersion ( std * adlin . Student , suffixip int ) ( int , error ) {
2020-04-09 10:27:25 +00:00
tunnels , err := std . GetTunnelTokens ( )
if err != nil {
return 0 , err
}
var minversion int = 2147483647
for _ , tunnel := range tunnels {
if tunnel . Version == 0 {
continue
}
2021-03-04 00:32:09 +00:00
if tunnel . Dump != nil && tunnel . Version < minversion && suffixip == tunnel . SuffixIP {
2020-04-09 10:27:25 +00:00
minversion = tunnel . Version
}
}
return minversion , nil
}
2020-03-27 12:09:54 +00:00
func studentsChecker ( ) {
2020-03-27 13:57:14 +00:00
students , err := adlin . GetStudents ( )
2020-03-27 12:09:54 +00:00
if err != nil {
log . Println ( "Unable to check students:" , err )
return
}
2021-03-07 15:06:53 +00:00
check_matrix_for := ( time . Now ( ) . Second ( ) / 30 ) * 5 + time . Now ( ) . Minute ( ) % 5
log . Printf ( "Checking students... (std_matrix%%10=%d)\n" , check_matrix_for )
2020-03-27 12:09:54 +00:00
2021-03-07 15:06:53 +00:00
for istd , s := range students {
2020-03-27 12:09:54 +00:00
time . Sleep ( 250 * time . Millisecond )
// Check ping
std := s
2021-03-04 00:32:09 +00:00
tuns , err := std . GetActivesTunnels ( )
if err != nil {
continue
}
2020-03-27 12:09:54 +00:00
2021-03-04 00:32:09 +00:00
for _ , tun := range tuns {
stdIP := tun . GetStudentIP ( )
go check_ping ( stdIP , func ( pkt * ping . Packet ) {
tunnel_version , err := minTunnelVersion ( std , tun . SuffixIP )
2020-04-02 14:06:47 +00:00
if verbose {
2021-03-04 00:32:09 +00:00
log . Printf ( "%s PONG (on %x); version=%d (%v)\n" , std . Login , tun . SuffixIP , tunnel_version , err )
2020-03-27 12:09:54 +00:00
}
2021-03-04 00:32:09 +00:00
std . OnPong ( true )
2020-03-27 12:09:54 +00:00
2021-03-04 00:32:09 +00:00
if tunnel_version == 2147483647 || tunnel_version == 0 {
log . Printf ( "%s unknown tunnel version: %d skipping tests (%v)" , std . Login , tunnel_version , err )
return
2020-04-02 14:06:47 +00:00
}
2021-03-04 00:32:09 +00:00
dnsIP := stdIP
2021-03-07 11:42:05 +00:00
var glueErr error
2021-03-04 00:32:09 +00:00
// Is GLUE defined?
if glueIP , err := get_GLUE ( std . MyDelegatedDomain ( ) ) ; glueIP != nil {
dnsIP = glueIP . String ( )
if verbose {
log . Printf ( "%s has defined GLUE: %s\n" , std . Login , dnsIP )
2020-03-27 12:09:54 +00:00
}
2021-03-04 00:32:09 +00:00
} else if err != nil {
log . Printf ( "%s and GLUE: %s\n" , std . Login , err )
2021-03-07 11:42:05 +00:00
glueErr = err
2020-03-27 12:09:54 +00:00
}
2020-04-25 21:53:09 +00:00
2021-03-04 00:32:09 +00:00
// Check DNS
if addr , err := check_dns ( std . MyDelegatedDomain ( ) , dnsIP ) ; err == nil {
if addr == nil {
2021-03-07 11:42:05 +00:00
dnsAt := " at " + dnsIP
if glueErr != nil {
dnsAt = " + there is a problem with the GLUE record: " + glueErr . Error ( )
2021-03-04 00:32:09 +00:00
}
2021-03-07 11:42:05 +00:00
if errreg := std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 3 , fmt . Errorf ( "%s: empty response from the server%s" , std . MyDelegatedDomain ( ) , dnsAt ) ) ; errreg != nil {
log . Printf ( "Unable to register challenge error for %s: %s\n" , std . Login , errreg )
2021-03-05 14:14:45 +00:00
}
} else {
2021-03-04 00:32:09 +00:00
if verbose {
2021-03-07 11:42:05 +00:00
log . Printf ( "%s just unlocked DNS challenge\n" , std . Login )
2021-03-04 00:32:09 +00:00
}
2021-03-07 11:42:05 +00:00
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 3 , addr . String ( ) ) ; err != nil {
2021-03-05 14:14:45 +00:00
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
2021-03-04 00:32:09 +00:00
}
2021-03-07 11:42:05 +00:00
// Check HTTP with DNS
if glueErr != nil {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 4 , fmt . Errorf ( "Unable to perform the test due to GLUE problem: %w" , err ) )
} else if err := check_http ( addr . String ( ) , std . MyDelegatedDomain ( ) ) ; err == nil {
if verbose {
log . Printf ( "%s just unlocked HTTP challenge\n" , std . Login )
}
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 4 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
}
} else {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 4 , err )
if verbose {
log . Printf ( "%s and HTTP (with DNS ip=%s): %s\n" , std . Login , addr . String ( ) , err )
}
}
// Check HTTPs with DNS
if glueErr != nil {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 5 , fmt . Errorf ( "Unable to perform the test due to GLUE problem: %w" , err ) )
} else if err := check_https ( std . MyDelegatedDomain ( ) , addr . String ( ) ) ; err == nil {
if verbose {
log . Printf ( "%s just unlocked HTTPS challenge\n" , std . Login )
}
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 5 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
}
} else {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 5 , err )
if verbose {
log . Printf ( "%s and HTTPS (with DNS ip=%s): %s\n" , std . Login , addr . String ( ) , err )
}
2021-03-05 14:14:45 +00:00
}
2021-03-07 15:06:53 +00:00
// Check Matrix (only if GLUE Ok and)
if glueErr == nil && istd % 10 == check_matrix_for {
if err := check_matrix ( std . MyDelegatedDomain ( ) ) ; err == nil {
if verbose {
log . Printf ( "%s just unlocked Matrix challenge\n" , std . Login )
}
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 6 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
}
} else {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 6 , err )
if verbose {
log . Printf ( "%s and Matrix: %s\n" , std . Login , err )
}
}
}
2021-03-05 14:14:45 +00:00
}
} else {
if errreg := std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 3 , err ) ; errreg != nil {
log . Printf ( "Unable to register challenge error for %s: %s\n" , std . Login , errreg )
}
if verbose {
log . Printf ( "%s and DNS: %s\n" , std . Login , err )
}
}
// Check HTTP without DNS
if err := check_http ( stdIP , "" ) ; err == nil {
if verbose {
log . Printf ( "%s just unlocked HTTP IP (without DNS) challenge\n" , std . Login )
}
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 0 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
2020-04-25 21:53:09 +00:00
}
2021-03-04 00:32:09 +00:00
} else {
2021-03-05 14:14:45 +00:00
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 0 , err )
if verbose {
log . Printf ( "%s and HTTP IP (without DNS): %s\n" , std . Login , err )
}
}
// Check DNS for association
if addr , err := check_dns ( std . MyAssociatedDomain ( ) , DEFAULT_RESOLVER ) ; err == nil {
// Check HTTP on delegated domain
if err := check_http ( addr . String ( ) , std . MyAssociatedDomain ( ) ) ; err == nil {
2021-03-04 00:32:09 +00:00
if verbose {
2021-03-05 14:14:45 +00:00
log . Printf ( "%s just unlocked HTTP (without DNS) challenge\n" , std . Login )
2021-03-04 00:32:09 +00:00
}
2021-03-05 14:14:45 +00:00
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 1 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
}
} else {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 1 , err )
if verbose {
log . Printf ( "%s and HTTP (without DNS): %s\n" , std . Login , err )
2020-04-25 21:53:09 +00:00
}
}
2021-03-04 00:32:09 +00:00
// Check HTTPs without DNS
if err := check_https ( std . MyAssociatedDomain ( ) , stdIP ) ; err == nil {
if verbose {
log . Printf ( "%s just unlocked HTTPS challenge\n" , std . Login )
2020-04-25 21:53:09 +00:00
}
2021-03-05 14:14:45 +00:00
if _ , err := std . UnlockChallenge ( 100 * ( tunnel_version - 1 ) + 2 , "" ) ; err != nil {
log . Printf ( "Unable to register challenge for %s: %s\n" , std . Login , err . Error ( ) )
}
} else {
std . RegisterChallengeError ( 100 * ( tunnel_version - 1 ) + 2 , err )
if verbose {
log . Printf ( "%s and HTTPS (without DNS): %s\n" , std . Login , err )
2021-03-04 00:32:09 +00:00
}
2020-04-25 21:53:09 +00:00
}
}
2020-03-27 12:09:54 +00:00
2021-03-04 00:32:09 +00:00
return
} )
}
2020-03-27 12:09:54 +00:00
}
}