diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fe97206
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+# 2023/12/26
+- [x] feat: upgrade to surge like config
+- [x] feat: support windows 10
+- [x] tech: merge proxy code
+- [x] feat: reload rule by manager: curl http://127.0.0.1:9200/reload
+
+# plan
+- [ ] feat: default hijack dns query
+- [ ] feat: show process name of network
+- [ ] bug: traffic will be endless loop if proxy's ip use proxy by rule
+- [ ] feat: support ss protocol
+- [ ] feat: support IPv6
+- [ ] feat: update GEOIP database
\ No newline at end of file
diff --git a/README.md b/README.md
index 4306a05..c6f9e28 100644
--- a/README.md
+++ b/README.md
@@ -26,16 +26,9 @@ The default web status port is 9200 , just visit http://localhost:9200/ to check
## Documents
-* [how to use with Raspberry Pi (在树莓派上使用kone)](./misc/docs/how-to-use-with-raspberry-pi.md)
-* [how to use kone in an ENT network (企业网中如何使用kone)](./misc/docs/kone-in-ent-network.md)
-* [how to make gotunnel & kone work together(gotunnel和kone的珠联璧合)](./misc/docs/gotunnel-kone-work-together.md)
+* [how to use with Raspberry Pi](./misc/docs/how-to-use-with-raspberry-pi.md)
+* [how to use kone in an ENT network](./misc/docs/kone-in-ent-network.md)
+* [how to make gotunnel & kone work together](./misc/docs/gotunnel-kone-work-together.md)
## License
The MIT License (MIT) Copyright (c) 2016 xjdrew
-
-## todo
-- [x] upgrade to surge like config
-- [x] support windows 10
-- [ ] merge proxy code
-- [ ] default hijack dns query
-- [ ] show process name of network
diff --git a/cmd/kone/main.go b/cmd/kone/main.go
index b5b17ae..9c8cf29 100644
--- a/cmd/kone/main.go
+++ b/cmd/kone/main.go
@@ -18,18 +18,11 @@ var VERSION = "0.3-dev"
var logger = logging.MustGetLogger("kone")
-func InitLogger(debug bool) {
- format := logging.MustStringFormatter(
+func init() {
+ logging.SetFormatter(logging.MustStringFormatter(
`%{color}%{time:06-01-02 15:04:05.000} %{level:.4s} @%{shortfile}%{color:reset} %{message}`,
- )
- logging.SetFormatter(format)
+ ))
logging.SetBackend(logging.NewLogBackend(os.Stdout, "", 0))
-
- if debug {
- logging.SetLevel(logging.DEBUG, "kone")
- } else {
- logging.SetLevel(logging.INFO, "kone")
- }
}
func main() {
@@ -43,7 +36,11 @@ func main() {
os.Exit(1)
}
- InitLogger(*debug)
+ if *debug {
+ logging.SetLevel(logging.DEBUG, "kone")
+ } else {
+ logging.SetLevel(logging.INFO, "kone")
+ }
configFile := *config
if configFile == "" {
diff --git a/config.go b/config.go
index 462aca3..f2c0348 100644
--- a/config.go
+++ b/config.go
@@ -13,6 +13,10 @@ import (
"gopkg.in/ini.v1"
)
+func init() {
+ ini.PrettyFormat = true
+}
+
const (
HTTP_PROXY = "http_proxy"
HTTPS_PROXY = "https_proxy"
@@ -48,6 +52,9 @@ type RuleConfig struct {
}
type KoneConfig struct {
+ source interface{} // config source: file name or raw ini data
+ inif *ini.File // parsed ini file
+
General GeneralConfig
Core CoreConfig
Proxy map[string]string
@@ -87,8 +94,9 @@ func (cfg *KoneConfig) check() (err error) {
return nil
}
-func ParseConfig(filename interface{}) (*KoneConfig, error) {
+func ParseConfig(source interface{}) (*KoneConfig, error) {
cfg := new(KoneConfig)
+ cfg.source = source
// set default value
cfg.Core.Network = "10.192.0.1/16"
@@ -107,11 +115,12 @@ func ParseConfig(filename interface{}) (*KoneConfig, error) {
cfg.Core.DnsWriteTimeout = DnsDefaultWriteTimeout
// decode config value
- f, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true, KeyValueDelimiters: "="}, filename)
+ f, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true, KeyValueDelimiters: "="}, source)
if err != nil {
logger.Errorf("%v", err)
return nil, err
}
+ cfg.inif = f
err = f.MapTo(cfg)
if err != nil {
diff --git a/dns_table.go b/dns_table.go
index 4694d3f..132032b 100644
--- a/dns_table.go
+++ b/dns_table.go
@@ -156,11 +156,21 @@ func (c *DnsTable) clearExpiredNonProxyDomain(now time.Time) {
for domain, expired := range c.nonProxyDomains {
if expired.Before(now) {
delete(c.nonProxyDomains, domain)
- logger.Debugf("[dns] release non proxy domain: %s", domain)
+ logger.Debugf("[dns] release expired non proxy domain: %s", domain)
}
}
}
+func (c *DnsTable) ClearNonProxyDomain() {
+ c.npdLock.Lock()
+ defer c.npdLock.Unlock()
+ for domain := range c.nonProxyDomains {
+ delete(c.nonProxyDomains, domain)
+ logger.Debugf("[dns] release non proxy domain: %s", domain)
+
+ }
+}
+
func (c *DnsTable) clearExpiredDomain(now time.Time) {
c.recordsLock.Lock()
defer c.recordsLock.Unlock()
@@ -189,6 +199,7 @@ func (c *DnsTable) Serve() error {
tick := time.NewTicker(60 * time.Second)
for now := range tick.C {
c.clearExpiredDomain(now)
+ //TODO: is it necessary?
c.clearExpiredNonProxyDomain(now)
}
return nil
diff --git a/manager.go b/manager.go
index 32059e0..a597b72 100644
--- a/manager.go
+++ b/manager.go
@@ -183,6 +183,24 @@ table th.title {
{{template "footer" .}}
{{end}}
+
+
+{{define "config"}}
+{{template "header" .}}
+
Config
+
+
+
+Source |
+
+
+{{.Source}} |
+
+
+{{template "footer" .}}
+{{end}}
`
// statistical data of every connection
@@ -212,6 +230,7 @@ type TrafficRecord struct {
type Manager struct {
one *One
+ cfg *KoneConfig
startTime time.Time // process start time
listen string
tmpl *template.Template
@@ -255,6 +274,8 @@ func (m *Manager) indexHandle(w io.Writer, r *http.Request) error {
"/website/",
"/proxy/",
"/dns/",
+ "/reload/",
+ "/config/",
},
})
}
@@ -343,6 +364,34 @@ func (m *Manager) dnsHandle(w io.Writer, r *http.Request) error {
})
}
+func (m *Manager) reloadHandle(w io.Writer, r *http.Request) error {
+ logger.Infof("[manager] reload config")
+ newcfg, err := ParseConfig(m.cfg.source)
+ if err != nil {
+ return err
+ }
+
+ err = m.one.Reload(newcfg)
+ if err != nil {
+ return err
+ }
+
+ m.cfg = newcfg
+ w.Write([]byte("reload succeed"))
+ return nil
+}
+
+func (m *Manager) configHandle(w io.Writer, r *http.Request) error {
+ b := bytes.NewBuffer([]byte{})
+ m.cfg.inif.WriteTo(b)
+
+ return m.tmpl.ExecuteTemplate(w, "config", map[string]interface{}{
+ "Title": "Config",
+ "RuleCount": len(m.cfg.Rule),
+ "Source": string(b.Bytes()),
+ })
+}
+
// statistical data api
func (m *Manager) consumeData() {
accumulate := func(s map[string]*TrafficRecord, name string, endpoint string, upload int64, download int64, now time.Time) {
@@ -394,14 +443,16 @@ func (m *Manager) Serve() error {
http.HandleFunc("/website/", handleWrapper(m.websiteHandle))
http.HandleFunc("/proxy/", handleWrapper(m.proxyHandle))
http.HandleFunc("/dns/", handleWrapper(m.dnsHandle))
+ http.HandleFunc("/reload/", handleWrapper(m.reloadHandle))
+ http.HandleFunc("/config/", handleWrapper(m.configHandle))
go m.consumeData()
logger.Infof("[manager] listen on: %s", m.listen)
return http.ListenAndServe(m.listen, nil)
}
-func NewManager(one *One, cfg GeneralConfig) *Manager {
- if cfg.ManagerAddr == "" {
+func NewManager(one *One, cfg *KoneConfig) *Manager {
+ if cfg.General.ManagerAddr == "" {
return nil
}
@@ -441,8 +492,9 @@ func NewManager(one *One, cfg GeneralConfig) *Manager {
return &Manager{
one: one,
+ cfg: cfg,
startTime: time.Now(),
- listen: cfg.ManagerAddr,
+ listen: cfg.General.ManagerAddr,
dataCh: make(chan ConnData),
hosts: make(map[string]*TrafficRecord),
websites: make(map[string]*TrafficRecord),
diff --git a/misc/example/example.ini b/misc/example/example.ini
deleted file mode 100644
index 3699337..0000000
--- a/misc/example/example.ini
+++ /dev/null
@@ -1,239 +0,0 @@
-[general]
-# outbound network interface
-#out = eth0
-
-# virtual network
-# tun name, auto allocate if not set
-# DEFAULT VALUE: ""
-# tun = tun0
-
-# inet addr/mask
-# DEFAULT VALUE: 10.192.0.1/16
-# if a domain matches a pattern and the pattern uses proxy
-# the domain will be hijacked to the virtual network
-network = 10.192.0.1/16
-
-# nat config
-[tcp]
-#listen-port = 82
-#nat-port-start = 10000
-#nat-port-end = 60000
-
-[udp]
-#listen-port = 82
-#nat-port-start = 10000
-#nat-port-end = 60000
-
-[dns]
-# DEFAULT VALUE: 53
-# dns-port = 53
-
-# backend dns
-# DEFAULT VALUE: 114.114.114.114, 223.5.5.5
-# nameserver = 114.114.114.114
-# nameserver = 223.5.5.5
-
-# dns-ttl = 600
-# dns-packet-size = 4096
-# dns-read-timeout = 5
-# dns-write-timeout = 5
-
-# set tun0 as default gate for this ip
-[route]
-# telegram
-v = 91.108.0.0/16
-v = 149.154.0.0/16
-
-# define a proxy named "A"
-[proxy "A"]
-url = http://example.com:3228
-default = yes
-
-# define a proxy named "B"
-# [proxy "B"]
-# url = socks5://example.com:2080
-
-# define a pattern and outbound proxy
-# if don't set proxy, packets will be sent to target directly
-[pattern "direct-website"]
-scheme = DOMAIN-SUFFIX
-v = cn
-v = 126.net
-v = 163.com
-v = appldnld.apple.com
-v = adcdownload.apple.com
-v = alicdn.com
-v = amap.com
-v = bdimg.com
-v = bdstatic.com
-v = cnbeta.com
-v = cnzz.com
-v = douban.com
-v = gtimg.com
-v = hao123.com
-v = haosou.com
-v = icloud-content.com
-v = ifeng.com
-v = iqiyi.com
-v = jd.com
-v = lcdn-registration.apple.com
-v = ls.apple.com
-v = netease.com
-v = phobos.apple.com
-v = qhimg.com
-v = qq.com
-v = sogou.com
-v = sohu.com
-v = soso.com
-v = suning.com
-v = swcdn.apple.com
-v = tmall.com
-v = tudou.com
-v = weibo.com
-v = xunlei.com
-v = youku.com
-v = zhihu.com
-
-[pattern "direct-website-keyword"]
-scheme = DOMAIN-KEYWORD
-v = 360buy
-v = alipay
-v = baidu
-v = qiyi
-v = sohu
-v = taobao
-
-[pattern "proxy-website"]
-proxy = A
-scheme = DOMAIN-SUFFIX
-v = appspot.com
-v = t.co,Proxy
-v = twimg.com
-v = amazonaws.com
-v = android.com
-v = angularjs.org
-v = akamaihd.net
-v = bit.ly
-v = bitbucket.org
-v = blog.com
-v = blogcdn.com
-v = blogger.com
-v = blogsmithmedia.com
-v = box.net
-v = bloomberg.com
-v = chromium.org
-v = cl.ly
-v = cloudfront.net
-v = cloudflare.com
-v = cocoapods.org
-v = crashlytics.com
-v = dribbble.com
-v = dropbox.com
-v = dropboxstatic.com
-v = dropboxusercontent.com
-v = docker.com
-v = duckduckgo.com
-v = digicert.com
-v = dnsimple.com
-v = edgecastcdn.net
-v = engadget.com
-v = eurekavpt.com
-v = fb.me
-v = fbcdn.net
-v = fc2.com
-v = feedburner.com
-v = fabric.io
-v = flickr.com
-v = fastly.net
-v = ggpht.com
-v = github.com
-v = github.io
-v = githubusercontent.com
-v = golang.org
-v = goo.gl
-v = gstatic.com
-v = godaddy.com
-v = gravatar.com
-v = imageshack.us
-v = imgur.com
-v = jshint.com
-v = ift.tt
-v = itunes.com
-v = j.mp
-v = kat.cr
-v = linode.com
-v = linkedin.com
-v = licdn.com
-v = lithium.com
-v = megaupload.com
-v = mobile01.com
-v = modmyi.com
-v = mzstatic.com
-v = nytimes.com
-v = name.com
-v = openvpn.net
-v = openwrt.org
-v = ow.ly
-v = pinboard.in
-v = ssl-images-amazon.com
-v = sstatic.net
-v = stackoverflow.com
-v = staticflickr.com
-v = squarespace.com
-v = symcd.com
-v = symcb.com
-v = symauth.com
-v = ubnt.com
-v = thepiratebay.org
-v = tumblr.com
-v = twitch.tv
-v = wikipedia.com
-v = wikipedia.org
-v = wikimedia.org
-v = wordpress.com
-v = wsj.com
-v = wsj.net
-v = wp.com
-v = vimeo.com
-v = youtu.be
-v = ytimg.com
-
-[pattern "proxy-website-keyword"]
-proxy = A
-scheme = DOMAIN-KEYWORD
-v = google
-v = gmail
-v = facebook
-v = instagram
-v = twitter
-v = youtube
-v = blogspot
-
-[pattern "internal-ip"]
-scheme = IP-CIDR
-v = 10.0.0.0/8
-v = 127.0.0.1/8
-v = 172.16.0.0/16
-v = 192.168.0.0/16
-
-[pattern "direct-country"]
-scheme = IP-COUNTRY
-v = CN
-
-[pattern "proxy-country"]
-proxy = A
-scheme = IP-COUNTRY
-v = US
-v = HK
-
-# rules define the order of checking pattern
-[rule]
-pattern = direct-website
-pattern = direct-website-keyword
-pattern = proxy-website
-pattern = proxy-website-keyword
-pattern = internal-ip
-pattern = direct-country
-# set to a proxy for domaines that don't match any pattern
-# DEFAULT VALUE: ""
-final = A
diff --git a/one.go b/one.go
index cbcbbd2..36489bb 100644
--- a/one.go
+++ b/one.go
@@ -55,6 +55,12 @@ func (one *One) Serve() {
wg.Wait()
}
+func (one *One) Reload(cfg *KoneConfig) error {
+ one.rule = NewRule(cfg.Rule)
+ one.dnsTable.ClearNonProxyDomain()
+ return nil
+}
+
func FromConfig(cfg *KoneConfig) (*One, error) {
ip, subnet, _ := net.ParseCIDR(cfg.Core.Network)
@@ -104,6 +110,6 @@ func FromConfig(cfg *KoneConfig) (*One, error) {
}
// new manager
- one.manager = NewManager(one, cfg.General)
+ one.manager = NewManager(one, cfg)
return one, nil
}
diff --git a/proxy/proxy.go b/proxy/proxy.go
index e8be54b..115afa8 100644
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -9,12 +9,8 @@ import (
"errors"
"net"
"net/url"
-
- "github.com/op/go-logging"
)
-var logger = logging.MustGetLogger("kone")
-
// A Dialer is a means to establish a connection.
type Dialer interface {
// Dial connects to the given address via the proxy.
@@ -29,7 +25,6 @@ var proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error))
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func registerDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) {
- logger.Debugf("register proxy schema %s", scheme)
proxySchemes[scheme] = f
}
diff --git a/tcp_relay.go b/tcp_relay.go
index 98877d9..0f868fd 100644
--- a/tcp_relay.go
+++ b/tcp_relay.go
@@ -34,7 +34,7 @@ func (r *TCPRelay) realRemoteHost(conn net.Conn, connData *ConnData) (addr strin
session := r.nat.getSession(remotePort)
if session == nil {
- logger.Errorf("[tcp] %s > %s no session", conn.LocalAddr(), remoteAddr)
+ logger.Errorf("[tcp relay] %s > %s no session", conn.LocalAddr(), remoteAddr)
return
}
@@ -45,7 +45,7 @@ func (r *TCPRelay) realRemoteHost(conn net.Conn, connData *ConnData) (addr strin
if one.dnsTable.IsLocalIP(dstIP) { // for dns hijacked traffic
record := one.dnsTable.GetByIP(dstIP)
if record == nil {
- logger.Debugf("[tcp] %s:%d > %s:%d dns expired", session.srcIP, session.srcPort, dstIP, session.dstPort)
+ logger.Debugf("[tcp relay] %s:%d > %s:%d dns expired", session.srcIP, session.srcPort, dstIP, session.dstPort)
return
}
@@ -61,7 +61,7 @@ func (r *TCPRelay) realRemoteHost(conn net.Conn, connData *ConnData) (addr strin
connData.Proxy = proxy
addr = fmt.Sprintf("%s:%d", host, session.dstPort)
- logger.Debugf("[tcp] tunnel %s:%d > %s proxy %q", session.srcIP, session.srcPort, addr, proxy)
+ logger.Debugf("[tcp relay] tunnel %s:%d > %s proxy %q", session.srcIP, session.srcPort, addr, proxy)
return
}
@@ -108,19 +108,20 @@ func (r *TCPRelay) Serve() error {
addr := &net.TCPAddr{IP: r.relayIP, Port: int(r.relayPort)}
ln, err := net.ListenTCP("tcp", addr)
if err != nil {
+ logger.Errorf("[tcp relay] listen failed: %v", err)
return err
}
- logger.Infof("[tcp] listen on %v", addr)
+ logger.Infof("[tcp relay] listen on %v", addr)
for {
conn, err := ln.AcceptTCP()
if err != nil {
- logger.Errorf("acceept failed temporary: %v", err)
+ logger.Errorf("[tcp relay] acceept failed temporary: %v", err)
time.Sleep(time.Second) //prevent log storms
continue
}
- logger.Debugf("[tcp] new connection [%s > %s]", conn.RemoteAddr(), conn.LocalAddr())
+ logger.Debugf("[tcp relay] new connection [%s > %s]", conn.RemoteAddr(), conn.LocalAddr())
go r.handleConn(conn)
}
}
diff --git a/tun.go b/tun.go
index a5c22c0..34c0126 100644
--- a/tun.go
+++ b/tun.go
@@ -28,6 +28,7 @@ func (tun *TunDriver) Serve() error {
for {
n, err := ifce.Read(buffer)
if err != nil {
+ logger.Errorf("[tun] read failed: %v", err)
return err
}
diff --git a/udp_relay.go b/udp_relay.go
index 08ea7f6..ee5529b 100644
--- a/udp_relay.go
+++ b/udp_relay.go
@@ -77,7 +77,7 @@ func (r *UDPRelay) grabTunnel(localConn *net.UDPConn, cliaddr *net.UDPAddr) *UDP
srvaddr := &net.UDPAddr{IP: record.RealIP, Port: int(session.dstPort)}
remoteConn, err := net.DialUDP("udp", nil, srvaddr)
if err != nil {
- logger.Errorf("[udp] connect to %s failed: %v", srvaddr, err)
+ logger.Errorf("[udp relay] connect to %s failed: %v", srvaddr, err)
return nil
}
tunnel = &UDPTunnel{
@@ -88,16 +88,16 @@ func (r *UDPRelay) grabTunnel(localConn *net.UDPConn, cliaddr *net.UDPAddr) *UDP
remoteConn: remoteConn,
}
- logger.Debugf("[udp] %s:%d > %v: new tunnel", session.srcIP, session.srcPort, srvaddr)
+ logger.Debugf("[udp relay] %s:%d > %v: new tunnel", session.srcIP, session.srcPort, srvaddr)
r.tunnels[addr] = tunnel
go func() {
err := tunnel.Pump()
if err != nil {
- logger.Debugf("[udp] pump to %v failed: %v", tunnel.remoteConn.RemoteAddr(), err)
+ logger.Debugf("[udp relay] pump to %v failed: %v", tunnel.remoteConn.RemoteAddr(), err)
}
tunnel.remoteConn.Close()
- logger.Debugf("[udp] %s:%d > %v: destroy tunnel", tunnel.session.srcIP, tunnel.session.srcPort, srvaddr)
+ logger.Debugf("[udp relay] %s:%d > %v: destroy tunnel", tunnel.session.srcIP, tunnel.session.srcPort, srvaddr)
r.lock.Lock()
delete(r.tunnels, addr)
@@ -111,12 +111,12 @@ func (r *UDPRelay) grabTunnel(localConn *net.UDPConn, cliaddr *net.UDPAddr) *UDP
func (r *UDPRelay) handlePacket(localConn *net.UDPConn, cliaddr *net.UDPAddr, packet []byte) {
tunnel := r.grabTunnel(localConn, cliaddr)
if tunnel == nil {
- logger.Errorf("[udp] %v > %v: grap tunnel failed", cliaddr, localConn.LocalAddr())
+ logger.Errorf("[udp relay %v > %v: grap tunnel failed", cliaddr, localConn.LocalAddr())
return
}
_, err := tunnel.Write(packet)
if err != nil {
- logger.Debugf("[udp] %v", err)
+ logger.Debugf("[udp relay] tunnel write failed: %v", err)
}
}
@@ -124,6 +124,7 @@ func (r *UDPRelay) Serve() error {
addr := &net.UDPAddr{IP: r.relayIP, Port: int(r.relayPort)}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
+ logger.Errorf("[udp relay] listen failed: %v", err)
return err
}
@@ -131,7 +132,9 @@ func (r *UDPRelay) Serve() error {
b := make([]byte, MTU)
n, cliaddr, err := conn.ReadFromUDP(b)
if err != nil {
- return err
+ logger.Errorf("[udp relay] acceept failed temporary: %v", err)
+ time.Sleep(time.Second) //prevent log storms
+ continue
}
go r.handlePacket(conn, cliaddr, b[:n])
}