wvdial arm移植笔记

目 录... 2

 

1.     引言... 3

1.1.       目的... 3

1.2.       术语解释... 3

1.3.       主要内容... 3

1.4.       开发环境... 4

1.4.1.        交叉编译环境... 4

1.4.2.        目标机环境... 4

2.     交叉编译... 4

2.1.       所需开发包... 4

2.2.       注意事项... 4

2.3.       zlib. 5

2.3.1.        小结... 5

2.3.2.        具体步骤... 5

2.4.       opensll 6

2.4.1.        小结... 6

2.4.2.        具体步骤... 6

2.5.       wvstreams. 6

2.5.1.        小结... 6

2.5.2.        具体步骤... 6

2.5.3.        解决arm下getcontext调用失败问题... 7

2.6.       wvdial 18

3.     运行测试... 19

3.1.       安装... 19

3.1.1.        目标文件... 19

3.1.2.        整理... 20

3.2.       配置并拨号... 20

3.2.1.        生成默认配置... 20

3.2.2.        调整配置... 21

3.2.3.        拨号... 22

3.2.4.        遗留问题... 23

 


 

1.   引言

1.1. 目的

最近在做ARM上支持3G拨号的相关预研,其中涉及到了对linux下拨号工具的研究,最终测试通过了两种可行的3G 拨号(3G USB上网卡)方案:

1>     pppd+chat,脚本直接调用方式

2>     移植成熟的wvdial工具至ARM开发板

两种方式各有利弊,对于方案一基本没有花费太多时间就测试通过,但对于方案二,几乎可用坎坷和一言难尽来形容;

究其原因,一方面由于网上有关ARM下的3G开发资料较少且大多不完整;另一方面很多遇到了即使移植通过但运行出错的问题且未能解决的情况;当然这与我在此方面的开发经验少可能有更大的关系;

总之是走了不少弯路,故整理此文,给后续有此开发需求的童鞋借鉴。

1.2. 术语解释

[ wvdial ]

         是一个简单好用的Linux 下的拨号工具。它完全不需要chat scripts,也不用你去编辑pap-secrets 和chap-secrets,它完全能够自动发现你modem ,并自动选择合适的初始化串。利用wvdial 和ppp 可以实现linux 下的轻松上网。在整个过程中wvdial 的作用是拨号并等待提示,并根据提示输入相应的用户名和密码等认证信息。

[ 3G 拨号]

         详见我之前整理的:3G常识资料整理.doc

1.3. 主要内容

对于内核支持3G USB网卡的编译选项不再此文列出;

对于3G/USB的相关驱动及转换等不再此文列出;

仅对wvdial的ARM移植过程/运行使用及遇到的问题做下记录说明,总体分两方面阐述:

1>     交叉编译wvdial程序及各依赖的库

2>     目标机上运行使用,及使用wvdial测试3G拨号

1.4. 开发环境

1.4.1.   交叉编译环境

[arm-linux-gcc -v]

Reading specs from /arm/ToolSet/tools.Linux/hard_fp/bin/../lib/gcc-lib/armv5-linux/3.3.1/specs

Configured with: /opt/mcs1000/tools/src/gcc-3.3.1/configure --target=armv5-linux --build=i686-pc-linux-gnu --prefix=/home/prj/scp/tools/hard_fp --program-pr

efix=arm-linux- --with-libraries=/home/prj/scp/tools/hard_fp/lib --enable-languages=c,c++

Thread model: posix

gcc version 3.3.1

[uname -a]

Linux localhost.localdomain 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 i686 i686 i386 GNU/Linux

1.4.2.   目标机环境

Processor :  XScale-IXP4xx/IXC11xx rev 2 (v5l)

Kernel       :  2.4.20 arm

                     (Linux UNINITIALIZED 2.4.20_mvl31-ixdp4xx-arm_xscale_le armv5tel unknown)

2.   交叉编译

 

2.1.   所需开发包

因wvdial工具依赖于 wvstreams库,而此库又依赖于openssl和zlib库

1>     zlib-1.2.5.tar.bz2

2>     openssl-0.9.8n.tar.gz, openssl-0.9.8n-fix_manpages-1.patch

3>     wvstreams-4.6.1.tar.gz

4>     wvdial-1.61.tar.gz

2.2.  注意事项

交叉编译一些工具时,我们经常遇到明明网上有时有基本的步骤的,但按步骤下来到自己这还是编不过,就我遇到的问题来讲,大多与不同的交叉编译工具链有关,如:

1>     工具链做的有问题,比如编译工具链时少了某些链接步骤等

2>     工具链gcc版本不同

3>     工具链中缺少安装了了要编译的目标工具所依赖的一些头文件以及库

发现以及解决这些问题,我的经验是:

1>     借助强大的google

2>     注意分析configure时的日志文件:config.log

另外,编译前请记得将你的交叉编译器的路径加入到PATH环境变量中,除非默认的PATH就可以找到它,如export PATH=$PATH:your-arm-gcc-path

特别注意,交叉编译环境上对于安装路径的配置,由于各代码的Makefile可能对install采取的策略不同,因此并不是所有的地方都可以通过./configure --prefix=your-install-path, 在没有设置好之前就make install,有可能覆盖你原编译主机的东西,为安全,最好在执行make install之前,使用make -n install检查下安装路由是否符合预期

2.3.  zlib

2.3.1.   小结

只要不是你当前的编译环境上的ARM工具链不完整,基本上zlib和openssl都会很成功

2.3.2.   具体步骤

 

tar xjf zlib-1.2.5.tar.bz2

cd zlib-1.2.5

./configure --prefix=$PWD/../_install

         #此处,我将目录安装文件都放在了上层目录的_install下

修改Makefile

         CC = arm-linux-gcc

         CPP=arm-linux-gcc -E

         AR=arm-linux-ar rc

         RANLIB=arm-linux-ranlib

make

make install

2.4.  opensll

2.4.1.   小结

2.4.2.   具体步骤

tar xzf openssl-0.9.8n.tar.gz

cd openssl-0.9.8n

patch -Np1 -i ../openssl-0.9.8n-fix_manpages-1.patch

./Configure --prefix=$PWD/../install os/compiler:arm-linux-gcc

make

make install

2.5.  wvstreams

2.5.1.   小结

这是最关键的一步,会产生我们所需要的:

libwvbase.so

libwvstreams.so

libwvutils.so

此库的编译过程中,我遇到了不少问题,也耗了我比较多时间,主要:

1>     configure时死活不过 - 基本上看config.log能发现问题所在,再根据具体问题找解决办法

2>     编完后运行才发现arm体系下有不支持的调用 - 需要改源码,在中解决arm下getcontext调用失败问题描述

2.5.2.   具体步骤

tar xzf wvstreams-4.6.1.tar.gz

cd wvstreams-4.6.1

修改configure ##这步记得一定要做,否则在configure时会有错误

         with_zlib=no改为with_zlib=yes(有两处)

         with_openssl=no      改为         with_openssl=yes     (有两处)

./configure --prefix=$PWD/../_install --host=arm-linux \

         CPPFLAGS=-I$PWD/../_install/include LDFLAGS=-L$PWD/../_install/lib \

         CFLAGS=-I$PWD/../_install/include --with-zlib=$PWD/../zlib-1.2.5 \

         --with-openssl=$PWD/../openssl-0.9.8n \

         --without-dbus --with-pam=no --with-tcl=no --with-qt=no --without-valgrind

make

make install

这步的关键是configure那步,只要过了基本后边是没有问题的,比如我遇到的检测说boost/***找不到,重新安装了下boost库至交叉编译工具链路径

2.5.3.   解决arm下getcontext调用失败问题

第一次编译时,基本上没想到会有在arm下还存在某些不支持的调用的情况,所以解决完所有问题编过后便很是happy;

后来在遇到运行出错时很是郁闷,网上能搜到的讨论基本全英文的,且都说没有解决办法,曾一度想要否定此方案,不过最终感谢强大的google以及某某不知名的大侠,修改源码后顺利解决,hoho~

[ 问题 ]

-----------

wvdial evdo

         --> WvDial: Internet dialer version 1.61

         --> Cannot get information for serial port.

         --> Initializing modem.

         --> Sending: ATZ

         ATZ

         OK

         --> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0

         ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0

         OK

         --> Modem initialized.

         wvdial: utils/wvtask.cc:202: WvTaskMan::WvTaskMan(): Assertion `getcontext(&get_stack_return) == 0' failed.

         Aborted

-----------

以标红处的关键字来搜,会发现很多评论,且结论是没办法解决的,如:

https://bugs.launchpad.net/ubuntu/+source/wvstreams/+bug/383978

[ 解决办法 ]

修改源码,用jmp_buf代替ucontext_t的相关实现,具体见下边;

改完后重编2.5.2重编库文件

--- include/wvtask.h.orig Mon Jul 14 13:11:35 2008

+++ include/wvtask.h Mon Nov 23 13:10:28 2009

@@ -50,12 +50,10 @@

 

int tid;

size_t stacksize;

 - void *stack;

bool running, recycled;

 WvTaskMan &man;

- ucontext_t mystate; // used for resuming the task

 - ucontext_t func_call, func_return;

 + jmp_buf mystate; // used for resuming the task

TaskFunc *func;

void *userdata;

 

@@ -91,16 +89,15 @@

 static void stackmaster();

 static void _stackmaster();

 static void do_task();

 - static void call_func(WvTask *task);

 static char *stacktop;

 - static ucontext_t stackmaster_task;

 + static jmp_buf stackmaster_task;

 static WvTask *stack_target;

 - static ucontext_t get_stack_return;

 + static jmp_buf get_stack_return;

static WvTask *current_task;

- static ucontext_t toplevel;

 + static jmp_buf toplevel;

 WvTaskMan();

 virtual ~WvTaskMan();

 

 --- utils/wvtask.cc.orig Wed May 13 15:42:52 2009

 +++ utils/wvtask.cc Mon Nov 23 13:12:54 2009

 @@ -32,10 +32,6 @@

 #include

 #include

 #include

 -#include

 -#include

 -#include

 -#include

 #ifdef HAVE_VALGRIND_MEMCHECK_H

 #include

 @@ -60,7 +56,7 @@

 

WvTaskMan *WvTaskMan::singleton;

 int WvTaskMan::links, WvTaskMan::magic_number;

 WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;

 -ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,

 +jmp_buf WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,

 WvTaskMan::toplevel;

 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;

 char *WvTaskMan::stacktop;

 

@@ -198,9 +194,7 @@

 stacktop = (char *)alloca(0);

 - context_return = 0;

 - assert(getcontext(&get_stack_return) == 0);

 - if (context_return == 0)

 + if (setjmp(get_stack_return) == 0)

 {

 // initial setup - start the stackmaster() task (never returns!)

 stackmaster();

 

 @@ -257,22 +251,18 @@

 WvTask *old_task = current_task;

 current_task = &task;

 - ucontext_t *state;

 + jmp_buf *state;

 

if (!old_task)

 state = &toplevel; // top-level call (not in an actual task yet)

 else

 state = &old_task->mystate;

  - context_return = 0;

 - assert(getcontext(state) == 0);

 - int newval = context_return;

 + int newval = setjmp(*state);

 if (newval == 0)

 {

 // saved the state, now run the task.

 - context_return = val;

 - setcontext(&task.mystate);

 - return -1;

 + longjmp(task.mystate, val);

 }

 else

 {

 @@ -303,8 +293,6 @@

 assert(*current_task->stack_magic == WVTASK_MAGIC);

 #if TASK_DEBUG

 - if (use_shared_stack())

 - {

 size_t stackleft;

 char *stackbottom = (char *)(current_task->stack_magic + 1);

 for (stackleft = 0; stackleft < current_task->stacksize;

 stackleft++)

 

@@ -315,18 +303,13 @@

 Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld

 \n",

 current_task->tid, current_task->name.cstr(), (long)

 stackleft,

 (long)current_task->stacksize);

 - }

 #endif

 

 - context_return = 0;

 - assert(getcontext(¤t_task->mystate) == 0);

 - int newval = context_return;

 + int newval = setjmp(current_task->mystate);

 if (newval == 0)

 {

 // saved the task state; now yield to the toplevel.

 - context_return = val;

 - setcontext(&toplevel);

 - return -1;

 + longjmp(toplevel, val);

 }

 else

 {

 

@@ -340,39 +323,14 @@

 

void WvTaskMan::get_stack(WvTask &task, size_t size)

 {

 - context_return = 0;

 - assert(getcontext(&get_stack_return) == 0);

 - if (context_return == 0)

 + if (setjmp(get_stack_return) == 0)

 {

 assert(magic_number == -WVTASK_MAGIC);

 assert(task.magic_number == WVTASK_MAGIC);

 - if (!use_shared_stack())

 - {

 -#if defined(__linux__) && (defined(__386__) || defined(__i386) ||

 defined(__i386__))

 - static char *next_stack_addr = (char *)0xB0000000;

 - static const size_t stack_shift = 0x00100000;

 - next_stack_addr -= stack_shift;

 -#else

 - static char *next_stack_addr = NULL;

 -#endif

 - task.stack = mmap(next_stack_addr, task.stacksize,

 - PROT_READ | PROT_WRITE,

 -#ifndef MACOS

 - MAP_PRIVATE | MAP_ANONYMOUS,

 -#else

 - MAP_PRIVATE,

 -#endif

 - -1, 0);

 - }

 -

 // initial setup

 stack_target = &task;

 - context_return = size/1024 + (size24 > 0);

 - setcontext(&stackmaster_task);

 - }

 + longjmp(stackmaster_task, size/1024 + (size24 > 0));

 + }

 else

 {

 if (current_task)

 @@ -408,9 +366,7 @@

 {

 assert(magic_number == -WVTASK_MAGIC);

 - context_return = 0;

 - assert(getcontext(&stackmaster_task) == 0);

 - val = context_return;

 + val = setjmp(stackmaster_task);

 if (val == 0)

 {

 assert(magic_number == -WVTASK_MAGIC);

 @@ -418,19 +374,13 @@

 // just did setjmp; save stackmaster's current state (with

 // all current stack allocations) and go back to get_stack

 // (or the constructor, if that's what called us)

 - context_return = 1;

 - setcontext(&get_stack_return);

 + longjmp(get_stack_return, 1);

 }

 else

 {

 valgrind_fix(stacktop);

 assert(magic_number == -WVTASK_MAGIC);

 - total = (val+1) * (size_t)1024;

 - if (!use_shared_stack())

 - total = 1024; // enough to save the do_task stack

 frame

 // set up a stack frame for the new task. This runs once

 // per get_stack.

 //alloc_stack_and_switch(total);

 

@@ -439,6 +389,7 @@

 assert(magic_number == -WVTASK_MAGIC);

 

// allocate the stack area so we never use it again

 + total = (val+1) * (size_t)1024;

 alloca(total);

 // a little sentinel so we can detect stack overflows

 

 @@ -455,17 +406,6 @@

 }

 -void WvTaskMan::call_func(WvTask *task)

 -{

 - Dprintf("WvTaskMan: calling task #%d (%s)\n",

 - task->tid, (const char *)task->name);

 - task->func(task->userdata);

 - Dprintf("WvTaskMan: returning from task #%d (%s)\n",

 - task->tid, (const char *)task->name);

 - context_return = 1;

 -}

 

-void WvTaskMan::do_task()

 {

 assert(magic_number == -WVTASK_MAGIC);

 @@ -473,9 +413,7 @@

 assert(task->magic_number == WVTASK_MAGIC);

 // back here from longjmp; someone wants stack space.

 - context_return = 0;

 - assert(getcontext(&task->mystate) == 0);

 - if (context_return == 0)

 + if (setjmp(task->mystate) == 0)

 {

 // done the setjmp; that means the target task now has

 // a working jmp_buf all set up. Leave space on the stack

 

 @@ -501,31 +439,11 @@

 if (task->func && task->running)

 {

 - if (use_shared_stack())

 - {

 // this is the task's main function. It can call

 yield()

 // to give up its timeslice if it wants. Either

 way, it

 // only returns to *us* if the function actually

 finishes.

 task->func(task->userdata);

 - }

 - else

 - {

 - assert(getcontext(&task->func_call) == 0);

 - task->func_call.uc_stack.ss_size = task-

 >stacksize;

 - task->func_call.uc_stack.ss_sp = task->stack;

 - task->func_call.uc_stack.ss_flags = 0;

 - task->func_call.uc_link = &task->func_return;

 - Dprintf("WvTaskMan: makecontext #%d (%s)\n",

 - task->tid, (const char *)task->name);

 - makecontext(&task->func_call,

 - (void (*)(void))call_func, 1, task);

 -

 - context_return = 0;

 - assert(getcontext(&task->func_return) == 0);

 - if (context_return == 0)

 - setcontext(&task->func_call);

 - }

 // the task's function terminated.

 task->name = "DEAD";

 task->running = false;

 @@ -541,10 +459,7 @@

{

#ifdef HAVE_LIBC_STACK_END

extern const void *__libc_stack_end;

- if (use_shared_stack() || current_task == NULL)

return __libc_stack_end;

- else

- return (const char *)current_task->stack + current_task-

>stacksize;

#else

return 0;

#endif

 

@@ -553,16 +468,7 @@

 

size_t WvTaskMan::current_stacksize_limit()

{

- if (use_shared_stack() || current_task == NULL)

- {

- struct rlimit rl;

- if (getrlimit(RLIMIT_STACK, &rl) == 0)

- return size_t(rl.rlim_cur);

- else

return 0;

- }

- else

- return size_t(current_task->stacksize);

}

 

2.6.  wvdial

tar xzf wvdial-1.61.tar.gz

cd wvdial-1.61

./configure #会生成Makefile

修改Makefile

         prefix=/usr/local

         改为

         prefix=${PWD}/../_install

        

         PPPDIR=/etc/ppp/peers

         改为

         PPPDIR=${prefix}/etc/ppp/peers

        

         PC_CFLAGS=$(shell pkg-config --cflags libwvstreams)

         改为

         PC_CFLAGS=-I${PWD}/../_install/include/wvstreams

        

         PC_LIBS=$(shell pkg-config --libs libwvstreams)

         PC_LIBS=-L${PWD}/../_install /lib -lwvstreams -lwvutils -lwvbase -lz

        

修改wvrules.mk

         WVLINK_CC = gcc (如果没有此句则直接添加)

         改为

         WVLINK_CC = arm-linux-g++

         CC = arm-linux-g++

         CXX = arm-linux-g++

make

make install

3.   运行测试

3.1.  安装

3.1.1.   目标文件

最后检查_install目录下的所有东西,我的安装目录如下:

[SCP_LINUX_HFP: wvdial ]$ ls _install/*

_install/bin:

c_rehash  openssl  uni  wvdial  wvdialconf  wvtestrun

 

_install/etc:

ppp  uniconf.conf

 

_install/include:

openssl  wvstreams  zconf.h  zlib.h

 

_install/lib:

engines      libssl.a       libuniconf.so.4.6  libwvbase.so.4.6  libwvstreams.so

libcrypto.a  libuniconf.so  libwvbase.so       libwvstatic.a     libwvstreams.so

 

_install/sbin:

uniconfd

 

_install/share:

man

 

_install/ssl:

certs  man  misc  openssl.cnf  private

 

_install/var:

lib

3.1.2.   整理

_install目录下产生的库及文件等都是没有strip和去掉静态链接库的,为节省空间,去掉运行环境上不需要的文件及库,最终生成如下:

[SCP_LINUX_HFP: _install_arm ]$ ls ./*

./bin:

c_rehash  uni  wvdial  wvdialconf  wvtestrun

 

./etc:

ppp  uniconf.conf

 

./lib:

libuniconf.so      libwvstreams.so      libz.so        valgrind

libuniconf.so.4.6  libwvstreams.so.4.6  libz.so.1

libwvbase.so       libwvutils.so        libz.so.1.2.5

libwvbase.so.4.6   libwvutils.so.4.6    pkgconfig

 

./sbin:

uniconfd

压缩再解压至目标运行机即可

3.2.  配置并拨号

3.2.1.   生成默认配置

运行wvdialconf,可自动生成默认配置/etc/wvdial.conf,我的板子上输出如下

/usr/sbin # wvdialconf

Editing `/etc/wvdial.conf'.

 

Scanning your serial ports for a modem.

 

WvModem<*1>: Cannot get information for serial port.

ttyUSB0<*1>: ATQ0 V1 E1 -- OK

ttyUSB0<*1>: ATQ0 V1 E1 Z -- OK

ttyUSB0<*1>: ATQ0 V1 E1 S0=0 -- OK

ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 -- OK

ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 -- OK

ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK

ttyUSB0<*1>: Modem Identifier: ATI -- Manufacturer: +GMI: HUAWEI TECHNOLOGIES CO., LTD

ttyUSB0<*1>: Speed 4800: AT -- OK

ttyUSB0<*1>: Speed 9600: AT -- OK

ttyUSB0<*1>: Speed 19200: AT -- OK

ttyUSB0<*1>: Speed 38400: AT -- OK

ttyUSB0<*1>: Speed 57600: AT -- OK

ttyUSB0<*1>: Speed 115200: AT -- OK

ttyUSB0<*1>: Speed 230400: AT -- OK

ttyUSB0<*1>: Speed 460800: AT -- OK

ttyUSB0<*1>: Max speed is 460800; that should be safe.

ttyUSB0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK

 

Found a modem on /dev/ttyUSB0.

Modem configuration written to /etc/wvdial.conf.

ttyUSB0: Speed 460800; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"

3.2.2.   调整配置

需要注意的是,对于波特率,此出测出的是460800,但实测发现460800对比上网速度不如115200,因此不要太迷信其测试结果,具体的参数还需要自己来调整配置

附我的配置文件:

[Dialer Defaults]

Modem Type = USB Modem

ISDN = 0

Init1 = ATZ

Modem = /dev/ttyUSB0

;Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0

Baud = 115200

 

[Dialer evdo]

Phone = #777

Username = card

Password = card

Carrier Check = on

Check Def Route = on

Dial Attempts = 1

Auto Reconnect = on

Abort on Busy = on

Check Def Route = on

Abort on No Dialtone = on

Stupid Mode = on

Idle Seconds = 0

Auto DNS = on

3.2.3.   拨号

开始拨号:

/usr/sbin # wvdial evdo

--> WvDial: Internet dialer version 1.61

--> Cannot get information for serial port.

--> Initializing modem.

--> Sending: ATZ

ATZ

OK

--> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0

ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0

OK

--> Modem initialized.

--> Sending: ATDT#777

--> Waiting for carrier.

ATDT#777

CONNECT

--> Carrier detected.  Starting PPP immediately.

--> Starting pppd at Fri Mar 25 12:57:39 2011

--> Pid of pppd: 24546

--> Using interface ppp0

--> local  IP address 113.112.238.66

--> remote IP address 115.168.82.68

--> primary   DNS address 202.96.128.86

--> secondary DNS address 220.192.32.103

停止拨号:ctrl+c即可

Caught signal 2:  Attempting to exit gracefully...

--> Terminating on signal 15

--> Connect time 0.2 minutes.

--> Disconnecting at Fri Mar 25 12:57:51 2011

3.2.4.   遗留问题

1.       当然这只是一些初步的配置及参数,目前的配置情况下wvdial尙不能自动检测掉线,应该有配置方式,但后续的基本都google一大把的;

2.       另外wvdial会自动将账户写入/etc/ppp/pap-secrets和/etc/ppp/chap- secrets

当存在一台机有其它类型拨号时,需注意测试是否会有影响

3.       拨号会pppd决定了,会将dns写入/etc/ppp/resolv.conf而非系统的/etc/resolv.conf,需要自行实现拨上号后修改dns功能

建议借助/etc/ppp/ip-up(pppd拨上号会回调此脚本)/ip-down来实现动态修改

你可能感兴趣的:(LINUX,工具,linux,makefile,测试,returning,internet)