commit e8e9fa1d4aaa3d15171b258d63c1fd8722e24cc9 Author: eson <474420502@qq.com> Date: Mon Dec 17 02:58:52 2018 +0800 v1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dac615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +IPCenter +ipcenter diff --git a/actives.json b/actives.json new file mode 100644 index 0000000..d61c80a --- /dev/null +++ b/actives.json @@ -0,0 +1,28 @@ +{ + "switch": { + "192.168.6.100:8080": { + "HostMap": {} + }, + "192.168.6.100:8082": { + "HostMap": {} + }, + "192.168.6.100:8083": { + "HostMap": {} + }, + "192.168.6.100:8084": { + "HostMap": {} + }, + "192.168.6.100:8085": { + "HostMap": {} + }, + "192.168.6.100:8086": { + "HostMap": {} + }, + "192.168.6.100:8087": { + "HostMap": {} + }, + "192.168.6.100:8088": { + "HostMap": {} + } + } +} \ No newline at end of file diff --git a/actives.yaml b/actives.yaml new file mode 100644 index 0000000..5f45e8a --- /dev/null +++ b/actives.yaml @@ -0,0 +1,9 @@ +switch: + 192.168.6.100:8080: ["10.10.10.70", "10.10.10.71"] + 192.168.6.100:8082: ["10.10.10.74", "10.10.10.75"] + 192.168.6.100:8083: ["10.10.10.76", "10.10.10.77"] + 192.168.6.100:8085: ["10.10.10.80", "10.10.10.81"] + 192.168.6.100:8087: ["10.10.10.84", "10.10.10.85"] + 192.168.6.100:8088: ["10.10.10.86", "10.10.10.87"] + 192.168.6.100:8091: ["10.10.10.92", "10.10.10.93"] +restart: 65 \ No newline at end of file diff --git a/alertover.go b/alertover.go new file mode 100644 index 0000000..6a768ac --- /dev/null +++ b/alertover.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + "sync" + "time" + + "github.com/levigross/grequests" +) + +// AlertOver 报警相关 +type AlertOver struct { + Collection sync.Map // k 是title v是时间戳 用于统计上次的发送时间, 发送题目不能重复 + Setting map[string]string +} + +// SetDefaultSetting 设置默认Setting +func (ao *AlertOver) SetDefaultSetting() { + ao.Setting = map[string]string{ + "source": "s-577f047d-763a-4f45-a652-475595dc", + "receiver": "g-5b4ce1ea-7f6b-422a-9127-a9049c65", + "priority": "1", + } +} + +func (ao *AlertOver) send(data map[string]string) { + // 合并初始化设置 + for k, v := range ao.Setting { + data[k] = v + } + + resp, err := grequests.Post("https://api.alertover.com/v1/alert", + &grequests.RequestOptions{Data: data}) + + now := time.Now().Unix() + ao.Collection.Store(data["title"], now) + + ErrorLog(err) + log.Println(resp.String()) +} + +// Alert 报警API +func (ao *AlertOver) Alert(title, content string) { + + data := map[string]string{ + "title": title, + "content": content, + } + + now := time.Now().Unix() + if _t, ok := ao.Collection.Load(title); ok { + lasttime := _t.(int64) + if now-lasttime >= 600 { + ao.send(data) + } + } else { + ao.Collection.Store(title, now) + ao.send(data) + } +} diff --git a/dip.go b/dip.go new file mode 100644 index 0000000..eaac63f --- /dev/null +++ b/dip.go @@ -0,0 +1,158 @@ +package main + +import ( + "io/ioutil" + "log" + "strconv" + "strings" + "sync" + "time" + + "474420502.top/eson/requests" + "gopkg.in/yaml.v2" +) + +type DIPWorker struct { + ForMatch map[string]*DIPGroup `yaml:"switch"` + ForLoop map[string]*DIPGroup + Restart int64 `yaml:"restart"` + Mutex *sync.Mutex +} + +// ShowGroupInfo 展示实时的 Group 信息动态 +func (worker *DIPWorker) ShowGroupInfo() string { + + now := time.Now().Unix() + + content := "" + for addr, group := range worker.ForLoop { + content += addr + ": [" + for _, dhost := range group.Group { + + overtimeLabel := "" + if now-dhost.ActiveTime >= 150 { + overtimeLabel = "<☠?>" + } + tm := time.Unix(dhost.ActiveTime, 0) + tm.Format("2006-01-02 15:04:05") //2018-07-11 15:10:19 + content += dhost.Host + "(" + tm.Format("2006-01-02 15:04:05") + overtimeLabel + ")," + } + content = strings.TrimRight(content, ",") + "]\n" + } + content = strings.TrimRight(content, "\n") + return content +} + +type DIPGroup struct { + Group map[string]*DHost // 原始参照 + Ready map[string]*DHost + IPTableNum string + Current *DHost + + Mutex *sync.Mutex +} + +func NewDIPGroup() *DIPGroup { + group := DIPGroup{} + group.Group = make(map[string]*DHost) + group.Ready = make(map[string]*DHost) + group.Mutex = &sync.Mutex{} + return &group +} + +func (group *DIPGroup) Choose(addr string) { + now := time.Now().Unix() + for ip, dhost := range group.Ready { + // 设置转换的iptable + if group.Current == nil { + group.Current = dhost + group.Current.ActiveTime = now + SetAddrForward(group.IPTableNum, addr, ip) + } else { + restartAddr := "http://" + group.Current.Host + ":8800/pppoe/restart" + if _, err := requests.NewSession().Get(restartAddr).Execute(); err != nil { + log.Println(err) + // group.Current.ActiveTime += 12 + } else { + group.Current = dhost + group.Current.ActiveTime = now + // log.Println("new set addr:", group.Current.Host, "restartAddr:", restartAddr, "resp", resp.Content()) + SetAddrForward(group.IPTableNum, addr, ip) + } + } + + delete(group.Ready, ip) + + break + } +} + +type DHost struct { + Host string + ActiveTime int64 +} + +func NewDHost(host string) *DHost { + dh := DHost{} + dh.ActiveTime = time.Now().Unix() + dh.Host = host + return &dh +} + +func (dh *DHost) OverTime(now int64) int64 { + if now-dh.ActiveTime >= 150 { + return now - dh.ActiveTime + } + return 0 +} + +func (dip *DIPWorker) UnmarshalYAML(unmarshal func(interface{}) error) error { + var data map[string]interface{} + if err := unmarshal(&data); err != nil { + return err + } + + log.Println(data["switch"]) + myswitch := data["switch"] + + dip.Restart = int64(data["restart"].(int)) + + num := 1 + for k, v := range myswitch.(map[interface{}]interface{}) { + + group := NewDIPGroup() + + // sudo iptables -t nat -R IPSWITCH 1 -p icmp -j DNAT --to 1.1.1.1:8885 + for _, host := range v.([]interface{}) { + dhost := NewDHost(host.(string)) + group.Group[dhost.Host] = dhost + dip.ForMatch[dhost.Host] = group + } + + dip.ForLoop[k.(string)] = group + group.IPTableNum = strconv.Itoa(num) + num++ + } + + return nil +} + +// NewDipWorker 创建一个 +func NewDipWorker(filename string) *DIPWorker { + worker := DIPWorker{} + worker.ForMatch = make(map[string]*DIPGroup) + worker.ForLoop = make(map[string]*DIPGroup) + + worker.Mutex = &sync.Mutex{} + + data, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(data, &worker) + if err != nil { + panic(err) + } + + return &worker +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..df84c36 --- /dev/null +++ b/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" +) + +var passwd = "youmi" + +// ErrorLog 错误打日志 +func ErrorLog(err error) bool { + if err != nil { + log.Println(err) + return true + } + return false +} + +// ErrorPanic 错误打日志 +func ErrorPanic(err error) { + if err != nil { + panic(err) + } +} + +func main() { + log.SetFlags(log.Llongfile | log.LstdFlags) + swi := NewSwitch() + swi.Run("0.0.0.0:3333", `10\.10\..+`) + // swi.Run("0.0.0.0:3333", ``) + log.Fatal() +} diff --git a/switch.go b/switch.go new file mode 100644 index 0000000..8415950 --- /dev/null +++ b/switch.go @@ -0,0 +1,175 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "os/exec" + "regexp" + "strings" + "time" +) + +// Switch 轮换IP相关类 +type Switch struct { + // config map[string]interface{} + // configMutex sync.Mutex + ipregion *regexp.Regexp + lastShow int64 + lastSwitch int64 + + Worker *DIPWorker + AO *AlertOver +} + +// SetAddrForward 设置当前ip 转发的节点 +func SetAddrForward(num, addr, ip string) { + port := strings.Split(addr, ":")[1] + cmd := exec.Command("/bin/sh", "-c", "sudo iptables -t nat -R IPSWITCH "+num+" -p tcp --dport "+port+" -j DNAT --to "+ip+":8885") + err := cmd.Run() + if err != nil { + panic(err) + } +} + +// NewSwitch 初始化默认 +func NewSwitch() *Switch { + swi := Switch{} + swi.Worker = NewDipWorker("actives.yaml") + swi.AO = &AlertOver{} + swi.AO.SetDefaultSetting() + + log.Println(swi.Worker.ShowGroupInfo()) + + http.HandleFunc("/ippool/switch/imactive", swi.imActive) + http.HandleFunc("/ippool/switch/update", swi.updateActives) + http.HandleFunc("/ippool/switch/actives", swi.switchActives) + + return &swi +} + +// Run addr 监听的地址addr +// ipregion 匹配adsl的规则, 作为ping服务器并且控制网络转发的调度 +func (swi *Switch) Run(addr string, ipregion string) { + swi.ipregion = regexp.MustCompile(ipregion) + + cmd := exec.Command("/bin/sh", "-c", "sudo iptables -t nat -N IPSWITCH") + cmd.Run() + + cmd = exec.Command("/bin/sh", "-c", "sudo iptables -t nat -F IPSWITCH") + cmd.Run() + + for range swi.Worker.ForLoop { + cmd := exec.Command("/bin/sh", "-c", "sudo iptables -t nat -A IPSWITCH -p icmp -s 1.1.1.1 -j DNAT --to 1.1.1.1:8885") + err := cmd.Run() + if err != nil { + panic(err) + } + } + + http.ListenAndServe(addr, nil) + +} + +// timeToSwitch 计算是否到切换时间 +func (swi *Switch) timeToSwitch(now int64) { + for addr, group := range swi.Worker.ForLoop { + if group.Current == nil { + group.Choose(addr) + } else { + if now >= group.Current.ActiveTime+swi.Worker.Restart { + // log.Println(now, group.Current.ActiveTime, swi.Worker.Restart) + group.Choose(addr) + } + } + } +} + +// checkInReady 签到的IP(vps的vpn节点IP)) +func (swi *Switch) checkInReady(ip string, now int64) { + + if dipg, ok := swi.Worker.ForMatch[ip]; ok { + + dipg.Group[ip].ActiveTime = now + + if dipg.Current == nil { + dhost := NewDHost(ip) + dhost.ActiveTime = now + dipg.Ready[ip] = dhost + } else { + + if dipg.Current.Host != ip { + if v, ok := dipg.Ready[ip]; ok { + v.ActiveTime = now + } else { + dhost := NewDHost(ip) + dhost.ActiveTime = now + dipg.Ready[ip] = dhost + } + } + + } + } +} + +// imActive 子节点访问 证明自己是活跃 +func (swi *Switch) imActive(w http.ResponseWriter, r *http.Request) { + + w.Write([]byte("ok")) + ip := strings.Split(r.RemoteAddr, ":")[0] + // log.Println(ip) // 后续可以把这些节点, 自动活跃与更新 + + swi.Worker.Mutex.Lock() + defer swi.Worker.Mutex.Unlock() + + now := time.Now().Unix() + + // 60秒show一次日志 + if now >= swi.lastShow+60 { + swi.lastShow = now + log.Println("\n" + swi.Worker.ShowGroupInfo()) + } + + // 5秒一次检测 + if now >= swi.lastSwitch+5 { + swi.lastSwitch = now + swi.timeToSwitch(now) + } + + swi.checkInReady(ip, now) +} + +// updateActives 更新最新配置 +func (swi *Switch) updateActives(w http.ResponseWriter, req *http.Request) { + + swi.Worker.Mutex.Lock() + defer swi.Worker.Mutex.Unlock() + + swi.Worker = NewDipWorker("actives.yaml") + w.Write([]byte("update success!")) +} + +func (swi *Switch) switchActives(w http.ResponseWriter, req *http.Request) { + + swi.Worker.Mutex.Lock() + defer swi.Worker.Mutex.Unlock() + + now := time.Now().Unix() + + var content []string + for addr, group := range swi.Worker.ForLoop { + + isappend := int64(1) + for _, dhost := range group.Group { + isappend *= dhost.OverTime(now) + } + if isappend == 0 { + content = append(content, addr) + } + } + jdata, err := json.Marshal(content) + ErrorLog(err) + _, err = w.Write(jdata) + ErrorLog(err) + +} diff --git a/switch_test.go b/switch_test.go new file mode 100644 index 0000000..a5760d9 --- /dev/null +++ b/switch_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" +) + +func TestSwitchYaml(t *testing.T) { + t.Error() + + swi := NewSwitch() + if swi == nil { + t.Error("swi is error") + } +}