显示标签为“OpenVPN”的博文。显示所有博文
显示标签为“OpenVPN”的博文。显示所有博文

2013年3月17日星期日

iPhone 反向 USB 网络共享 - Linux 篇

所谓“反向共享”,就是电脑连接上网络,iPhone 没有,然后把网络连接共享给 iPhone。
网上有一些方案是利用代理服务器的,不完美,这里利用的是 OpenVPN,可以说是 90% 完美了。
为了避免伸手党(其实是我自己懒得写),我就不说详细了,把大概的内容说一下。如果谁感兴趣可以写一份更详细的教程。

  1. 为什么需要反向网络共享?
    你可能在一个没有 Wi-Fi 接入的地方(或者你的机器是行货 3GS 无 Wi-Fi),然后想向 iPhone 里下载一些东西,这个时候反向共享就有用了。
  2. iPhone 端安装软件
    首先,你的 iPhone 必须越狱(废话),然后从 Cydia 安装这些包:
    openssh GuizmOVPN
    如果你找不到 openssh 请把身份设置成“开发者”,后面那个是收费的,可以下载免费试用版,我们不需要主程序,只需要里面打过特殊补丁的 OpenVPN 客户端。
    2015年1月1日更新:请不要升级 GuizmOVPN 到 1.2.1!因为它将 TUNEMU 换成了 UTUN,因而不能工作!幸好我有备份:com.guizmo.openvpn_1.2.0_iphoneos-arm.deb
    至于怎么安装我就不说了,有 2G/3G/4G 的连上网下。没有的可以用 Cyder 2。截至发文,Cyder 2 仍然没有支持 iOS 5 以上系统,所以可以用 Cyder 下载 deb 包及其依赖,然后用 i-FunBox 拷贝进 iPhone 的 Cyder 自动安装目录。
    如果你打算用命令行 apt-get install,请用下面的软件包名:
    openssh com.guizmo.openvpn
  3. 电脑端安装软件
    你需要这些:
    openssh openvpn usbmuxd 你可能还想要 libimobiledevice ifuse
    根据发行版不同,你用的发行版可能是别的名字。
  4. 配置 OpenVPN
    怎么生成证书不在本文讨论的范畴(Google is your friend)。下面我们来讨论特殊的地方。
    假设你已经生成好 ca.crt ca.key dh1024.pem server.crt server.key client.crt client.key 了。我们来写 server.ovpn:
    local 127.0.0.1
    port 1194
    proto tcp
    dev tun
    ca ca.crt
    cert server.crt
    key server.key
    dh dh1024.pem
    server 10.8.0.0 255.255.255.0
    push "dhcp-option DNS 8.8.8.8"
    push "dhcp-option DNS 8.8.4.4"
    client-to-client
    duplicate-cn
    keepalive 10 120
    verb 3
    然后是 client.ovpn:
    client
    dev tun
    proto tcp
    remote localhost 1194
    nobind
    persist-key
    persist-tun
    ca ca.crt
    cert client.crt
    key client.key
    ns-cert-type server
    verb 3
    up ./guizmovpn_updown.sh
    down ./guizmovpn_updown.sh
    然后是关键的 guizmovpn_updown.sh,这个文件应该在 /Applications/GuizmOVPN.app 有。但是我的这个版本貌似可以用,如果你的版本不能用,试试看我的:
    #!/bin/bash
    
    #######################
    # GuizmOVPN_updown.sh #
    # Version 1.0.6       #
    #######################
    
    function CopyKey
    {
     if [ "`KeyExists $1`" == "NO" ] ; then
      return
     fi
            source=$1;
            dest=$2;
     error="0";
            echo "d.init" >/tmp/tmpscutil
            for param in `echo "show $1" | /usr/sbin/scutil | grep -v "<dictionary>" | tr -d '\n' | tr '}' '\n' | awk '{ print $1 }'` ; do
                    value=`echo "show $1" | /usr/sbin/scutil| grep -v "<dictionary>" | tr -d '\n' | tr '}' '\n' | grep $param`
      if $( echo $value | grep --quiet '<array>' ) ; then
                            unset array_val
                            for arraynum in 2 3 4 5 6 7 8 9 ; do
                                    val=`echo $value | cut -d '{' -f 2 | cut -d ':' -f $arraynum | cut -d ' ' -f 2`
                                    if [ "$val" != "" ] ; then
                                            array_val[$(($arraynum-1))]=$val;
                                    fi
                            done
                            if [ ${#array_val[@]} ]; then
                                    echo "d.add $param * ${array_val[*]}" >>/tmp/tmpscutil
                            else
        error="1" 
       fi
                    else
                            val=`echo "$value" | awk '{ print $3 }'`
                            if [ "`echo "$val" | tr -d ' '`"  == "" ] ; then
                                    error="1";
                            else
                                    echo "d.add $param $val" >>/tmp/tmpscutil
                            fi
                    fi
    
            done
            echo "set $dest" >>/tmp/tmpscutil
     if [ "$error"=="0" ] ; then
      cat /tmp/tmpscutil | /usr/sbin/scutil
     else
      echo "Error reading key $1"
     fi 
    }
    
    function GetActualDNS
    {
     OLDDNS=`echo "show State:/Network/Service/$1/DNS" | /usr/sbin/scutil | grep -A 1 "ServerAddresses : " | grep "0 : " | awk '{print $3}' | tr -d " "`
     echo $OLDDNS  
    }
    
    function KeyExists
    {
     if [ "`echo "show $1" | /usr/sbin/scutil | tr -d " "`" == "Nosuchkey" ] ; then
      echo "NO" ;
     else
      echo "YES" ;
     fi
    }
    
    PSID=$( (/usr/sbin/scutil | grep PrimaryService | sed -e 's/.*PrimaryService : //')<< EOF
    open
    get State:/Network/Global/IPv4
    d.show
    quit
    EOF
    )
    
    case "$script_type" in
       up)
         # Stop the APSd
         #/bin/launchctl unload /System/Library/LaunchDaemons/com.apple.apsd.plist
         
         # Remove informations about a previous connection
         echo "remove State:/Network/Service/OpenVPN/DNS" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil >/dev/null 2>&1
     
         # Check if we need to handle DNSPush
         unset dns
         unset domain
         if [ "$DNSPush" == "Y" ] ; then
     # Store the PSID that we modify, in order to restore the correct one on disconnection
            if [ "$PSID" != "" ] ; then
                    echo "d.init" >/tmp/tmpscutil
                    echo "d.add PSID $PSID" >>/tmp/tmpscutil
                    echo "set State:/Network/Service/OpenVPN/PSID" >>/tmp/tmpscutil
                    cat /tmp/tmpscutil | /usr/sbin/scutil
            fi
    
     # Save the DNS settings
     # If it has already been set, don't save it again
     if [ "`KeyExists State:/Network/Global/DNSold`" == "NO" ] ; then 
      CopyKey Setup:/Network/Service/$PSID/DNS Setup:/Network/Service/$PSID/DNSold ; 
      CopyKey State:/Network/Service/$PSID/DNS State:/Network/Service/$PSID/DNSold ;
      CopyKey State:/Network/Global/DNS State:/Network/Global/DNSold ; 
     fi
    
          n=1; i=0; j=0;
          while o=foreign_option_${n}; o=${!o}; [ "$o" ]
          do
               case $o in
                'dhcp-option DNS '*)        dns[i++]=${o/dhcp-option DNS /};;
                  'dhcp-option DOMAIN '*)     domain[j++]=${o/dhcp-option DOMAIN /} ;;
               esac;
               let n++
          done
    
     # Check if we need to try to keep local DNS
          if [ "$DNSKeep" == "Y" ] ; then
      # If DNS push has been set, add the actual DNS in the list, in case the DNS pushed don't respond
      if [ $i != 0 ] && [ $i -le 3 ] ; then
              dns[i]=`GetActualDNS $PSID`;
      
        # Check if the DNS is different from the gateway IP.
        # If it's the case, add a route to it
        if [ "$traffic_is_redirected" == "1" ] && [ "$dns[i]" != "" ] && [ "$route_net_gateway" != "${dns[i]}" ] && [ "$route_net_gateway" != "" ] ; then
        echo "Add a route to ${dns[i]} with gateway $route_net_gateway" ;
        /sbin/route add -net ${dns[i]} $route_net_gateway 255.255.255.255;
        fi 
           fi
     fi
    
          if [ "${dns[0]}" != "" ] ; then
      echo "Setting DNS to /Network/Service/$PSID/DNS (${dns[*]})"
      echo "d.init" >/tmp/tmpscutil
             echo "d.add ServerAddresses * ${dns[*]}" >>/tmp/tmpscutil 
      if [ "${domain[0]}" != "" ] ; then
       echo "Setting domain to /Network/Service/$PSID/DNS (${domain[*]})" 
       echo "d.add SupplementalMatchDomains * ${domain[*]}" >>/tmp/tmpscutil
      fi
      echo "set State:/Network/Service/$PSID/DNS" >>/tmp/tmpscutil 
      
      cat /tmp/tmpscutil | /usr/sbin/scutil 
    
      # For a strange reason, some devices don't use the "State" but the "Setup", so we copy the key.
      if [ "`KeyExists Setup:/Network/Service/$PSID/DNS`" == "YES" ] ; then
                     CopyKey State:/Network/Service/$PSID/DNS Setup:/Network/Service/$PSID/DNS 
                    fi
      CopyKey State:/Network/Service/$PSID/DNS State:/Network/Service/OpenVPN/DNS 
                    CopyKey State:/Network/Service/$PSID/DNS State:/Network/Global/DNS 
     fi
         fi
         
         # Add informations about the connection
         # IPv4
         echo "d.init" >/tmp/tmpscutil
         echo "d.add DestAddresses * $trusted_ip" >>/tmp/tmpscutil
         echo "d.add ServerAddress $trusted_ip" >>/tmp/tmpscutil
         echo "d.add InterfaceName ppp0" >>/tmp/tmpscutil
         echo "d.add Addresses * $ifconfig_local" >>/tmp/tmpscutil
         echo "d.add SubnetMasks * 255.255.255.255" >>/tmp/tmpscutil
         echo "d.add NetworkSignature VPN.RemoteAddress=$trusted_ip" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/IPv4" >>/tmp/tmpscutil
         echo "set State:/Network/Service/OpenVPN/IPv4" >>/tmp/tmpscutil
    
         # PPP
         echo "d.init" >>/tmp/tmpscutil
         echo "d.add Status 8" >>/tmp/tmpscutil
         echo "d.add InterfaceName ppp0" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/PPP" >>/tmp/tmpscutil
         echo "set State:/Network/Service/OpenVPN/PPP" >>/tmp/tmpscutil
         
         # Interface
         echo "d.init" >>/tmp/tmpscutil
         echo "d.add Type PPP" >>/tmp/tmpscutil
         echo "d.add SubType OpenVPN" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/Interface" >>/tmp/tmpscutil
     
         cat /tmp/tmpscutil | /usr/sbin/scutil
    
         # Add infos about connection to be displayed in GUI
         /Applications/GuizmOVPN.app/tools writeprefs InfosIPAddress "$ifconfig_local"
         if [ "$ifconfig_netmask" == "" ] ; then
              /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask "255.255.255.255"
       /Applications/GuizmOVPN.app/tools writeprefs InfosGateway "$ifconfig_remote"
         else
              /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask "$ifconfig_netmask"
       /Applications/GuizmOVPN.app/tools writeprefs InfosGateway "$InfosGateway"
         fi
         /Applications/GuizmOVPN.app/tools writeprefs InfosTrafficRedirected "$traffic_is_redirected"
    
         # Start the APSd
         #/bin/launchctl load /System/Library/LaunchDaemons/com.apple.apsd.plist
       ;;
    
       down)
         # Remove the connection infos
         /Applications/GuizmOVPN.app/tools writeprefs InfosIPAddress "" 
         /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask ""
         /Applications/GuizmOVPN.app/tools writeprefs InfosGateway ""
         /Applications/GuizmOVPN.app/tools writeprefs InfosTrafficRedirected ""
    
          # Check if we have a stored PSID. If not, take the actual one
          OLD_PSID=`echo "show State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil | grep PSID | awk '{print $3}'`
          if [ "$OLD_PSID" == "" ] ; then
              OLD_PSID=$PSID
          fi
     
          # If we have saved DNS, restore them
          if [ "$OLD_PSID" != "" ] && [ "`KeyExists State:/Network/Global/DNSold`" == "YES" ] ; then
              echo "Restoring DNS to $OLD_PSID";
              CopyKey Setup:/Network/Service/$OLD_PSID/DNSold Setup:/Network/Service/$OLD_PSID/DNS ;
              CopyKey State:/Network/Service/$OLD_PSID/DNSold State:/Network/Service/$OLD_PSID/DNS ;
              CopyKey State:/Network/Global/DNSold State:/Network/Global/DNS ;
    
       echo "remove Setup:/Network/Service/$OLD_PSID/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
       echo "remove State:/Network/Service/$OLD_PSID/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
       echo "remove State:/Network/Global/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
          fi
    
          # Check if we need to remove the route we added for the DNS
          if [ "$traffic_is_redirected" == "1" ] && [ "$route_net_gateway" != "`GetActualDNS $OLD_PSID`" ] && [ "$route_net_gateway" != "" ] && [ "`GetActualDNS $OLD_PSID`" != "" ] ; then
        echo "Removing route to `GetActualDNS $OLD_PSID` with gateway $route_net_gateway" 
       /sbin/route delete -net `GetActualDNS $OLD_PSID` $route_net_gateway 255.255.255.255 
          fi
    
          # Remove informations about the connection
          echo "remove State:/Network/Service/OpenVPN/DNS" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil >/dev/null 2>&1
    
          echo "remove Setup:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove Setup:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove Setup:/Network/Service/OpenVPN/Interface" | /usr/sbin/scutil >/dev/null 2>&1
       ;;
       *) echo "$0: invalid script_type $script_type" && exit 1 ;;
    esac
    
    #####
    
  5. 启动脚本
    我们来写一个启动脚本吧,connect.sh
    #!/bin/sh

    sudo -v
    sudo usbmuxd
    iproxy 2222 22 &
    sudo -n openvpn --config server.ovpn &
    sleep 1
    exec ssh root@localhost -p 2222 -R 1194:localhost:1194 /var/root/iptether/ovpncon.sh
    然后是 iPhone 端的启动脚本 ovpncon.sh
    #!/bin/sh

    (cd /var/root/iptether; /Applications/GuizmOVPN.app/openvpn --config client.ovpn --script-security 2) &
    还有 DNS 修复脚本,一般情况下是不需要的,但如果 DNS 不工作,就试试看 iPhone 端执行 ovpndns.sh:
    #!/bin/sh

    /usr/sbin/scutil << EOF
    open
    d.init
    d.add ServerAddresses * 8.8.8.8 8.8.4.4
    set State:/Network/Service/OpenVPN/DNS
    quit
    EOF
  6. 配置 NAT 转发
    echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
    但是这会在关机后失效。如果希望下次开机自动开启转发,再修改 /etc/sysctl.conf:
    net.ipv4.ip_forward = 1
    然后配置防火墙:
    sudo iptables -t nat -A POSTROUTING -j MASQUERADE
    同样,这个也是关机后丢失,如果想要保留,再修改 /etc/iptables/iptables.rules 并且开启系统的 iptables 服务(可能会因发行版而不同):
    *nat
    -A POSTROUTING -j MASQUERADE
    COMMIT
  7. Copy & Run!
    现在,把一大堆 OpenVPN 的配置文件和证书文件用 SCP 拷贝到 iPhone 的 /var/root/iptether 里面,用户名 root,密码 alpine(如果你没有修改,i-FunBox 之类的软件也没有替你修改的话)。
    还有,所有 .sh 结尾的文件都要给 0755 权限。
    然后,在电脑上 ./connect.sh,会自动调用刚刚拷贝过去的 ovpncon.sh 然后连接上网。Enjoy then!
    可能会有一些小 Bug,比如 iCloud 无线同步时好时坏(亲测 iOS 5.0.1 工作,iOS 6.1 不工作),或者是推送(亲测 iOS 4.2.1 不工作,iOS 5.0.1 工作,iOS 6.1 工作),又或者是 iMessage(亲测 iOS 5.0.1 不工作,iOS 6.1 工作)。