IOS 笔记之 网络代理

来源:互联网 发布:js年月日时间轴 编辑:程序博客网 时间:2024/06/07 05:11

前一段时间做了一个功能:

在App内添加联通服务器代理,实现定向流量的功能。

成果如下:

这里添加的代理分两类:HTTP代理和TCP代理

(1)HTTP代理

对于HTTP代理,走的是HTTP标准代理协议,但是在IOS上面实现起来并不是那么简单,App内可能使用了多种封装的网络请求,还有一些第三方的类库,都不知道内部使用了什么方式联网,基于OC,大体分两种,一种是直接调用CFHTTP实现的;一种是基于系统封装类NSURLConnection实现的。

那么问题来了,基于系统封装类NSURLConnection实现的,是不提供接口设置代理的,尝试过各种方法,包括强行将代理塞入到Header中,或者使用NSURLProtocol拦截,都是无效的,所以使用此方式且不好修改的,就果断放弃吧。(典型代表:AFNetwork,MKNetwork)

对于直接使用CFHTTP实现的请求方式,属于较底层的实现方式,corefoundation是提供了添加代理接口的。(典型代表:ASIHTTPRequest)

最终,把联网请求修改成了ASI方式,虽然ASI已经停止更新,但是由于ASI的实现方式偏底层,非常稳定,并且便于深度定制自己的网络请求模块。

对于ASI的代理设置方式,很简单:

//设置代理    if ([self shouldSetProxy]) {        [request setProxyHost:"your host"];        [request setProxyPort:8080];        NSMutableDictionary * dic = [NSMutableDictionary dictionaryWithCapacity:2];        [dic setObject:@"your value" forKey:@"Authorization"];        [dic setObject:@"Keep-Alive" forKey:@"Proxy-Connection"];        [request setRequestHeaders:dic];    }

另外,对于ASI和AFN,这位兄弟分析的很好,感谢他:http://www.infoq.com/cn/articles/afn_vs_asi/

层次结构可以看一下:


(2)TCP代理

这个代理的设置比较有意思,或者说由于刚开始对于TCP/IP和SOCKET不熟悉,像没头苍蝇一样抓狂了好几天,有意思到想大喊一声:法克!

最终问题解决了,由于联通的TCP连接并不走标准sock5代理协议,只是按照格式发送一个数据流,也让我郁闷到想吐血。

首先通俗的介绍一下设置socket代理的原理:

socket是建立TCP连接的方式,三次握手之后,与代理服务器建立TCP连,接这是第一步。

此时,可以与代理服务器通信了,下面就是按照协议发送代理认证信息和目的服务器信息。如果采取sock5协议,需要先发送连接请求,查看服务器返回状态码,若需要验证,发送验证信息,若不需要验证或者验证通过,此时继续发送目的服务器相关信息。代码如下:

其中使用了第三方类库 GCDAsyncSocket

/** * Sends the SOCKS5 open/handshake/authentication data, and starts reading the response. * We attempt to gain anonymous access (no authentication). **/- (void)socksOpen{    //XMPPLogTrace();        //      +-----+-----------+---------+    // NAME | VER | NMETHODS  | METHODS |    //      +-----+-----------+---------+    // SIZE |  1  |    1      | 1 - 255 |    //      +-----+-----------+---------+    //    // Note: Size is in bytes    //    // Version    = 5 (for SOCKS5)    // NumMethods = 1    // Method     = 0 (No authentication, anonymous access)        NSUInteger byteBufferLength = 3;    uint8_t *byteBuffer = malloc(byteBufferLength * sizeof(uint8_t));        uint8_t version = 5; // VER    byteBuffer[0] = version;        uint8_t numMethods = 1; // NMETHODS    byteBuffer[1] = numMethods;        uint8_t method = 0; // METHODS    byteBuffer[2] = method;        NSData *data = [NSData dataWithBytesNoCopy:byteBuffer length:byteBufferLength freeWhenDone:YES];    DLog(@"TURNSocket: SOCKS_OPEN: %@", data);        [asyncSocket writeData:data withTimeout:-1 tag:SOCKS_OPEN];        //      +-----+--------+    // NAME | VER | METHOD |    //      +-----+--------+    // SIZE |  1  |   1    |    //      +-----+--------+    //    // Note: Size is in bytes    //    // Version = 5 (for SOCKS5)    // Method  = 0 (No authentication, anonymous access)    /* Method:     o  X'00' NO AUTHENTICATION REQUIRED     o  X'01' GSSAPI     o  X'02' USERNAME/PASSWORD     o  X'03' to X'7F' IANA ASSIGNED     o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS     o  X'FF' NO ACCEPTABLE METHODS     */        [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ tag:SOCKS_OPEN];}-(void)socksAuthentication{    if (self.proxyUsername.length < 1 || self.proxyPassword.length < 1) {        DDLogCError(@"no proxyUsername or proxyPassword");        return;    }    /*     +----+------+----------+------+----------+     NAME    |VER | ULEN |  UNAME   | PLEN |  PASSWD  |     +----+------+----------+------+----------+     SIZE    | 1  |  1   | 1 to 255 |  1   | 1 to 255 |     +----+------+----------+------+----------+          The VER field contains the current version of the subnegotiation,     which is X'01'. The ULEN field contains the length of the UNAME field     that follows. The UNAME field contains the username as known to the     source operating system. The PLEN field contains the length of the     PASSWD field that follows. The PASSWD field contains the password     association with the given UNAME.     */        NSUInteger byteBufferLength = 1 + 1 + self.proxyUsername.length + 1 + self.proxyPassword.length;    uint8_t *byteBuffer = malloc(byteBufferLength * sizeof(uint8_t));    NSUInteger offset = 0;        uint8_t version = 1; // VER    byteBuffer[offset] = version;    offset++;        uint8_t userNameLength = self.proxyUsername.length;    byteBuffer[offset] = userNameLength;    offset++;        NSData *userNameData = [self.proxyUsername dataUsingEncoding:NSUTF8StringEncoding];    memcpy(byteBuffer+offset, [userNameData bytes], userNameLength);    offset += userNameLength;        uint8_t passwordLength = self.proxyPassword.length;    byteBuffer[offset] = passwordLength;    offset++;        NSData *passwordData = [self.proxyPassword dataUsingEncoding:NSUTF8StringEncoding];    memcpy(byteBuffer+offset, [passwordData bytes], passwordLength);        NSData *data = [NSData dataWithBytesNoCopy:byteBuffer length:byteBufferLength freeWhenDone:YES];    [asyncSocket writeData:data withTimeout:-1 tag:SOCKS_AUTHENTICATION];        /*     +----+--------+     NAME |VER | STATUS |     +----+--------+     SIZE | 1  |   1    |     +----+--------+          A STATUS field of X'00' indicates success. If the server returns a     `failure' (STATUS value other than X'00') status, it MUST close the     connection.     */    [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ tag:SOCKS_AUTHENTICATION];}/** * Sends the SOCKS5 connect data (according to XEP-65), and starts reading the response. **/- (void)socksConnect{    //      +-----+-----+-----+------+------+------+    // NAME | VER | CMD | RSV | ATYP | ADDR | PORT |    //      +-----+-----+-----+------+------+------+    // SIZE |  1  |  1  |  1  |  1   | var  |  2   |    //      +-----+-----+-----+------+------+------+    //    // Note: Size is in bytes    //    // Version      = 5 (for SOCKS5)    // Command      = 1 (for Connect)    // Reserved     = 0    // Address Type = 3 (1=IPv4, 3=DomainName 4=IPv6)    // Address      = P:D (P=LengthOfDomain D=DomainWithoutNullTermination)    // Port         = 0        NSUInteger hostLength = [self.hostName length];    NSData *hostData = [self.hostName dataUsingEncoding:NSUTF8StringEncoding];    NSUInteger byteBufferLength = (uint)(4 + 1 + hostLength + 2);    uint8_t *byteBuffer = malloc(byteBufferLength * sizeof(uint8_t));    NSUInteger offset = 0;        // VER    uint8_t version = 0x05;    byteBuffer[0] = version;    offset++;        /* CMD     o  CONNECT X'01'     o  BIND X'02'     o  UDP ASSOCIATE X'03'     */    uint8_t command = 0x01;    byteBuffer[offset] = command;    offset++;        byteBuffer[offset] = 0x00; // Reserved, must be 0    offset++;    /* ATYP     o  IP V4 address: X'01'     o  DOMAINNAME: X'03'     o  IP V6 address: X'04'     */    uint8_t addressType = 0x03;    byteBuffer[offset] = addressType;    offset++;    /* ADDR     o  X'01' - the address is a version-4 IP address, with a length of 4 octets     o  X'03' - the address field contains a fully-qualified domain name.  The first     octet of the address field contains the number of octets of name that     follow, there is no terminating NUL octet.     o  X'04' - the address is a version-6 IP address, with a length of 16 octets.     */    byteBuffer[offset] = hostLength;    offset++;    memcpy(byteBuffer+offset, [hostData bytes], hostLength);    offset+=hostLength;    uint16_t port = htons(self.hostPort);    NSUInteger portLength = 2;    memcpy(byteBuffer+offset, &port, portLength);    offset+=portLength;        NSData *data = [NSData dataWithBytesNoCopy:byteBuffer length:byteBufferLength freeWhenDone:YES];    DDLogVerbose(@"TURNSocket: SOCKS_CONNECT: %@", data);        [asyncSocket writeData:data withTimeout:-1 tag:SOCKS_CONNECT];        //      +-----+-----+-----+------+------+------+    // NAME | VER | REP | RSV | ATYP | ADDR | PORT |    //      +-----+-----+-----+------+------+------+    // SIZE |  1  |  1  |  1  |  1   | var  |  2   |    //      +-----+-----+-----+------+------+------+    //    // Note: Size is in bytes    //    // Version      = 5 (for SOCKS5)    // Reply        = 0 (0=Succeeded, X=ErrorCode)    // Reserved     = 0    // Address Type = 3 (1=IPv4, 3=DomainName 4=IPv6)    // Address      = P:D (P=LengthOfDomain D=DomainWithoutNullTermination)    // Port         = 0    //    // It is expected that the SOCKS server will return the same address given in the connect request.    // But according to XEP-65 this is only marked as a SHOULD and not a MUST.    // So just in case, we'll read up to the address length now, and then read in the address+port next.        [asyncSocket readDataToLength:5 withTimeout:TIMEOUT_READ tag:SOCKS_CONNECT_REPLY_1];}

解释:里面的tag是自定义的,用于在收到消息的时候判断发送类型。

而对于自定义类型的TCP代理协议,那就简单了,怎么定义的发送什么就行了,举例如下:

- (void)TCPRequest{    /*     * 发送示例:     CONNECT www.baidu.com:80 HTTP/1.1     Host: www.baidu.com:80     Proxy-Connection: Keep-Alive     Content-Length: 0     Proxy-Authorization: Basic *     * 注意:     * 每一行必须以\r\n结束,最后需要两个\r\n结束,说明字段结束     */        // 拼接消息体    NSString *str = [NSString stringWithFormat:@"CONNECT %@:%d HTTP/1.1\r\n",hostName,hostPort];    str = [str stringByAppendingFormat:@"Host: %@:%d\r\n",hostName,hostPort];    str = [str stringByAppendingFormat:@"Proxy-Connection: Keep-Alive\r\n"];    str = [str stringByAppendingFormat:@"Content-Length: 0\r\n"];    str = [str stringByAppendingFormat:@"%@: %@\r\n\r\n",_proxyUsername,_proxyPassword];    DLog(@"str:\n*********\n%@\n*********",str);        NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];    [asyncSocket writeData:data withTimeout:-1 tag:SOCKS_TCP_AUTH];        /*     若请求成功,服务端将给予xxxxxxxxxxxxx响应     */    [asyncSocket readDataWithTimeout:TIMEOUT_READ buffer:[NSMutableData data] bufferOffset:0 maxLength:1024 tag:SOCKS_TCP_AUTH];    /*     * 说明:     * 返回长度未知,不能使用此方法     */    //[asyncSocket readDataToLength:length withTimeout:TIMEOUT_READ tag:SOCKS_TCP_AUTH];}

若确认自己没有发错信息,看返回验证码就会成功和代理服务器建立连接了,这是第二部分。

收到服务器的返回信息是在这里看,并执行以后的操作:

/** * Called when a socket has completed reading the requested data. Not called if there is an error.**/- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{    {// log data        NSString *result = [NSString stringWithUTF8String:[data bytes]];        DLog(@"data_tag[%ld]_result:\n---------\n%@\n---------",tag,result);    }    if (tag == SOCKS_OPEN)    {        NSAssert(data.length == 2, @"SOCKS_OPEN reply length must be 2!");        // See socksOpen method for socks reply format        uint8_t *bytes = (uint8_t*)[data bytes];        UInt8 ver = bytes[0];        UInt8 mtd = bytes[1];                DDLogVerbose(@"TURNSocket: SOCKS_OPEN: ver(%o) mtd(%o)", ver, mtd);                if(ver == 5 && mtd == 0)        {            [self socksConnect];        }else if (ver == 5 && mtd == 2){            // The proxy requires some kind of authentication.            [self socksAuthentication];        }        else        {            // Some kind of error occurred.            DDLogError(@"SOCKS_OPEN_ERROR:%d,%d",ver,mtd);            [asyncSocket disconnect];        }    }    else if (tag == SOCKS_AUTHENTICATION){        NSAssert(data.length == 2, @"SOCKS_AUTHENTICATION reply length must be 2!");        uint8_t *bytes = (uint8_t*)[data bytes];        UInt8 ver = bytes[0];        UInt8 status = bytes[1];        if (ver == 1 &&  status == 0) {            [self socksConnect];        }else{            DDLogError(@"SOCKS_AUTHENTICATION_ERROR:%d",status);        }    }    else if (tag == SOCKS_TCP_AUTH){        NSString *resultStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];        if ([resultStr rangeOfString:@"200"].location != NSNotFound) {            NSLog(@"SOCKS_TCP_AUTH_SUCCESS:%@",resultStr);            self.isConnectingToProxy = NO;             // Are we using old-style SSL? (Not the upgrade to TLS technique specified in the XMPP RFC)             if ([self isSecure]){                 // The connection must be secured immediately (just like with HTTPS)                 [self startTLS];             }             else{                 [self startNegotiation];             }        }else{            [asyncSocket disconnect];        }    }    else if (tag == SOCKS_CONNECT_REPLY_1)    {        // See socksConnect method for socks reply format        NSAssert(data.length == 5, @"SOCKS_CONNECT_REPLY_1 length must be 5!");        DDLogVerbose(@"TURNSocket: SOCKS_CONNECT_REPLY_1: %@", data);        uint8_t *bytes = (uint8_t*)[data bytes];                uint8_t ver = bytes[0];        uint8_t rep = bytes[1];                DDLogVerbose(@"TURNSocket: SOCKS_CONNECT_REPLY_1: ver(%o) rep(%o)", ver, rep);                if(ver == 5 && rep == 0)        {            // We read in 5 bytes which we expect to be:            // 0: ver  = 5            // 1: rep  = 0            // 2: rsv  = 0            // 3: atyp = 3            // 4: size = size of addr field            //            // However, some servers don't follow the protocol, and send a atyp value of 0.                        uint8_t addressType = bytes[3];            uint8_t portLength = 2;                        if (addressType == 1) { // IPv4                // only need to read 3 address bytes instead of 4 + portlength because we read an extra byte already                [asyncSocket readDataToLength:(3+portLength) withTimeout:TIMEOUT_READ tag:SOCKS_CONNECT_REPLY_2];            }            else if (addressType == 3) // Domain name            {                uint8_t addrLength = bytes[4];                                DDLogVerbose(@"TURNSocket: addrLength: %o", addrLength);                DDLogVerbose(@"TURNSocket: portLength: %o", portLength);                                [asyncSocket readDataToLength:(addrLength+portLength)                                       withTimeout:TIMEOUT_READ                                               tag:SOCKS_CONNECT_REPLY_2];            } else if (addressType == 4) { // IPv6                [asyncSocket readDataToLength:(16+portLength) withTimeout:TIMEOUT_READ tag:SOCKS_CONNECT_REPLY_2];            } else if (addressType == 0) {                // The size field was actually the first byte of the port field                // We just have to read in that last byte                [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_READ tag:SOCKS_CONNECT_REPLY_2];            } else {                DDLogVerbose(@"TURNSocket: Unknown atyp field in connect reply");                [asyncSocket disconnect];            }        }        else        {            NSString *failureReason = nil;            switch (rep) {                case 1:                    failureReason = @"general SOCKS server failure";                    break;                case 2:                    failureReason = @"connection not allowed by ruleset";                    break;                case 3:                    failureReason = @"Network unreachable";                    break;                case 4:                    failureReason = @"Host unreachable";                    break;                case 5:                    failureReason = @"Connection refused";                    break;                case 6:                    failureReason = @"TTL expired";                    break;                case 7:                    failureReason = @"Command not supported";                    break;                case 8:                    failureReason = @"Address type not supported";                    break;                default: // X'09' to X'FF' unassigned                    failureReason = @"unknown socks  error";                    break;            }            DDLogVerbose(@"SOCKS failed, disconnecting: %@", failureReason);            // Some kind of error occurred.                        [asyncSocket disconnect];        }    }    else if (tag == SOCKS_CONNECT_REPLY_2)    {        // See socksConnect method for socks reply format        DDLogVerbose(@"TURNSocket: SOCKS_CONNECT_REPLY_2: %@", data);    }    else{        // This method is invoked on the xmppQueue.                XMPPLogTrace();                lastSendReceiveTime = [NSDate timeIntervalSinceReferenceDate];        numberOfBytesReceived += [data length];                XMPPLogRecvPre(@"RECV: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);                // Asynchronously parse the xml data        [parser parseData:data];                if ([self isSecure])        {            // Continue reading for XML elements            if (state == STATE_XMPP_OPENING)            {                [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START tag:TAG_XMPP_READ_START];            }            else            {                [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM];            }        }        else        {            // Don't queue up a read on the socket as we may need to upgrade to TLS.            // We'll read more data after we've parsed the current chunk of data.        }    }}

这是第三部,确认成功设置代理。


总结:

其实,对于一个没有网络编程经验的来说,仅仅copy这些代码还是远远不够的,建议多买几本书看看,例如计算机网络,先把网络模型搞清楚才是上策,另外,强烈建议找一个对网络比较熟悉的同事协助一二,要不然出了问题就傻眼,毕竟TCP和HTTP这一块水深,小心溺水。

在此,特别鸣谢亲爱的阿伦同学,么么哒~

0 0
原创粉丝点击