原文链接: http://www.morfast.net/blog/linux/pppoe-multilink/ ,转载请注明出处,谢谢!
先上一张最终效果图吊吊大家胃口: 是的,这张是普通家用10M小区宽带*10拨后,下载速度实测图。下面正文开始。
大家伙用openwrt一般为了两件事:脱机下载;多拨带宽合并。今天讨论后者的前半部分:多拨。
多拨这个词太口语化了,书面一点的说法是:建立多条PPPOE连接。但由于ISP在PPPOE认证服务器上的限制(注意,完全是PPPOE认证服务器的限制,和地域啊,不同的ISP啊都没关系的,所以很多BBS上讨论XX地区XXISP能不能多拨是没多大意义的),多拨有时候很难成功。表现形式多为只能成功建立一条PPPOE连接,再尝试连接时虽然账号密码都正确,也无法认证成功。
后有网友发现,在有ISP限制的情况下,如果能做到同时多次拨号,有一定的概率能同时建立多条连接。这一般采用脚本同时启动多个pppd进程来实现。经过我的测试,这种方法的确可行,但在连接数多到一定程度时,成功率仍然较低。在我的网络环境下,一般只能成功建立两条连接。有没有办法进一步提高成功率呢?
刚才提到,成功的关键在于“同时”,我们就从这里入手。为什么同时就可以而不同时就不行呢?为什么即使同时也不是百分百成功呢?这里我们抓下认证过程的包,看看PPPOE服务端是如何做的限制:
PPPOE连接的认证过程主要分为两大步:发现阶段和认证阶段。在发现阶段,客户端以广播的方式找到认证服务器。在认证阶段,服务器向客户端询问用户名和密码(challenge),客户端响应用户名和密码(response),最后服务器回应认证成功。
观察抓包结果后我们不难发现,一但有一条连接成功建立(服务端回应Success),后续发送的response就只会得到Failure的回应。于是得到一个很重要的结论: 多个连接成功建立的关键,在于要在服务端回应第一个Success之前,发出所有的chap response 。如果是使用脚本同时开始多个pppd进程,有一定的可能性满足这一条件。但由于开始多个pppd进程后,这些进程的调度完全由操作系统接管了,在shell脚本里不可能对其进行更精确的控制,只能通过pppd进程间的通信与同步来实现。我们要做的是,在pppd要发送response这个点上做同步,让所有pppd进程的response同时发出 。
好在一切都是开源的。 翻翻ppp的源码,和chap认证有关的函数都在pppd文件夹的chap-new.c文件中。再找一下,发现chap_respond函数的注释写得很清楚:
/* chap_respond - Generate and send a response to a challenge. */
这就是我们要找的地方了。在此函数的最后,调用output()函数把reponse发送出去。我们在output()函数之前做同步就行了。也就是说,所有的pppd进程会在这个地方停一下,直到所有的pppd程序都跑到这个地方了,再一起同时继续运行,把response发送出去。
下面是代码。初学进程间通信,代码很乱。两个主要文件:syncpppinit.c编译后为独立程序,做的事是给一个文件lockfile加排它锁,并统计到达同步位置的pppd进程的数目,到达预定数目后解锁文件。syncppp.c文件中的syncppp()函数用于在chap_respond()中做进程同步。它会操作相关信号量以便syncpppinit能正确计数,以及尝试加锁lockfile。当syncpppinit解锁时,所有的pppd进程将加锁成功,同时继续运行并发送respond,从而达到我们的目的。当然这一定不是最好的同步方式。本人菜鸟一个,希望有大牛可以写一个更好的版本:)
1
2
3
4
5
6
7
8
9
10
11
12
13
/* syncppp.h */
#include<semaphore.h>
#define MAX_PPP_NUM 30
#define keyfilename "/tmp/pppkeyfile"
#define lockfilename "/tmp/ppplockfile"
void syncppp( void ) ;
struct semaphores {
sem_t count; /* count the pppd processes which has recieved the challenge */
} ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/* syncpppinit.c
* lock the lockfile, and count pppd, then unlock the lockfile
* to release all pppd
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
#include "initppp.h"
int openlockfile( )
{
int fdlock;
if ( ( fdlock = creat( lockfilename, 0644 ) ) < 0 ) {
perror( "fdlock open error" ) ;
exit( 1 ) ;
}
return fdlock;
}
void lockfile( int fdlock)
{
if ( flock( fdlock, LOCK_EX) < 0 ) {
perror( "flock lock error" ) ;
exit( 1 ) ;
}
fprintf( stderr, "initppp: locked\n " ) ;
}
void unlockfile( int fdlock)
{
if ( flock( fdlock, LOCK_UN) < 0 ) {
perror( "flock unlock error" ) ;
exit( 1 ) ;
}
}
int main( int argc, char * argv[ ] )
{
int shm_id;
int ppp_num;
int fdlock;
sem_t * p_sem;
key_t key;
struct semaphores * semphs;
if ( argc != 2 ) {
fprintf( stderr, "Usage: %s <number of pppd>\n " , argv[ 0 ] ) ;
exit( 1 ) ;
}
ppp_num = atoi( argv[ 1 ] ) ;
if ( ppp_num > MAX_PPP_NUM || ppp_num <= 0 ) {
fprintf( stderr, "Number of pppd beyoung limit\n " ) ;
exit( 1 ) ;
}
/* create a uniqe key */
creat( keyfilename, 0755 ) ;
key = ftok( keyfilename, 4 ) ;
if ( key < 0 ) {
perror( "key error\n " ) ;
exit( 1 ) ;
}
shm_id = shmget( key, sizeof ( struct semaphores) , IPC_CREAT | IPC_EXCL | 0644 ) ;
if ( shm_id < 0 ) {
/* exist */
shm_id = shmget( key, 1 , 0644 ) ;
}
if ( ( void * ) ( semphs = shmat( shm_id, 0 , 0 ) ) == ( void * ) - 1 ) {
perror( "shmat error" ) ;
exit( 1 ) ;
}
if ( sem_init( & ( semphs-> count) , 1 , 0 ) != 0 ) { /* shared between processes, init 0 */
fprintf( stderr, "sem_init error\n " ) ;
return 1 ;
}
fdlock = openlockfile( ) ;
lockfile( fdlock) ;
while ( ppp_num > 0 ) {
sem_wait( & ( semphs-> count) ) ;
fprintf( stderr, "%d " , ppp_num) ;
ppp_num--;
}
unlockfile( fdlock) ;
fprintf( stderr, "\n initppp: unlocked\n " ) ;
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* syncppp.c
* sync all pppd
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<stdlib.h>
#include<semaphore.h>
#include <fcntl.h>
#include <sys/file.h>
#include"initppp.h"
#include <sys/stat.h>
#include<unistd.h>
void syncppp( void )
{
int shm_id;
key_t key;
int fdlock;
struct semaphores * semphs;
key = ftok( keyfilename, 4 ) ;
if ( key < 0 ) {
perror( "key error\n " ) ;
exit( 1 ) ;
}
shm_id = shmget( key, 1 , 0644 ) ;
if ( shm_id < 0 ) {
perror( "shmget" ) ;
exit( 1 ) ;
}
if ( ( void * ) ( semphs = shmat( shm_id, 0 , 0 ) ) == ( void * ) - 1 ) {
perror( "shmat error" ) ;
exit( 1 ) ;
}
sem_post( & ( semphs-> count) ) ;
shmdt( semphs) ;
if ( ( fdlock = open( lockfilename, O_RDONLY, 0644 ) ) < 0 ) {
perror( "lockfile open error" ) ;
exit( 1 ) ;
}
flock( fdlock, LOCK_SH) ;
close( fdlock) ;
}
修改了pppd后,拨号脚本代码段像这样:
1
2
3
4
5
6
7
8
9
10
# run syncpppinit first, PPP_NUM is the number of pppd processes
./ synpppinit ${PPP_NUM} &
# start all pppd with a loop
for i in $( seq -w 01 $PPP_NUM )
do
echo -n "executing pppd for connection ${i} ... "
./ pppd plugin / usr/ lib/ pppd/ 2.4.4/ rp-pppoe.so mtu 1492 mru 1492 nic-eth${i} persist \
usepeerdns user ${USERNAME} password ${PASSWORD} ipparam wan ifname ${PPP_IF_PREFIX} ${i} nodetach &
echo "done"
done
抓个包看看,哈哈,成功了!用这种方式可以稳定拨到15拨以上。
最后是带宽叠加后的效果图:
本文只讨论建立PPPOE连接。而多拨中大家另一个经常问到的问题是负载不够均衡。我后面会再写一篇博文讨论这一问题。