Optimized UX and code structure

+ Added automatic self-sign certificate sniffing
+ Moved all constant into def.go
+ Added auto restart on port change when proxy server is running
+ Optimized slow search geoIP resolver by introducing new cache mechanism
+ Updated default incoming port to HTTPS instead of HTTP
This commit is contained in:
Toby Chui
2024-11-24 11:38:01 +08:00
parent 0af8c67346
commit 015889851a
16 changed files with 249 additions and 115 deletions

View File

@@ -210,8 +210,8 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
}
session.Values["authenticated"] = false
session.Values["username"] = nil
session.Save(r, w)
return nil
session.Options.MaxAge = -1
return session.Save(r, w)
}
// Get the current session username from request
@@ -339,6 +339,7 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
if err != nil {
return false
}
// Check if user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
return false

View File

@@ -1,7 +1,6 @@
package dynamicproxy
import (
"log"
"net/http"
"os"
"path/filepath"
@@ -16,7 +15,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
if err != nil {
//Unable to load access rule. Target rule not found?
log.Println("[Proxy] Unable to load access rule: " + ruleID)
h.Parent.Option.Logger.PrintAndLog("proxy-access", "Unable to load access rule: "+ruleID, err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Internal Server Error"))
return true

View File

@@ -10,8 +10,14 @@ package domainsniff
*/
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
"time"
"imuslab.com/zoraxy/mod/utils"
)
// Check if the domain is reachable and return err if not reachable
@@ -27,30 +33,114 @@ func DomainReachableWithError(domain string) error {
}
// Check if a domain have TLS but it is self-signed or expired
func DomainIsSelfSigned(domain string) (bool, error) {
// Return false if sniff error
func DomainIsSelfSigned(domain string) bool {
//Extract the domain from URl in case the user input the full URL
host, port, err := net.SplitHostPort(domain)
if err != nil {
host = domain
} else {
domain = host + ":" + port
}
if !strings.Contains(domain, ":") {
domain = domain + ":443"
}
//Get the certificate
conn, err := net.Dial("tcp", domain)
if err != nil {
return false, err
return false
}
defer conn.Close()
//Connect with TLS using secure verify
tlsConn := tls.Client(conn, nil)
err = tlsConn.Handshake()
if err == nil {
//This is a valid certificate
fmt.Println()
return false
}
//Connect with TLS using insecure skip verify
config := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn := tls.Client(conn, config)
tlsConn = tls.Client(conn, config)
err = tlsConn.Handshake()
if err != nil {
return false, err
}
//Check if the certificate is self-signed
cert := tlsConn.ConnectionState().PeerCertificates[0]
return cert.Issuer.CommonName == cert.Subject.CommonName, nil
//If the handshake is successful, this is a self-signed certificate
return err == nil
}
// Check if domain reachable
func DomainReachable(domain string) bool {
return DomainReachableWithError(domain) == nil
}
// Check if domain is served by a web server using HTTPS
func DomainUsesTLS(targetURL string) bool {
//Check if the site support https
httpsUrl := fmt.Sprintf("https://%s", targetURL)
httpUrl := fmt.Sprintf("http://%s", targetURL)
client := http.Client{Timeout: 5 * time.Second}
resp, err := client.Head(httpsUrl)
if err == nil && resp.StatusCode == http.StatusOK {
return true
}
resp, err = client.Head(httpUrl)
if err == nil && resp.StatusCode == http.StatusOK {
return false
}
//If the site is not reachable, return false
return false
}
/*
Request Handlers
*/
//Check if site support TLS
//Pass in ?selfsignchk=true to also check for self-signed certificate
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
targetURL, err := utils.PostPara(r, "url")
if err != nil {
utils.SendErrorResponse(w, "invalid url given")
return
}
//If the selfsign flag is set, also chec for self-signed certificate
_, err = utils.PostBool(r, "selfsignchk")
if err == nil {
//Return the https and selfsign status
type result struct {
Protocol string `json:"protocol"`
SelfSign bool `json:"selfsign"`
}
scanResult := result{Protocol: "http", SelfSign: false}
if DomainUsesTLS(targetURL) {
scanResult.Protocol = "https"
if DomainIsSelfSigned(targetURL) {
scanResult.SelfSign = true
}
}
js, _ := json.Marshal(scanResult)
utils.SendJSONResponse(w, string(js))
return
}
if DomainUsesTLS(targetURL) {
js, _ := json.Marshal("https")
utils.SendJSONResponse(w, string(js))
return
} else {
js, _ := json.Marshal("http")
utils.SendJSONResponse(w, string(js))
return
}
}

View File

@@ -291,7 +291,7 @@ func (router *Router) Restart() error {
return err
}
time.Sleep(300 * time.Millisecond)
time.Sleep(800 * time.Millisecond)
// Start the server
err = router.StartProxyService()
if err != nil {

View File

@@ -3,6 +3,7 @@ package geodb
import (
_ "embed"
"net/http"
"time"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/netutils"
@@ -15,17 +16,22 @@ var geoipv4 []byte //Geodb dataset for ipv4
var geoipv6 []byte //Geodb dataset for ipv6
type Store struct {
geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie
geotrieIpv6 *trie
sysdb *database.Database
option *StoreOptions
geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie
geotrieIpv6 *trie
sysdb *database.Database
slowLookupCacheIpv4 map[string]string //Cache for slow lookup
slowLookupCacheIpv6 map[string]string //Cache for slow lookup
cacheClearTicker *time.Ticker //Ticker for clearing cache
cacheClearTickerStopChan chan bool //Stop channel for cache clear ticker
option *StoreOptions
}
type StoreOptions struct {
AllowSlowIpv4LookUp bool
AllowSloeIpv6Lookup bool
AllowSlowIpv4LookUp bool
AllowSlowIpv6Lookup bool
SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval
}
type CountryInfo struct {
@@ -50,18 +56,44 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
}
var ipv6Trie *trie
if !option.AllowSloeIpv6Lookup {
if !option.AllowSlowIpv6Lookup {
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
}
return &Store{
geodb: parsedGeoData,
geotrie: ipv4Trie,
geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: ipv6Trie,
sysdb: sysdb,
option: option,
}, nil
if option.SlowLookupCacheClearInterval == 0 {
option.SlowLookupCacheClearInterval = 15 * time.Minute
}
//Create a new store
thisGeoDBStore := &Store{
geodb: parsedGeoData,
geotrie: ipv4Trie,
geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: ipv6Trie,
sysdb: sysdb,
slowLookupCacheIpv4: make(map[string]string),
slowLookupCacheIpv6: make(map[string]string),
cacheClearTicker: time.NewTicker(option.SlowLookupCacheClearInterval),
cacheClearTickerStopChan: make(chan bool),
option: option,
}
//Start cache clear ticker
if option.AllowSlowIpv4LookUp || option.AllowSlowIpv6Lookup {
go func(store *Store) {
for {
select {
case <-store.cacheClearTickerStopChan:
return
case <-thisGeoDBStore.cacheClearTicker.C:
thisGeoDBStore.slowLookupCacheIpv4 = make(map[string]string)
thisGeoDBStore.slowLookupCacheIpv6 = make(map[string]string)
}
}
}(thisGeoDBStore)
}
return thisGeoDBStore, nil
}
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
@@ -73,8 +105,12 @@ func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error)
}
// Close the store
func (s *Store) Close() {
if s.option.AllowSlowIpv4LookUp || s.option.AllowSlowIpv6Lookup {
//Stop cache clear ticker
s.cacheClearTickerStopChan <- true
}
}
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {

View File

@@ -44,6 +44,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
false,
true,
0,
})
if err != nil {
t.Errorf("error creating store: %v", err)

View File

@@ -56,6 +56,12 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv4[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodb {
startIp := ipRange[0]
endIp := ipRange[1]
@@ -63,6 +69,8 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
if inRange {
//Add to cache
s.slowLookupCacheIpv4[ipAddr] = cc
return cc
}
}
@@ -73,6 +81,12 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
if isReservedIP(ipAddr) {
return ""
}
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv6[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodbIpv6 {
startIp := ipRange[0]
endIp := ipRange[1]
@@ -80,6 +94,8 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
if inRange {
//Add to cache
s.slowLookupCacheIpv6[ipAddr] = cc
return cc
}
}