implementation PING/PONG mechanism

This commit is contained in:
lilo
2022-03-01 15:01:45 +08:00
parent 3c0c7d4d2c
commit 504d531d72
10 changed files with 417 additions and 117 deletions

View File

@@ -19,7 +19,7 @@ all:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o target/gvn-Darwin-x86_64 main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o target/gvn-Darwin-aarch64 main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o target/gvn-Windows-x86_64.exe main.go
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o target/gvn-Freebsd-x86_64 main.go
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o target/gvn-Freebsd-amd64 main.go
macOS:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o target/gvn-Darwin-x86_64 main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o target/gvn-Darwin-aarch64 main.go

View File

@@ -26,6 +26,10 @@ import (
"github.com/spf13/viper"
)
const (
INTERVAL = 30
)
// rootCmd represents the base command when called without any subcommands
var (
rootCmd = &cobra.Command{
@@ -55,6 +59,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.gvn.yaml)")
rootCmd.PersistentFlags().BoolP("stdout", "", false, "logs to the stdout")
rootCmd.PersistentFlags().BoolP("debug", "", false, "debug log level")
// Cobra also supports local flags, which will only run
// when this action is called directly.
@@ -76,6 +81,9 @@ func initConfig() {
} else {
logrus.SetOutput(file)
}
if debug, _ := rootCmd.Flags().GetBool("debug"); debug {
logrus.SetLevel(logrus.DebugLevel)
}
if cfgFile == "" {
home, err := os.UserHomeDir()

140
cmd/up.go
View File

@@ -118,50 +118,66 @@ func upCommand(cmd *cobra.Command) {
// DHCP for client mode
if MODE(viper.GetUint("mode")) == MODECLIENT {
go func() {
ticker := time.NewTicker(10 * time.Second)
for _ = range ticker.C {
req := dhcp.Request{
Id: viper.GetString("id"),
Name: viper.GetString("dev.name"),
Subnets: viper.GetStringSlice("dev.subnets"),
// HEARTBEAT
req := dhcp.Request{
Id: viper.GetString("id"),
Name: viper.GetString("dev.name"),
Subnets: viper.GetStringSlice("dev.subnets"),
}
if client, res := dhcp.NewRPCClient(host, rpcZone, viper.GetString("server"), req); client != nil {
// ticker.Stop()
logrus.WithFields(logrus.Fields{
"res": res,
"req": req,
}).Info("RPC - Client received data")
devChan <- tun.Device{
Name: req.Name,
Ip: res.Ip,
Mtu: res.Mtu,
// ignore subnets because of self did't forward it to TUN
// Subnets: res.Subnets,
ServerVIP: res.ServerVIP,
Port: viper.GetUint("port"),
}
if client, res := dhcp.NewRPCClient(host, rpcZone, viper.GetString("server"), req); client != nil {
ticker.Stop()
logrus.WithFields(logrus.Fields{
"res": res,
"req": req,
}).Info("RPC - Client received data")
devChan <- tun.Device{
Name: req.Name,
Ip: res.Ip,
Mtu: res.Mtu,
// ignore subnets because of self did't forward it to TUN
// Subnets: res.Subnets,
ServerVIP: res.ServerVIP,
}
// TODO: sleep or make sure call after tun.New
// refresh local VIP table
var ress []dhcp.Response
if err := dhcp.Call("DHCPService", "Clients", req, &ress); err == nil {
subnets := make([]string, 0)
for _, r := range ress {
logrus.WithFields(logrus.Fields{
"VIP": r.Ip,
"ID": r.Id,
"Subnet": r.Subnets,
}).Info("Refresh local vip table")
route.Route.Add(strings.Split(r.Ip, "/")[0]+"/32", r.Id)
if r.Id != host.ID().Pretty() {
subnets = append(subnets, r.Subnets...)
}
}
logrus.WithFields(logrus.Fields{
"subnets": subnets,
}).Info("Refresh subnets")
tun.RefreshRoute(subnets)
}
var ress []dhcp.Response
if err := dhcp.Call("DHCPService", "Clients", req, &ress); err == nil {
// subnets := make([]string, 0)
for _, r := range ress {
logrus.WithFields(logrus.Fields{
"VIP": r.Ip,
"ID": r.Id,
"Subnet": r.Subnets,
}).Info("Refresh local vip table")
// route.Route.Add(strings.Split(r.Ip, "/")[0]+"/32", r.Id)
subnet := strings.Split(r.Ip, "/")[0] + "/32"
route.EventBus.Publish(route.ADD_ROUTE_TOPIC, route.RouteEvent{Id: r.Id, Subnets: []string{subnet}})
if r.Id != host.ID().Pretty() {
// does not change route via local ethernet
// subnets = append(subnets, r.Subnets...)
route.EventBus.Publish(route.ADD_ROUTE_TOPIC, route.RouteEvent{Id: r.Id, Subnets: r.Subnets})
}
}
// logrus.WithFields(logrus.Fields{
// "subnets": subnets,
// }).Info("Refresh subnets")
// tun.RefreshRoute(subnets)
} else {
logrus.WithFields(logrus.Fields{
"ERROR": err,
}).Error("Request clients error")
}
ticker := time.NewTicker(INTERVAL * time.Second)
for range ticker.C {
// TODO: sleep or make sure call after tun.New
// refresh local VIP table
if err := dhcp.Call("DHCPService", "Ping", req, nil); err != nil {
// TODO: log error
logrus.WithFields(logrus.Fields{
"ERROR": err,
}).Error("RPC - Ping error")
}
}
}()
}
@@ -175,7 +191,7 @@ func upCommand(cmd *cobra.Command) {
for sig := range c {
switch sig {
case syscall.SIGINT:
// exit when receive ctrl+c
// exit when receive ctrl+c and others signal
logrus.WithFields(logrus.Fields{
"SIG": sig,
"dev": mainDev,
@@ -293,3 +309,45 @@ func readData(stream network.Stream) {
}
}
}
/*
func contains(elems []string, v string) bool {
for _, s := range elems {
if v == s {
return true
}
}
return false
}
// check pre contains pos elements, eg:
// pre: [1, 3, 5, 6], pos: [1, 2, 3, 4]
// result should be: [2, 4]
func contains(left, right []string) []string {
sort.Strings(left)
sort.Strings(right)
tmp := make([]string, 0)
for _, item := range right {
if !contains(left, item) {
tmp = append(tmp, item)
}
}
return tmp
}
func mins(left, right *[]string) {
for _, item := range *right {
left = remove(*left, item)
}
}
func remove(items []string, item string) []string {
newitems := []string{}
for _, i := range items {
if i != item {
newitems = append(newitems, i)
}
}
return newitems
}
*/

View File

@@ -1,5 +1,6 @@
id: QmZiwVDDPhByWJsk5tJqajnPFjvM15KV1NuasSb9WN3ZJq
server: "/ip4/172.168.1.134/tcp/6543/p2p/QmSyYjoecj3aQ46TAWSEAmork3CRS51Y2SZCcEsiX8XeAx"
port: 6543
dev:
name: utun4
mtu: 1420

View File

@@ -17,9 +17,11 @@ package dhcp
import (
"context"
"errors"
"net"
"strings"
"sync"
"time"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
@@ -44,6 +46,7 @@ type RPC struct {
type Request struct {
Id string
Mode int
Name string
Subnets []string
}
@@ -55,6 +58,9 @@ type Response struct {
Mtu int
Subnets []string
ServerVIP string
Mode int
Ttl int64
LoginTime int64
}
type DHCPService struct {
@@ -75,6 +81,9 @@ func (s *DHCPService) DHCP(ctx context.Context, req Request, res *Response) erro
data.Id = req.Id
data.Name = req.Name
data.Subnets = req.Subnets
data.Mode = req.Mode
data.LoginTime = time.Now().Unix()
data.Ttl = 10 * 60 // 10 min
if MaxCIDR == "" {
MaxCIDR = s.Cidr
@@ -106,12 +115,17 @@ func (s *DHCPService) DHCP(ctx context.Context, req Request, res *Response) erro
res.Mtu = data.Mtu
res.Subnets = data.Subnets
res.ServerVIP = data.ServerVIP
res.Mode = data.Mode
res.LoginTime = data.LoginTime
res.Ttl = data.Ttl
logrus.WithFields(logrus.Fields{
"res": res,
}).Info("RPC - Client requested data")
// vip/mask -> vip/32
route.Route.Add(strings.Split(data.Ip, "/")[0]+"/32", data.Id)
// route.Route.Add(strings.Split(data.Ip, "/")[0]+"/32", data.Id)
subnet := strings.Split(data.Ip, "/")[0] + "/32"
route.EventBus.Publish(route.ADD_ROUTE_TOPIC, route.RouteEvent{Id: data.Id, Subnets: []string{subnet}})
mu.Unlock()
return nil
}
@@ -119,11 +133,15 @@ func (s *DHCPService) DHCP(ctx context.Context, req Request, res *Response) erro
func (s *DHCPService) Clients(ctx context.Context, req Request, res *[]Response) error {
mu.Lock()
for _, v := range s.KV {
if v.Mode != 1 && time.Now().Unix()-v.LoginTime > v.Ttl {
continue
}
r := Response{
Id: v.Id,
Name: v.Name,
Ip: v.Ip,
Mtu: v.Mtu,
Mode: v.Mode,
Subnets: v.Subnets,
ServerVIP: v.ServerVIP,
}
@@ -133,6 +151,20 @@ func (s *DHCPService) Clients(ctx context.Context, req Request, res *[]Response)
return nil
}
func (s *DHCPService) Ping(ctx context.Context, req Request, res *Response) error {
mu.Lock()
if v, ok := s.KV[req.Id]; ok {
v.Ttl = 10 * 60 // 10 min
v.LoginTime = time.Now().Unix()
s.KV[req.Id] = v
} else {
mu.Unlock()
return errors.New("not found")
}
mu.Unlock()
return nil
}
func NewRPCServer(host host.Host, zone string, cidr string, mtu int) {
server := rpc.NewServer(host, protocol.ID(zone))
service := DHCPService{KV: map[string]Response{}, Cidr: cidr, Mtu: mtu}
@@ -141,6 +173,21 @@ func NewRPCServer(host host.Host, zone string, cidr string, mtu int) {
"ERROR": err,
}).Panic("RPC - build RPC service error")
}
// does not clean the zombie client
/*
go func() {
ticker := time.NewTicker(1 * time.Minute)
for range ticker.C {
for k, v := range service.KV {
if time.Now().Unix()-v.LoginTime > v.Ttl {
mu.Lock()
delete(service.KV, k)
mu.Unlock()
}
}
}
}()
*/
// server register
mu.Lock()
service.KV[host.ID().Pretty()] = Response{
@@ -148,6 +195,9 @@ func NewRPCServer(host host.Host, zone string, cidr string, mtu int) {
Ip: cidr,
Mtu: mtu,
ServerVIP: cidr,
Mode: 1,
LoginTime: time.Now().Unix(),
Ttl: 10 * 60, // 10 min
}
mu.Unlock()
}
@@ -161,7 +211,7 @@ func NewRPCClient(host host.Host, zone string, server string, req Request) (*rpc
if err := client.Call(addr.ID, "DHCPService", "DHCP", req, &res); err != nil {
logrus.WithFields(logrus.Fields{
"ERROR": err,
}).Error("RPC - call RPC serveice error")
}).Error("RPC - call DHCP RPC serveice error")
}
return client, res
}
@@ -172,7 +222,11 @@ func NewRPCClient(host host.Host, zone string, server string, req Request) (*rpc
func Call(svcName string, svcMethod string, req Request, res interface{}) error {
if err := client.Call(serverId, svcName, svcMethod, req, &res); err != nil {
logrus.WithFields(logrus.Fields{
"ERROR": err,
"ERROR": err,
"svcName": svcName,
"svcMethod": svcMethod,
"req": req,
"res": res,
}).Error("RPC - call RPC serveice error")
return err
}

63
eventbus/eventbus.go Normal file
View File

@@ -0,0 +1,63 @@
/*
Copyright © 2022 lilo <luolee.me@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package eventbus
import (
"sync"
)
type DataEvent struct {
Data interface{}
Topic string
}
// DataChannel is a channel which can accept an DataEvent
type DataChannel chan DataEvent
// DataChannelSlice is a slice of DataChannels
type DataChannelSlice []DataChannel
// EventBus stores the information about subscribers interested for a particular topic
type EventBus struct {
Subscribers map[string]DataChannelSlice
rm sync.RWMutex
}
func (eb *EventBus) Publish(topic string, data interface{}) {
eb.rm.RLock()
if chans, found := eb.Subscribers[topic]; found {
// this is done because the slices refer to same array even though they are passed by value
// thus we are creating a new slice with our elements thus preserve locking correctly.
// special thanks for /u/freesid who pointed it out
channels := append(DataChannelSlice{}, chans...)
go func(data DataEvent, dataChannelSlices DataChannelSlice) {
for _, ch := range dataChannelSlices {
ch <- data
}
}(DataEvent{Data: data, Topic: topic}, channels)
}
eb.rm.RUnlock()
}
func (eb *EventBus) Subscribe(topic string, ch DataChannel) {
eb.rm.Lock()
if prev, found := eb.Subscribers[topic]; found {
eb.Subscribers[topic] = append(prev, ch)
} else {
eb.Subscribers[topic] = append([]DataChannel{}, ch)
}
eb.rm.Unlock()
}

View File

@@ -36,14 +36,11 @@ var (
routingDiscovery *discovery.RoutingDiscovery
)
// func NewDHT(host host.Host, bootstraps []multiaddr.Multiaddr) *dht.IpfsDHT {
func NewDHT(host host.Host, zone string, bootstraps []string) *dht.IpfsDHT {
ctx := context.Background()
addrs := make([]peer.AddrInfo, 0)
if len(bootstraps) > 0 {
for _, bootstrap := range bootstraps {
// ids := strings.Split(bootstrap, "/")
// bs := peer.AddrInfo{ID: peer.ID(ids[len(ids)-1]), Addrs: addrs}
addr, err := peer.AddrInfoFromString(bootstrap)
if err != nil {
continue
@@ -51,10 +48,6 @@ func NewDHT(host host.Host, zone string, bootstraps []string) *dht.IpfsDHT {
addrs = append(addrs, *addr)
}
// var options []dht.Option
// options = append(options, dht.Mode(dht.ModeServer))
// options = append(options, dht.BootstrapPeers(bs))
dstore := dsync.MutexWrap(ds.NewMapDatastore())
kdht = dht.NewDHT(ctx, host, dstore)

View File

@@ -31,7 +31,6 @@ import (
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/liloew/gvn/dhcp"
"github.com/liloew/gvn/route"
"github.com/liloew/gvn/tun"
"github.com/sirupsen/logrus"
"github.com/songgao/water/waterutil"
"github.com/spf13/viper"
@@ -133,10 +132,11 @@ func NewPubSub(host host.Host, topic string) *Publisher {
continue
}
// TODO: add MASQUERADE if self
tun.RefreshRoute(message.Subnets)
// tun.RefreshRoute(message.Subnets)
for _, subnet := range message.Subnets {
// Add will override the exist one
route.Route.Add(strings.TrimSpace(subnet), message.Id)
// route.Route.Add(strings.TrimSpace(subnet), message.Id)
route.EventBus.Publish(route.REFRESH_ROUTE_TOPIC, route.RouteEvent{Id: message.Id, Subnets: []string{subnet}})
}
} else if message.MessageType == MessageTypeOnline {
// refresh clients
@@ -147,7 +147,7 @@ func NewPubSub(host host.Host, topic string) *Publisher {
req := dhcp.Request{}
var ress []dhcp.Response
if err := dhcp.Call("DHCPService", "Clients", req, &ress); err == nil {
subnets := make([]string, 0)
// subnets := make([]string, 0)
for _, r := range ress {
if r.Id == host.ID().Pretty() {
continue
@@ -157,20 +157,25 @@ func NewPubSub(host host.Host, topic string) *Publisher {
"ID": r.Id,
"Subnet": r.Subnets,
}).Info("Refresh local vip table")
route.Route.Add(strings.Split(r.Ip, "/")[0]+"/32", r.Id)
// route.Route.Add(strings.Split(r.Ip, "/")[0]+"/32", r.Id)
subnet := strings.Split(r.Ip, "/")[0] + "/32"
route.EventBus.Publish(route.REFRESH_ROUTE_TOPIC, route.RouteEvent{Id: r.Id, Subnets: []string{subnet}})
if r.Id != host.ID().Pretty() {
subnets = append(subnets, r.Subnets...)
// subnets = append(subnets, r.Subnets...)
route.EventBus.Publish(route.REFRESH_ROUTE_TOPIC, route.RouteEvent{Id: r.Id, Subnets: r.Subnets})
}
}
logrus.WithFields(logrus.Fields{
"subnets": subnets,
}).Info("Refresh subnets")
tun.RefreshRoute(subnets)
// logrus.WithFields(logrus.Fields{
// "subnets": subnets,
// }).Info("Refresh subnets")
// tun.RefreshRoute(subnets)
}
}
} else if message.MessageType == MessageTypeOffline {
route.Route.Remove(strings.Split(message.Vip, "/")[0] + "/32")
// route.Route.Remove(strings.Split(message.Vip, "/")[0] + "/32")
subnet := strings.Split(message.Vip, "/")[0] + "/32"
route.EventBus.Publish(route.REMOVE_ROUTE_TOPIC, route.RouteEvent{Subnets: []string{subnet}})
}
}
} else {

172
route/route.go Normal file
View File

@@ -0,0 +1,172 @@
/*
Copyright © 2022 lilo <luolee.me@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package route
import (
"sync"
"github.com/liloew/gvn/eventbus"
"github.com/liloew/gvn/tun"
"github.com/sirupsen/logrus"
"github.com/zmap/go-iptree/iptree"
)
type RouteEvent struct {
Subnets []string
Id string
Vip string
}
const (
ADD_ROUTE_TOPIC = "ADD_ROUTE"
REMOVE_ROUTE_TOPIC = "REMOVE_ROUTE"
REFRESH_ROUTE_TOPIC = "REFRESH_ROUTE"
ONLINE_TOPIC = "ONLINE"
OFFLINE_TOPIC = "OFFLINE"
)
var (
Route = RouteTable{
tree: iptree.New(),
}
EventBus = &eventbus.EventBus{
Subscribers: map[string]eventbus.DataChannelSlice{},
}
LocalRoute = make([]string, 0)
)
func init() {
go func() {
addRouteCh := make(chan eventbus.DataEvent)
removeRouteCh := make(chan eventbus.DataEvent)
refreshRouteCh := make(chan eventbus.DataEvent)
onlineCh := make(chan eventbus.DataEvent)
// TODO: ofline channel
EventBus.Subscribe(ADD_ROUTE_TOPIC, addRouteCh)
EventBus.Subscribe(REMOVE_ROUTE_TOPIC, removeRouteCh)
EventBus.Subscribe(REFRESH_ROUTE_TOPIC, refreshRouteCh)
EventBus.Subscribe(ONLINE_TOPIC, onlineCh)
for {
select {
case data := <-addRouteCh:
logrus.WithFields(logrus.Fields{
"Data": data.Data,
"Topic": data.Topic,
}).Debug("Add Route Channel")
for _, subnet := range data.Data.(RouteEvent).Subnets {
Route.add(subnet, data.Data.(RouteEvent).Id)
}
case data := <-removeRouteCh:
logrus.WithFields(logrus.Fields{
"Data": data.Data,
"Topic": data.Topic,
}).Debug("Remove Route Channel")
for _, subnet := range data.Data.(RouteEvent).Subnets {
Route.remove(subnet)
}
case data := <-refreshRouteCh:
logrus.WithFields(logrus.Fields{
"Data": data.Data,
"Topic": data.Topic,
}).Debug("Refresh Route Channel")
for _, subnet := range data.Data.(RouteEvent).Subnets {
Route.remove(subnet)
Route.add(subnet, data.Data.(RouteEvent).Id)
}
case data := <-onlineCh:
logrus.WithFields(logrus.Fields{
"Data": data.Data,
"Topic": data.Topic,
}).Debug("Online Channel")
}
}
}()
}
type RouteTable struct {
tree *iptree.IPTree
rm sync.RWMutex
}
// subnet - 192.168.1.0/24
// peerId - P2P node ID
func (r *RouteTable) refresh(subnet string, peerId string) {
r.remove(subnet)
r.add(subnet, peerId)
}
func (r *RouteTable) remove(subnet string) {
// TODO: LocalRoute remove
r.rm.Lock()
r.tree.DeleteByString(subnet)
if err := tun.RemoveRoute([]string{subnet}); err == nil {
LocalRoute = remove(LocalRoute, subnet)
} else {
logrus.WithFields(logrus.Fields{
"ERROR": err,
}).Error("Remove route error")
}
r.rm.Unlock()
}
func (r *RouteTable) add(subnet string, peerId string) {
r.rm.Lock()
if contains(LocalRoute, subnet) {
logrus.WithFields(logrus.Fields{
"LocalRoute": LocalRoute,
"Subnet": subnet,
}).Info("Ignore the subnet becuase of existence")
r.rm.Unlock()
return
}
r.tree.AddByString(subnet, peerId)
if err := tun.AddRoute([]string{subnet}); err == nil {
LocalRoute = append(LocalRoute, subnet)
} else {
logrus.WithFields(logrus.Fields{
"ERROR": err,
}).Error("Add route error")
}
r.rm.Unlock()
}
func (r *RouteTable) Get(ip string) (interface{}, bool, error) {
return r.tree.GetByString(ip)
}
func (r *RouteTable) Clean() {
// TODO:
r.tree = iptree.New()
}
func contains(elems []string, v string) bool {
for _, s := range elems {
if v == s {
return true
}
}
return false
}
func remove(items []string, item string) []string {
newitems := []string{}
for _, i := range items {
if i != item {
newitems = append(newitems, i)
}
}
return newitems
}

View File

@@ -1,54 +0,0 @@
/*
Copyright © 2022 lilo <luolee.me@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package route
import (
"github.com/zmap/go-iptree/iptree"
)
var (
Route = RouteTable{
tree: iptree.New(),
}
)
type RouteTable struct {
tree *iptree.IPTree
}
// subnet - 192.168.1.0/24
// peerId - P2P node ID
func (r *RouteTable) Refresh(subnet string, peerId string) {
r.Remove(subnet)
r.Add(subnet, peerId)
}
func (r *RouteTable) Remove(subnet string) {
r.tree.DeleteByString(subnet)
}
func (r *RouteTable) Add(subnet string, peerId string) {
r.tree.AddByString(subnet, peerId)
}
func (r *RouteTable) Get(ip string) (interface{}, bool, error) {
return r.tree.GetByString(ip)
}
func (r *RouteTable) Clean() {
// TODO:
r.tree = iptree.New()
}