-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Birkhoff Lee <git@birkhoff.me>
- Loading branch information
1 parent
be48367
commit 92a79cb
Showing
9 changed files
with
481 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package checks | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
"regexp" | ||
"net" | ||
) | ||
|
||
type ClashAPIRootResponse struct { | ||
Hello string `json:"hello,omitempty"` // clash | ||
Message string `json:"message,omitempty"` // Unauthorized | ||
} | ||
|
||
func lookupFirstIp4Address(domain string) (string, error) { | ||
// lookup first ipv4 address of a domain | ||
addrs, err := net.LookupHost(domain) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
for _, addr := range addrs { | ||
if ip := net.ParseIP(addr); ip.To4() != nil { | ||
return addr, nil | ||
} | ||
} | ||
|
||
return "", fmt.Errorf("no ipv4 address found") | ||
} | ||
|
||
func IsRandomDomainResolvedToClashAddressSpace() (bool) { | ||
addr, err := lookupFirstIp4Address("this.domain.does.not.exist.") | ||
|
||
if err != nil { | ||
return false | ||
} | ||
|
||
return strings.HasPrefix(addr, "198.18.") | ||
} | ||
|
||
func GetClashApiBaseUrl() (string, error) { | ||
// Get Clash endpoint from either default gateway or system proxy | ||
addr := "" | ||
|
||
if proxy, err := getSystemDefaultProxy(); err == nil { | ||
// get ip address from proxy string | ||
re := regexp.MustCompile(`(?m)\/\/(.*?)(:\d+)?$`) | ||
match := re.FindStringSubmatch(proxy) | ||
|
||
if len(match) < 2 { | ||
return "", fmt.Errorf("invalid proxy address") | ||
} | ||
|
||
addr = match[1] | ||
} else if gateway, _, err := GetDefaultRoute(); err == nil { | ||
addr = gateway.String() | ||
} | ||
|
||
// Construct Clash API URL | ||
url := fmt.Sprintf("http://%s:9090", addr) | ||
|
||
// Check if Clash API is reachable | ||
resp, err := http.Get(url) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// Check response | ||
var clashAPIRootResponse ClashAPIRootResponse | ||
decoder := json.NewDecoder(resp.Body) | ||
err = decoder.Decode(&clashAPIRootResponse) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if clashAPIRootResponse.Hello == "clash" { | ||
return url, nil | ||
} | ||
|
||
if clashAPIRootResponse.Message == "Unauthorized" { | ||
return url, nil | ||
} | ||
|
||
return "", fmt.Errorf("clash api not detected") | ||
} | ||
|
||
// func GetClashApiVersion() (string, error) { | ||
// url, err := getClashApiBaseUrl() | ||
|
||
// if err != nil { | ||
// return "", err | ||
// } | ||
|
||
// resp, err := http.Get(fmt.Sprintf("%s/version", url)) | ||
|
||
// if err != nil { | ||
// return "", err | ||
// } | ||
|
||
// defer resp.Body.Close() | ||
|
||
// // {"premium":true,"version":"2023.08.17-11-g0f901d0"} | ||
// bodyBytes, err := io.ReadAll(resp.Body) | ||
|
||
// if err != nil { | ||
// return "", err | ||
// } | ||
|
||
// body := string(bodyBytes) | ||
// re := regexp.MustCompile(`"version":"(.*?)"`) | ||
// match := re.FindStringSubmatch(body) | ||
|
||
// if len(match) < 2 { | ||
// return "", fmt.Errorf("clash version not found in api response") | ||
// } | ||
|
||
// return match[1], nil | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package checks | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/go-ping/ping" | ||
) | ||
|
||
func CheckReachabilityWithICMP(host string) (string, error) { | ||
pinger, err := ping.NewPinger(host) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
pinger.Count = 3 | ||
pinger.Debug = true | ||
pinger.Interval = 200 * time.Millisecond | ||
pinger.Timeout = 2 * time.Second | ||
|
||
err = pinger.Run() | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
stats := pinger.Statistics() | ||
|
||
statsString := fmt.Sprintf( | ||
"%s/%s/%s", | ||
stats.MinRtt.Round(time.Millisecond), | ||
stats.AvgRtt.Round(time.Millisecond), | ||
stats.MaxRtt.Round(time.Millisecond), | ||
) | ||
|
||
return statsString, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package checks | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"regexp" | ||
"time" | ||
|
||
"github.com/miekg/dns" | ||
) | ||
|
||
func CheckNameserverAvailability(s string) error { | ||
c := new(dns.Client) | ||
c.Dialer = &net.Dialer{ | ||
Timeout: 3 * time.Second, | ||
} | ||
m := new(dns.Msg) | ||
m.SetQuestion("apple.com.", dns.TypeA) | ||
_, _, err := c.Exchange(m, s) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// fmt.Printf("%v %v", in, rtt) | ||
return nil | ||
} | ||
|
||
func CheckCaptivePortal() error { | ||
resp, err := http.Get("http://connectivitycheck.gstatic.com/generate_204") | ||
|
||
if err != nil || resp.StatusCode != 204 { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func GetCloudflareEdgeTrace() (string, error) { | ||
resp, err := http.Get("https://www.cloudflare.com/cdn-cgi/trace") | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
defer resp.Body.Close() | ||
|
||
bodyBytes, err := io.ReadAll(resp.Body) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
body := string(bodyBytes) | ||
re := regexp.MustCompile(`loc=(.*?)\n`) | ||
match := re.FindStringSubmatch(body) | ||
|
||
if len(match) < 2 { | ||
return "", fmt.Errorf("could not determine edge pop") | ||
} | ||
|
||
return match[1], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package checks | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"os" | ||
"regexp" | ||
|
||
"github.com/jackpal/gateway" | ||
) | ||
|
||
func getSystemDefaultProxy() (string, error) { | ||
http_proxy := os.Getenv("http_proxy") | ||
https_proxy := os.Getenv("https_proxy") | ||
all_proxy := os.Getenv("all_proxy") | ||
|
||
if all_proxy != "" { | ||
return all_proxy, nil | ||
} | ||
|
||
if http_proxy != "" { | ||
return http_proxy, nil | ||
} | ||
|
||
if https_proxy != "" { | ||
return https_proxy, nil | ||
} | ||
|
||
return "", fmt.Errorf("no proxy detected") | ||
} | ||
|
||
func GetDefaultRoute() (net.IP, string, error) { | ||
// check default gateway | ||
gw, err := gateway.DiscoverGateway() | ||
|
||
if err != nil { | ||
return nil, "", fmt.Errorf("error reading default route: %s", err) | ||
} | ||
|
||
stats, err := CheckReachabilityWithICMP(gw.String()) | ||
|
||
if err != nil { | ||
return gw, stats, fmt.Errorf("default route is unreachable: %s", err) | ||
} | ||
|
||
return gw, stats, nil | ||
} | ||
|
||
func GetDefaultNameserver() (string, error) { | ||
// get default ns from /etc/resolv.conf | ||
byteString, err := os.ReadFile("/etc/resolv.conf") | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
s := string(byteString) | ||
|
||
re := regexp.MustCompile(`(?m)^nameserver( *|\t*)(.*?)$`) | ||
match := re.FindStringSubmatch(s) | ||
|
||
if len(match) < 2 { | ||
return "", fmt.Errorf("match is less than 2") | ||
} | ||
|
||
return match[2], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package checks | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"tailscale.com/client/tailscale" | ||
"tailscale.com/types/key" | ||
"tailscale.com/ipn/ipnstate" | ||
) | ||
|
||
// func findTailscalePeerByStableNodeID(peers map[key.NodePublic]*ipnstate.PeerStatus, id tailcfg.StableNodeID) *ipnstate.PeerStatus { | ||
// for _, p := range peers { | ||
// if p.ID == id { | ||
// return p | ||
// } | ||
// } | ||
|
||
// return nil | ||
// } | ||
|
||
func FindActiveExitNodeFromPeersMap(peers map[key.NodePublic]*ipnstate.PeerStatus) *ipnstate.PeerStatus { | ||
for _, p := range peers { | ||
if p.ExitNode { | ||
return p | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func GetTailscaleStatus() string { | ||
// check tailscale status | ||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) | ||
ts, err := tailscale.Status(ctx) // https://pkg.go.dev/tailscale.com@v1.40.0/ipn/ipnstate#Status | ||
defer cancel() | ||
|
||
if err != nil { | ||
return fmt.Sprintf("[!] Could not determine tailscaled status: %s", err) | ||
} | ||
|
||
// https://github.com/tailscale/tailscale/blob/9bdaece3d7c3c83aae01e0736ba54e833f4aea51/cmd/tailscale/cli/status.go#L162-L196 | ||
|
||
if !ts.Self.Online { | ||
return fmt.Sprintf("[~] We're offline on tsnet: BackendState=%s", ts.BackendState) | ||
} | ||
|
||
exitNodeStatus := FindActiveExitNodeFromPeersMap(ts.Peer) | ||
|
||
if exitNodeStatus == nil { | ||
return "[+] We're online on tsnet; not using any exit node" | ||
} | ||
|
||
if exitNodeStatus.Active { | ||
if exitNodeStatus.Relay != "" && exitNodeStatus.CurAddr == "" { | ||
return fmt.Sprintf("[~] We're online on tsnet; exit node \"%s\" via relay %s", exitNodeStatus.HostName, exitNodeStatus.Relay) | ||
} | ||
|
||
if exitNodeStatus.CurAddr != "" { | ||
return fmt.Sprintf("[+] We're online on tsnet; exit node \"%s\" via %s", exitNodeStatus.HostName, exitNodeStatus.CurAddr) | ||
} | ||
|
||
return fmt.Sprintf("[!] We're online on tsnet; exit node \"%s\" (unknown connection)", exitNodeStatus.HostName) | ||
} | ||
|
||
return fmt.Sprintf("[+] We're online on tsnet; exit node \"%s\" is inactive", exitNodeStatus.HostName) | ||
} |
Oops, something went wrong.