本篇博客包含以下内容:
- 首先说明什么是 SRv6 技术, 并且通过虚拟机和路由工具 iproute2 来进行简单的 SRv6 网络搭建.
- 接下来我们介绍 P4 语言, 这是一种协议无关的描述网络数据包数据平面的语言, 我们用 P4 来实现我们需要的网络协议.
- 第三个部分, 我们介绍 P4 语言的环境如何安装
什么是 SRv6?
概要
SRv6 结合了 SR 分段路由和 IPv6。 SRv6利用现有的IPv6转发技术,通过灵活的IPv6扩展头实现网络编程。是下一代 IP 承载协议.
Why we need SRV6: SRv6 的必要性
当前的问题:
- Isolated IP islands - 孤立 IP 岛
- Programming-space limitation in IPv4 - IPv4 编程空间不足
- Decoupling of application and bearer networks - 软件和网络解耦
- Tight couple of data and control - 控制平面与数据平面高耦合
SRV6's Tech Values: SRv6 的技术价值
-
Intelligence 智能
- SRV6 的编程空间很大, 从 "网络路径","服务" 和 "转发行为" 三个纬度, 适合 "服务型驱动网络"
- SDN 架构, 网络中传递着应用的信息, 网络可以调度和优化
-
极简
- 不再需要 LDP or RSVP-TE 协议
-
原生的 IP 协议
- 保留了 IPv6 的数据报结构
- 可以与普通的 IPv6 设备连接, 兼容性强, 适合网络平滑升级
什么是 SR (Segment Routing):
Segment Routing, 既 "分段路由"
Core Idea:
下面是一个理解segment的例子:
Let's say you are departing Shanghai for a trip to Paris, but you need to take a connecting flight in Vienna. Your travel route will be divided into two segments: Shanghai → Vienna; Vienna → Paris. To reach your destination, you only need to buy the ticket at Shanghai. You can then take two flights to Paris according to your plan. 从上海坐飞机到巴黎, "上海 -> 巴黎" 分为 "上海 -> 维也纳", "维也纳 - > 巴黎"
SR 的核心idea:
- 包的转发路径分为多个 segment (段), 在路径的入口压入 segment
- 中转结点只需要转发
- segment identified by SID - 用 SID 区分 Segment
SR 的实现:
根据核心idea, 得出两大问题: - How to divide a path into segements? 转发路径如何分割成 "段"? - How to arrange the segments at starting point? 起初如何 "分段" 来决定转发路径? 我们使用代表不同功能函数的 segments 进行组合,来规划路径, 来实现不同业务的性能需求 转发层面可以用 IPv6 或者 MPLS, SRV6 使用 IPv6
SRv6 网络编程:
SR 是通过组合 segments 来实现路径编程的, 下面介绍 SRv6 如何实现路径编程: SRv6 中网络服务被编程为各种转发指令, 转发指令沿路线传递给网络设备, 实现网络编程
SRH
在 IPv6 数据报的header 添加了一个 Segment Routing Header, 既 SRH SRH包含:
- IPv6 显示路径
- segment list, 按顺序存储段和网络结点
- segment list 和 segment left 共同确定数据报 IPv6 目的地址 (DA)
3D Programming Space
三个方面:
-
segment list
-
128 位的 SID, 可编程路径
-
Segment 可编程服务, 组成部分有:
- Locator, 标识网络位置, 提供 IPv6 的路由能力, 引导转发地址查找
- Function, 定义转发时要执行的动作, 不同函数定义不同的行为
- Arguments (Optional): Function需要的参数 各部分长度可自定义, 灵活编程 配合 SDN, 可以实现网络和应用的交互, 建立服务驱动型的网络
How does SRV6 work a Network:
从两个方面来谈: Packet Forwarding Process and SRv6 Working Mode
包转发过程 Packet Forwarding Process
用一个例子来说:
需要将一个数据包从主机 1 转发到主机 2,主机 1 将数据包发送到节点 A 进行处理。 节点 A、B、D 和 E 都支持 SRv6,但节点 C 不支持。它仅支持 IPv6。 源节点 A 需要网络编程,使数据包经过链路 B-C 和 C-D 后通过节点 E 发送到主机 2 希望进行转发路径编程
- 源结点 A 将 B->C,C->D 路径信息, 以及 E结点发布的 SID 封装到 SRH中, SID 的封装顺序是逆序压栈封装的. Segment Left 指向要处理的 Segment List[2], 然后将值复制到 IPv6 数据报的目的地址 DA 中, 转发给 B
- B 收到数据报后, 查找本地 SID 表, 执行相应 SID 的指令 (SL - 1), 之后把segment list[1] 复制给 IPv6 数据报的目的地址 DA, 转发给C. 这里看出 SID 的编程功能
- C 收到数据报, 因为 C 不能识别 SRH, 直接进行 IPv6 转发, 转发规则为最长路径匹配, 转发给 D
- D 收到数据报后, 工作流程类似 B, 查表并执行指令, 复制 Segment List[0] 到 DA 里, 继续转发给 E
- E 收到数据报后, 查表, 执行指令 "解封装", 并向 Host 2 发送 IPv4 数据报, 结束转发过程 转发路径的编程信息是保存在 segment list 和不同种类 SID 里的
SRv6 工作模式 SRv6 Working Mode
两种工作模式: Traffic Engineerning and Best Effort
SRv6 TE Policy
SRv6 TE Policy的工作流程如下:
- 转发器(PE3)通过BGP-LS向控制器上报网络拓扑信息。拓扑信息包括节点和链路信息以及TE属性,例如链路成本、带宽和延迟。
- 控制器对收集到的拓扑信息进行分析,根据业务需求计算路径,满足业务SLA要求。
- 控制器将路径信息传递到网络的入口 (PE1)。然后入口生成 SRv6 TE 策略,其中包括头端地址、目标地址和颜色(扩展社区属性)。
- 入口(PE1)选择合适的SRv6 TE Policy来指导业务转发。在转发过程中,每个转发器根据 SRv6 报文中携带的信息执行其通告的 SID 的指令。
SRv6 BE
基于 IGP 算法算出最优路径 用于承载普通 VPN 业务
SRv6 比起 SR-MPLS 的优势
- 路由协议: 移除了 MPLS 数据平面, 简化网络
- 标签: 自定义 128 位的 SID 比固定 20 位的 MPLS 便签可编程度更高
- 兼容性: 兼容 IPv6 传统网络, 便于递增部署和平滑发展
- E2E 网络: 基于 IP 骨架, 简化 E2E 网络技术栈
- 网络编程: 三维的编程空间, 适应各种服务需求
利用Proxmox部署SRv6网络
目标:
目的:使2台仅支持IPv4的主机(主机a和主机b),通过SRv6实现VPN互通,并实现流量工程。
网络拓扑如下 :
环境准备:
proxmox 里配置六台虚拟机, 并且每一台都需要设置三张网卡 ens19,ens20,ens21
对于每一台, 首先设置 允许系统进行路由转发(临时的, 重启失效): sysctl -w net.ipv4.ip_forward=1 sysctl -w net.ipv6.conf.all.forwarding=1
之后每台机器需要安装wireshark来进行抓包: apt install -y wireshark
使用wireshark指令来启动
需要iproute2来设定路由 apt install iproute2
接下来是重头戏, 进入每一台主机来进行配置:
主机配置:
需要用到的SRV6 操作:
- End 和 End.X: SL-1, 下一跳转发
- End.DX4 和 End.DX6: 去掉IPv6 报头, 将内部v4/v6数据包转发下一跳
- End.B6 和 End.B6.Encaps: 插入新的 SRH/ SRH+IPv6 报头
- T.Encaps: 在中转点上外加 SRH+IPv6 报头
要实现目标, 我们按下面的步骤工作:
- R1 配置 T.Encaps, 封装 SIP+IPv6
- R1 配置 End.DX4, 把发往 a 的包解析出来, 用IPv4 发给 a
- R3 配置 End, 将 SL-1 获取下一路径
- R4 配置 End.DX4, 把发往 b 的包解析出来, 用IPv4 发给 b
- R4 配置 T.Encaps, 封装 SIP + IPv6 上面的步骤完成后, a 和 b 就能用SRv6 进行通信了
也就是说, 一条通信链路的两端需要 T.Encaps 和 End.DX4/6, 中间的点 End即可
实现过程:
A:
ip addr add 30.30.30.1/24 dev ens19
这一步设定主机 A 的ens19网卡的ipv4地址
ip route add 20.20.20.0/24 via 30.30.30.2
设定只要是目的地为 20.20.20.0/24 的数据包都发给 30.30.30.2
R1:
ip addr add 30.30.30.2/24 dev ens19 ip addr add 2000:2001::1001/120 dev ens21 ip addr add 2000:2003::1001/120 dev ens20 ip addr add 3000::11/128 dev lo
上面是设定各网卡ip地址, ens19配置为ipv4 用于与 A 通信, 其余两个口设定ipv6 地址来用于SRv6 通信
ip route add 20.20.20.0/24 encap seg6 mode encap segs 3000::2,3000::4 dev ens21
这一步是将发送往20.20.20.0/24的 IPv4 数据包封装为 SRv6数据包
指令内容: 设定发往 20.20.20.0/24 的数据包, 通过 T.Encaps 操作封装, 加上 SRH 和 IPv6头部, segment list为 [3000::2,3000::4], 并且下一步从ens21发出
ip -6 route add 3000::1/128 encap seg6local action End.DX4 nh4 30.30.30.1 dev ens19
这一步的目的是发送到 R1 的SRv6包解包, 发送IPv4给A
指令内容: 设定目的地位3000::1/128的包 通过End.DX4 解包, 去除 SRH和IPv6头部, 通过ens19 发送给30.30.30.1
ip -6 route add 3000::2 via 2000:2001::1002
设定只要是目的地为 3000::2 的数据包都发给 2000:2001::1002
R2:
ip addr add 2000:2001::1002/120 dev ens19 ip addr add 2000:2002::1002/120 dev ens20 ip addr add 3000::22/128 dev lo
设定 ip
ip -6 route add 3000::2/128 encap seg6local action End dev ens20
目的地为3000::2/128 的数据包, 通过End操作, 从 Segment List中取出下一跳的地址, 并发送数据包
ip -6 route add 3000::4 via 2000:2002::1004
设定只要是目的地为 3000::4 的数据包都发给 2000:2002::1004
R3:
R3 和 R2 配置是一个道理, 不解释了
ip addr add 2000:2003::1003/120 dev ens19 ip addr add 2000:2004::1003/120 dev ens20 ip addr add 3000::33/128 dev lo
ip -6 route add 3000::3/128 encap seg6local action End dev ens19 ip -6 route add 3000::1/128 via 2000:2003::1001
R4:
ip addr add 20.20.20.2/24 dev ens21 ip addr add 2000:2002::1004/120 dev ens19 ip addr add 2000:2004::1004/120 dev ens20 ip addr add 3000::44/128 dev lo
ip -6 route add 3000::4/128 encap seg6local action End.DX4 nh4 20.20.20.1 dev ens21
指令内容: 设定目的地位3000::4/128的包 通过End.DX4 解包, 去除 SRH和IPv6头部, 通过ens19 发送给20.20.20.1
ip route add 30.30.30.0/24 encap seg6 mode encap segs 3000::3,3000::1 dev ens20
指令内容: 设定发往 30.30.30.0/24 的数据包, 通过 T.Encaps 操作封装, 加上 SRH 和 IPv6头部, segment list为 [3000::3,3000::1], 并且下一步从ens20发出
ip -6 route add 3000::3 via 2000:2004::1003
B:
ip addr add 20.20.20.1/24 dev ens19 ip route add 30.30.30.0/24 via 20.20.20.2
测试:
在 B上 ping A:
ping 30.30.30.1
各机器上抓包ICMP
B:
B 发出一个普通的 IPv4 数据包
R4:
在R4 上, 执行了我们设置的 T.Encaps操作, 封装出一个 SRv6 的数据包, IPv6 头部里包含了一个 Routing Header, 并且将我们设定的 segment list的最后一跳复制到 IPv6头部的 目标地址当中, 进行下一跳转发
R3:
在 R3 上, 执行了我们设置的 End 操作, 弹出下一个 Segment List, 复制到 IPv6 头部目标地址, 进行下一跳转发 ( 我们在下一跳可以看到这一步处理完的包)
R1:
看到上一步处理好的包了, 可以看到SL自减,segment list向前指了一个.并且目标地址也得到了更新
P4 介绍
#SRV6
现有的SDN解决方案为用户开放的是控制平面的可编程能力,那转发平面呢? P4 解决了这个问题.
What is P4
P4 是一种特定领域(domain-specific)的编程语言,用于描述可编程的转发设备如何处理报文, 用 Target 指代要编程的转发设备
P4 本身对 target 的数据平面编程 P4 是协议无关的,由程序员通过编程来使数据平面能够处理各种协议以及其他数据平面功能。
P4 的三种特性:
P4语言在设计之初,就是为了实现以下三个特性:
(1)==协议无关性== *网络设备不与任何特定的网络协议绑定,用户可以使用P4语言描述任何网络数据平面协议和数据包处理行为。*这一特性通过自定义包解析器、匹配-动作表的匹配流程和流控制程序实现。 (2)==目标无关性== 用户不需要关心底层硬件的细节就可实现对数据包的处理方式的编程描述。这一特性通过P4前后端编译器实现,前端编译器将P4高级语言程序转换成中间表示IR,后端编译器将IR编译成设备配置,自动配置目标设备。 (3)==可重构性== 允许用户随时改变包解析和处理的程序 ,并在编译后配置交换机,真正实现现场可重配能力。
为了实现上述特性,P4语言的编译器采用了模块化的设计,各个模块之间的输入输出都采用标准格式的配置文件,如p4c-bm模块的输出作为载入到bmv2模块中的JSON格式配置文件。 [
P4 编程workflow
编译 P4 程序会产生两个交付件:
- 数据平面配置,用于在数据平面实现由 P4 程序指定的转发逻辑。这个交付件可以是二进制的设备固件(例如针对 ASIC);也可以是其他格式的文件,例如运行在 simple_switch 的 P4 程序交付件就是一个 JSON 文件
- API,用于控制平面管理数据平面对象,例如对某个表进行表项添加/删除
用户首先需要自定义数据帧的解析器和流控制程序,其次P4程序经过编译器编译后输出JSON格式的交换机配置文件和运行时的API,再次配置的文件载入到交换器中后更新解析起和匹配-动作表,最后交换机操作系统按照流控制程序进行包的查表操作。 [
以新增VLAN包解析为例,图中解析器除VXLAN以外的包解析是交换机中已有的,载入VXLAN.p4文件所得的配置文件的过程就是交换机的重配置过程。配置文件载入交换机后,解析器中会新增对VXLAN包解析,同时更新匹配-动作表,匹配成功后执行的动作也是在用户自定的程序中指定。执行动作需要交换机系统调用执行动作对应的指令来完成,这时交换机系统调用的是经过P4编译器生成的统一的运行时API,这个API就是交换机系统调用芯片功能的驱动,流控制程序就是指定API对应的交换机指令。
核心概念
为了对网络设备的转发行为进行适当建模,P4 语言定义了如下核心概念:
-
Header types:定义了每个报文中各个报头的格式(所包含的字段和它们的大小)
-
Parsers:描述如何处理所收到报文的包头,这包括包头的解析顺序,从报文中要提取的包头和字段等
-
Tables:将用户定义的 key 和 action 进行关联。P4 的 Tables 对传统的二层交换表进行了泛化,可以用于实现路由表、flow 查找表和用户自定义类型的表
-
Actions:描述如何处理包头的字段以及元数据。Actions 可以包含由控制平面在运行时提供的数据
-
Match-action units:执行以下动作序列
- 根据包头字段或者元数据构建查找 key
- 使用构建的查找 key 在 table 中执行查找,选择一个 action 执行(包括该 action 所包含的数据)
- 执行该动作
-
Control flow:描述在某个 target 上包处理的流程,这包括处理顺序(通常与报文相关)以及要执行的 match-actions。包重组(Deparse)也可以通过 Control flow 实现
-
Extern objects:体系结构相关的组件,可以由 P4 程序通过定义明确的 API 来调用
-
User-defined metadata:与每个报文相关联的用户自定义数据结构
-
Intrinsic metadata:与每个报文相关联的由体系结构提供的元数据,例如接收报文的端口号
P4交换机中也有流水线(pipeline)的概念,一条流水线表示一组完整的数据处理流程,这一概念和传统交换机中的的流水线是相似的。如图3所示,在P4交换机中一条流水线可以包含以下组件:解析器/逆解析器、匹配-动作表、元数据总线。其中除了元数据总线,其他组件都是非必须的。 解析器(parser): 将分组数据转化成元数据。 逆解析器(Deparser): 将元数据转化成序列化的分组数据。 匹配动作表(match-action table): 操作元数据。 元数据(metadata): 在流水线内存储数据信息。 [
项目组件
P4
P4 是一种编程语言
P4C
p4c 就是 P4 语言的参考编译器
bmv2
bmv2(Behavior Model Version 2)是 P4 项目实现的一个 P4 可编程软件交换机 用于开发人员开发、测试、调试 P4 程序 bmv2 项目又不仅只提供一个软件交换机,它是一套框架,通过它开发人员可以实现自己的软件交换机体系结构。因此目前 bmv2 已经提供了几个 target 变体:
- simple_switch:一个 P4 可编程软件交换机,可以运行在通用 CPU 上(Intel/AMD 等)。它遵循 P4_14 语言规范,在 P4_16 中也就是遵循 v1model 体系结构
- simple_switch_grpc:基于 simple_switch,但是其可以接受来自控制器的 TCP 连接,该连接中的控制消息由 P4Runtime 规范制定
- psa_switch:类似于 simple_switch,只不过在 P4_16 中发布了 PSA 体系结构,而 psa_switch 就是以 PSA 为体系结构,而不再是 v1model
P4 Runtime
P4 是用于对数据平面进行编程的语言,它定义了数据平面所支持的功能。但是数据平面仍然需要在运行时接收控制平面下发的控制信息,以指导数据平面对现网实现正确的转发行为。而 P4Runtime 就是一套控制平面规范,用于控制网络设备的转发平面。 P4Runtime 使用 Google 的 Protobuf 定义通信 API,使用 gRPC 作为通信机制,然后又通过一个名为 PI 的项目实现了 P4Runtime Server(运行在数据平面),
P4 安装
#SRV6
一共 需要安装: Protobuf, GRPC, BMv2, PI 和 P4C
便于测试, 我们还安装 mininet 来创建软件网络拓扑
依赖:
设置一个P4 工作目录:
root@ubuntu:~# mkdir ~/P4
root@ubuntu:~# cd ~/P4
root@ubuntu:~/P4#echo "P4_HOME=$(pwd)">>~/.bashrc
root@ubuntu:~/P4#source ~/.bashrc
[[P4 编程测试环境安装 - 知乎#1 安装依赖包]]
apt-get install automake cmake libjudy-dev libpcap-dev libboost-dev libboost-test-dev libboost-program-options-dev libboost-system-dev libboost-filesystem-dev libboost-thread-dev libevent-dev libtool flex bison pkg-config g++ libssl-dev -y
apt-get install cmake g++ git automake libtool libgc-dev bison flex libfl-dev libgmp-dev libboost-dev libboost-iostreams-dev libboost-graph-dev llvm pkg-config python python-scapy python-ipaddr python-ply tcpdump curl -y
apt-get install libreadline6 libreadline6-dev python-pip -y
pip install psutil
pip install crcmod
mininet:
apt install mininet -y
用 mn 命令检验安装成功情况
Protobuf:
root@ubuntu:~/P4# git clone shanwei/protobuf
root@ubuntu:~/P4# cd protobuf
root@ubuntu:~/P4/protobuf# git checkout v3.2.0
root@ubuntu:~/P4/protobuf# export CFLAGS="-Os"
root@ubuntu:~/P4/protobuf# export CXXFLAGS="-Os"
root@ubuntu:~/P4/protobuf# export LDFLAGS="-Wl,-s"
root@ubuntu:~/P4/protobuf# ./autogen.sh
root@ubuntu:~/P4/protobuf# ./configure --prefix=/usr
root@ubuntu:~/P4/protobuf# make -j4
root@ubuntu:~/P4/protobuf# make check -j4
root@ubuntu:~/P4/protobuf# make install
root@ubuntu:~/P4/protobuf# ldconfig
root@ubuntu:~/P4/protobuf# unset CFLAGS CXXFLAGS LDFLAGS
root@ubuntu:~/P4/protobuf# cd python
root@ubuntu:~/P4/protobuf/python# sudo python setup.py install
root@ubuntu:~/P4/protobuf/python# cd ../..
git checkout v3.2.0 和 make check -j 两步很重要
GRPC:
root@ubuntu:~/P4# git clone https://gitee.com/tonysw/grpc.git
root@ubuntu:~/P4# cd grpc/
root@ubuntu:~/P4/grpc# git checkout v1.3.2
root@ubuntu:~/P4/grpc# git submodule update --init --recursive
root@ubuntu:~/P4/grpc# export LDFLAGS="-Wl,-s"
root@ubuntu:~/P4/grpc# make -j4
root@ubuntu:~/P4/grpc# make install
root@ubuntu:~/P4/grpc# ldconfig
root@ubuntu:~/P4/grpc# unset LDFLAGS
root@ubuntu:~/P4/grpc# cd ..
Bmv2 依赖
root@ubuntu:~/P4# git clone p4lang/behavioral-model
root@ubuntu:~/P4# cd behavioral-model
root@ubuntu:~/P4/behavioral-model# git checkout b447ac4c0cfd83e5e72a3cc6120251c1e91128ab
root@ubuntu:~/P4/behavioral-model# tmpdir=`mktemp -d -p .`
root@ubuntu:~/P4/behavioral-model# cd ${tmpdir}
root@ubuntu:~/P4/behavioral-model/tmp.PpKtbm6Jdw# ../travis/install-thrift.sh
root@ubuntu:~/P4/behavioral-model/tmp.PpKtbm6Jdw# ../travis/install-nanomsg.sh
root@ubuntu:~/P4/behavioral-model/tmp.PpKtbm6Jdw# ldconfig
root@ubuntu:~/P4/behavioral-model/tmp.PpKtbm6Jdw# ../travis/install-nnpy.sh
root@ubuntu:~/P4/behavioral-model/tmp.PpKtbm6Jdw# cd ..
root@ubuntu:~/P4/behavioral-model# rm tmp.PpKtbm6Jdw/ -rf
root@ubuntu:~/P4/behavioral-model# cd ..
PI安装:
root@ubuntu:~/P4# git clone https://github.com/p4lang/PI.git
root@ubuntu:~/P4/PI# git checkout 19de33e83bae7b737a3f8a1c9507c6e84173d96f
root@ubuntu:~/P4/PI# git submodule update --init --recursive
root@ubuntu:~/P4/PI# ./autogen.sh
root@ubuntu:~/P4/PI# ./configure --with-proto
root@ubuntu:~/P4/PI# make -j4
root@ubuntu:~/P4/PI# make install
root@ubuntu:~/P4/PI# ldconfig
root@ubuntu:~/P4/PI# cd ..
Bmv2 安装:
root@ubuntu:~/P4# cd behavioral-model
root@ubuntu:~/P4/behavioral-model# ./autogen.sh
root@ubuntu:~/P4/behavioral-model# ./configure --enable-debugger --with-pi
root@ubuntu:~/P4/behavioral-model# make -j4
root@ubuntu:~/P4/behavioral-model# make check -j4
root@ubuntu:~/P4/behavioral-model# ldconfig
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc#
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# ./autogen.sh
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# ./configure --with-thrift
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# make -j4
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# make install
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# ldconfig
root@ubuntu:~/P4/behavioral-model/targets/simple_switch_grpc# cd ../../..
P4C 安装:
root@ubuntu:~/P4# git clone p4lang/p4c
root@ubuntu:~/P4# cd p4c/
root@ubuntu:~/P4/p4c# git checkout 61409c890c58d14ec7d6790f263eb44f393e542a
root@ubuntu:~/P4/p4c# git submodule update --init --recursive
root@ubuntu:~/P4/p4c# mkdir -p build
root@ubuntu:~/P4/p4c# cd build/
root@ubuntu:~/P4/p4c/build# cmake ..
root@ubuntu:~/P4/p4c/build# make -j4
root@ubuntu:~/P4/p4c/build# make check -j4
root@ubuntu:~/P4/p4c/build# make install
root@ubuntu:~/P4/p4c/build# ldconfig
root@ubuntu:~/P4/p4c/build# cd ../..