Go RIP协议简单的模拟实现

Posted yizdu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go RIP协议简单的模拟实现相关的知识,希望对你有一定的参考价值。

RIP协议是一种动态路由选择协议。一个路由器,定时与和自身相邻的路由器交换路由表。路由器通过一定的算法,根据对方发送的路由表更新自身的路由表。从而动态更新整个自治系统内的所有路由器的路由表。

如果你只是为了完成学校的RIP协议实验但又不懂go语言,我可以简单的说明一下,go里面的结构体类似其他语言中的类,只不过是只有成员变量的类。结构体的方法,类似类(对象)的方法。

这个实验的主函数中,我只设计了RIP互换路由表最后达到收敛的过程,并没有设计模拟某个路由器不可达的情况,当然也没有设计出现“坏消息传得慢”的情况。

首先定义数据结构
RIP报文内的路由表项,定义了,去网段号为NetId的网段,需要转发给NextHop路由器,距离为Distance。在这个实验中,我将路由器所在网段距离设为1。下一跳路由器为-1时,代表直接交付,也就是目标网段在当前路由器所在网段中。

// RIP 路由表项
type Info struct {
    NetId    int
    // 路由器所在网段距离设为1
    Distance int
    // NextHop 为-1代表直接交付
    NextHop  int
}

路由表,包含一个路由表项类型的切片。

//路由表
type RouterTable []Info

路由器。包含地址和路由表。这里因为只是个简单实验,我使用一个整形数来充当路由器地址。虽然IP地址本身也只是个32位无符号整数。

// 路由器
type Router struct {
    Addr  int
    Table RouterTable
}

定义路由器结构体的方法。

输出路由器信息方法。实现了String()接口,可以被内置的输出函数调用。

func (r Router) String() string {
    return fmt.Sprint("路由器地址:", r.Addr, "\\n路由表", r.Table)
}

FindInfo方法,可以找到路由器自身的路由表内对应某个网段(netID)的路由表项的索引。如果当前路由器有这个网段的路由表项,返回其下标和True,如果没有,返回-1和False。具体有什么用,后面会说到。

func (r *Router) FindInfo(netId int) (int, bool) {
    for i, info := range r.Table {
        if netId == info.NetId {
            return i, true
        }
    }
    return -1, false
}

重点:接收路由表方法。当前路由器会通过这个方法,接收其他路由器的地址(Addr)和路由表(recvTable),并修改自身的路由表(r.Table)

这个实验会使用多线程执行,所以为了避免多个线程同时修改自身路由表产生冲突,加了一个互斥锁(mux)

首先,路由器在收到其他路由器的路由表后,对其做预处理。将收到路由表内所有表项下一跳地址改为发送方路由器的地址,同时所有距离加1。

然后,遍历收到的路由表(recvTable)。在每次遍历时,尝试找出和当前收到的路由表项(info)网段(info.NetId)相同的(也就是对应的)自身路由表项的下标(existigInfoIndex)。

  1. 自身路由表内存不存在对应的表项?
    如果不存在,则将该表项加入自身路由表
    如果存在,执行2
  2. 如果存在自身对应表项,则查看其下一跳是否为发送方路由器的地址。
    如果是,则将自身对应表项信息更新为收到的表项信息。当然实际上这只会修改表项的距离。
    修改的原因是因为收到的路由器表是最新的消息,一切按照最新的为准。不管收到的距离相对自身已有的记录的是大还是小,一切按最新为准。
    如果不是,执行3
  3. 如果对应表项的下一跳路由器不是发送方路由器地址,则比对距离。
    如果自身对应表项距离大于收到的表项距离,更新为收到的表项。
    如果不是,则不做任何事情,进入下一次遍历。

在真正的RIP协议中,还会存在发送自身路由表给目标路由器后,3分钟内收不到回复的路由表,就将自身表内下一跳为该目标路由器的表项的距离设为16,即该路由器不可达。这里我的实验中没有模拟出这个。

修改完成自身路由表后,释放锁(mux.Unlock),输出当前路由表信息。

func (r *Router) Recv(recvRtrAddr int, recvTable RouterTable) {
    var mux sync.Mutex
    for i := range recvTable {
        recvTable[i].NextHop = recvRtrAddr
        recvTable[i].Distance += 1
    }
    mux.Lock()
    for _, info := range recvTable {
        existigInfoIndex, ok := r.FindInfo(info.NetId)
        if !ok {
            r.Table = append(r.Table, info)
        } else {
            if r.Table[existigInfoIndex].NextHop == recvRtrAddr {
                r.Table[existigInfoIndex] = info
            } else {
                if r.Table[existigInfoIndex].Distance > info.Distance {
                    r.Table[existigInfoIndex] = info
                }
            }

        }
    }
    mux.Unlock()
    fmt.Printf("\\n%v\\n", r)
}

发送路由表方法。当前路由器将自身的地址和路由表,发送给目标路由器。
输入目标路由器对象指针(dstRtr),将自身的路由表复制一份,然后调用目标路由器对象的Recv()方法接收当前路由器的地址和路由表。

为什么要将自身的路由表显式复制一份呢,是因为go中的切片只是个引用,直接传递给函数修改,影响会传递到函数外。所以要进行深复制确保发送的只是一份副本。

func (r *Router) Send(dstRtr *Router) {
    sendTable := make(RouterTable, len(r.Table))
    copy(sendTable, r.Table)
    dstRtr.Recv(r.Addr, sendTable)

}

自动更新方法。这里有两个全局变量。ROUTER_LIST是一个包含所有路由器对象指针的切片。REACHABLE是一个指示两个路由器之间是否连接(也就是是否相邻)的可达矩阵。这里的“可达”仅指物理上的连接,不表示某个路由器坏了无法回复RIP报文时的情况。
遍历所有路由器对象,如果是相邻的,且不是自身,这对其发送自身的路由表。

延时可以不加,在这个实验中因为没有设计超时设为不可达的情况,设置为无延时也是行的。

func (r *Router) AutoUpdate() {
    for true {
        time.Sleep(time.Second * 3)
        for _, dstRtr := range ROUTER_LIST {
            if dstRtr != r {
                if REACHABLE[r.Addr][dstRtr.Addr] {
                    r.Send(dstRtr)
                }
            }
        }
    }
}

整份代码。
参考主函数第一行注释,实验中的网路拓扑结构是这样的。

N0 -R0- N1 -R1- N2 -R2- N3

N*代表网络号,R*代表路由器号(号和地址相同)。-代表路由器和某几个网段连接了。

通过多线程启动自动更新后,3个路由器之间会一直与相邻的路由器互换消息,最后达到收敛,即趋于稳定。代码最后最后的wg.Wait()只是为了不让主线程退出。

package main

import (
    "fmt"
    "sync"
    "time"
)

var REACHABLE [][]bool
var ROUTER_LIST []*Router

// RIP 路由表项
type Info struct {
    NetId    int
    // 路由器所在网段距离设为1
    Distance int
    // NextHop 为-1代表直接交付
    NextHop  int
}

// 路由器
type Router struct {
    Addr  int
    Table RouterTable
}

//路由表
type RouterTable []Info

func (r *Router) FindInfo(netId int) (int, bool) {
    for i, info := range r.Table {
        if netId == info.NetId {
            return i, true
        }
    }
    return -1, false
}

func (r Router) String() string {
    return fmt.Sprint("路由器地址:", r.Addr, "\\n路由表", r.Table)
}

func (r *Router) Recv(recvRtrAddr int, recvTable RouterTable) {
    var mux sync.Mutex
    for i := range recvTable {
        recvTable[i].NextHop = recvRtrAddr
        recvTable[i].Distance += 1
    }
    mux.Lock()
    for _, info := range recvTable {
        existigInfoIndex, ok := r.FindInfo(info.NetId)
        if !ok {
            r.Table = append(r.Table, info)
        } else {
            if r.Table[existigInfoIndex].NextHop == recvRtrAddr {
                r.Table[existigInfoIndex] = info
            } else {
                if r.Table[existigInfoIndex].Distance > info.Distance {
                    r.Table[existigInfoIndex] = info
                }
            }

        }
    }
    mux.Unlock()
    fmt.Printf("\\n%v\\n", r)
}

func (r *Router) Send(dstRtr *Router) {
    sendTable := make(RouterTable, len(r.Table))
    copy(sendTable, r.Table)
    dstRtr.Recv(r.Addr, sendTable)

}

func (r *Router) AutoUpdate() {
    for true {
        time.Sleep(time.Second * 0)
        for _, dstRtr := range ROUTER_LIST {
            if dstRtr != r {
                if REACHABLE[r.Addr][dstRtr.Addr] {
                    r.Send(dstRtr)
                }
            }
        }
    }
}

func main() {
    // N0 -R0- N1 -R1- N2 -R2- N3
    a := Router{0, []Info{}}
    a.Table = append(a.Table, Info{0, 1, -1})
    a.Table = append(a.Table, Info{1, 1, -1})
    b := Router{1, []Info{}}
    b.Table = append(b.Table, Info{1, 1, -1})
    b.Table = append(b.Table, Info{2, 1, -1})
    c := Router{2, []Info{}}
    c.Table = append(c.Table, Info{2, 1, -1})
    c.Table = append(c.Table, Info{3, 1, -1})
    ROUTER_LIST = append(ROUTER_LIST, &a)
    ROUTER_LIST = append(ROUTER_LIST, &b)
    ROUTER_LIST = append(ROUTER_LIST, &c)
    REACHABLE = [][]bool{
        {true, true, false},
        {true, true, true},
        {false, true, true},
    }
    //a.Send(ROUTER_LIST[1])
    go a.AutoUpdate()
    go b.AutoUpdate()
    go c.AutoUpdate()

    var wg sync.WaitGroup
    wg.Add(1)
    wg.Wait()
}

最后达到收敛时的输出是这样的

路由器地址:2
路由表[{2 1 -1} {3 1 -1} {1 2 1} {0 3 1}]

路由器地址:0
路由表[{0 1 -1} {1 1 -1} {2 2 1} {3 3 1}]

路由器地址:1
路由表[{1 1 -1} {2 1 -1} {3 2 2} {0 2 0}]

以上是关于Go RIP协议简单的模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

eNSP模拟实验-RIP路由协议基本配置

关于简单动态路由协议配置,注入,路由重分布

通过思科模拟器CISCO PACKET TRACER学习网络8——RIP路由

RIP路由协议基本配置

思科模拟器实验03-RIP协议路由配置

使用OSPF与RIP动态路由协议实现全网互连互通