네트워크 네임스페이스에서 OpenVPN을 실행해 보세요.

네트워크 네임스페이스에서 OpenVPN을 실행해 보세요.

일부 애플리케이션이 OpenVPN을 통해 인터넷에 액세스하기를 원합니다. 이 질문/스레드 하단의 최종 답변/댓글에서 해결책을 찾았습니다. 특정 네트워크 네임스페이스에 대해 OpenVPN을 통해서만 모든 트래픽을 제공합니다.

나는 그 기사를 인용하고 있는데, 내가 겪고 있는 문제는 맨 아래에 명시되어 있습니다.

네임스페이스 내에서 OpenVPN 링크를 시작한 다음 네임스페이스 내에서 해당 OpenVPN 링크를 사용하는 각 명령을 실행할 수 있습니다. 이 작업을 수행하는 방법(내 직업 아님)에 대한 자세한 내용은 다음과 같습니다.

http://www.naju.se/articles/openvpn-netns.html

시도해 보았는데 효과가 있었습니다. (전역 네임스페이스가 아닌) 특정 네임스페이스 내에서 OpenVPN 연결의 시작 및 라우팅 단계를 수행하는 사용자 지정 스크립트를 제공하는 것이 아이디어였습니다. 나중에 오프라인 상태가 될 경우를 대비해 위의 링크를 인용합니다.

First create an --up script for OpenVPN. This script will create the VPN tunnel interface inside a network namespace called vpn, instead of the default namespace.

$ cat > netns-up << EOF
#!/bin/sh
case $script_type in
        up)
                ip netns add vpn
                ip netns exec vpn ip link set dev lo up
                mkdir -p /etc/netns/vpn
                echo "nameserver 8.8.8.8" > /etc/netns/vpn/resolv.conf
                ip link set dev "$1" up netns vpn mtu "$2"
                ip netns exec vpn ip addr add dev "$1" \
                        "$4/${ifconfig_netmask:-30}" \
                        ${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"}
                test -n "$ifconfig_ipv6_local" && \
          ip netns exec vpn ip addr add dev "$1" \
                        "$ifconfig_ipv6_local"/112
                ;;
        route-up)
                ip netns exec vpn ip route add default via "$route_vpn_gateway"
                test -n "$ifconfig_ipv6_remote" && \
          ip netns exec vpn ip route add default via \
                        "$ifconfig_ipv6_remote"
                ;;
        down)
                ip netns delete vpn
                ;;
esac

그런 다음 OpenVPN을 시작하고 ifconfig 및 라우팅을 수행하는 대신 --up 스크립트를 사용하도록 지시하십시오.

openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up

이제 다음과 같이 터널링하려는 프로그램을 시작할 수 있습니다.

ip netns exec vpn command

유일한 문제는 ip netns exec를 호출하려면 루트여야 하며 응용 프로그램이 루트로 실행되는 것을 원하지 않을 수도 있다는 것입니다. 해결책은 간단합니다.

sudo ip netns exec vpn sudo -u $(whoami) command

내 질문:

netns-up 스크립트를 호출하는 openvpn 명령을 실행하려고 하면 다음 두 가지 오류가 발생합니다.

:/etc/openvpn$ sudo openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up --config za1.nordvpn.com.tcp443.ovpn
(..)

Tue Mar 22 00:10:56 2016 [vpn-za.nordvpn.com] Peer Connection Initiated with [AF_INET]154.127.61.142:443
Tue Mar 22 00:10:59 2016 SENT CONTROL [vpn-za.nordvpn.com]: 'PUSH_REQUEST' (status=1)
Tue Mar 22 00:10:59 2016 PUSH: Received control message: 'PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 78.46.223.24,dhcp-option DNS 162.242.211.137,route 10.7.7.1,topology net30,ping 5,ping-restart 30,ifconfig 10.7.7.102 10.7.7.101'
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: timers and/or timeouts modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: --ifconfig/up options modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: route options modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified
Tue Mar 22 00:10:59 2016 ROUTE_GATEWAY 192.168.1.254/255.255.255.0 IFACE=eth0 HWADDR=b8:27:eb:39:7e:46
Tue Mar 22 00:10:59 2016 TUN/TAP device tun0 opened
Tue Mar 22 00:10:59 2016 TUN/TAP TX queue length set to 100
Tue Mar 22 00:10:59 2016 netns-up tun0 1500 1592 10.7.7.102 10.7.7.101 init
Tue Mar 22 00:10:59 2016 WARNING: Failed running command (--up/--down): external program exited with error status: 1
Tue Mar 22 00:10:59 2016 Exiting due to fatal error

sudo를 사용하거나 사용하지 않고 netns-up 스크립트를 다시 작성해 보았지만 도움이 되지 않았습니다. 내가 뭘 잘못했나요?

답변1

네트워크 네임스페이스 내에서 openvpn을 시작하는 것이 더 안전합니다. 다음 스크립트(Schnouki의 포크)를 사용하여 네임스페이스를 생성하고, 방화벽, DNS를 구성하고, 연결을 테스트하고, openvpn을 시작하고, 마지막으로 토렌트 클라이언트를 시작했습니다. TODO를 스크립트에 넣었으니 필요에 따라 조정해야 합니다.

#!/bin/sh
# start openvpn tunnel and torrent client inside Linux network namespace
#
# this is a fork of schnouki's script, see original blog post
# https://schnouki.net/posts/2014/12/12/openvpn-for-a-single-application-on-linux/
#
# original script can be found here
# https://gist.github.com/Schnouki/fd171bcb2d8c556e8fdf

# ------------ adjust values below ------------
# network namespace
NS_NAME=myVPN
NS_EXEC="ip netns exec $NS_NAME"
# user for starting the torrent client
REGULAR_USER=heinzwurst
# ---------------------------------------------

# exit on unbound variable
set -u

# exit on error
set -e
set -o pipefail

# trace option
#set -x

if [ $USER != "root" ]; then
    echo "This must be run as root."
    exit 1
fi

start_vpn() {
    echo "Add network interface"

    # Create the network namespace
    ip netns add $NS_NAME

    # Start the loopback interface in the namespace
    $NS_EXEC ip addr add 127.0.0.1/8 dev lo
    $NS_EXEC ip link set lo up

    # Create virtual network interfaces that will let OpenVPN (in the
    # namespace) access the real network, and configure the interface in the
    # namespace (vpn1) to use the interface out of the namespace (vpn0) as its
    # default gateway
    ip link add vpn0 type veth peer name vpn1
    ip link set vpn0 up
    ip link set vpn1 netns $NS_NAME up

    ip addr add 10.200.200.1/24 dev vpn0
    $NS_EXEC ip addr add 10.200.200.2/24 dev vpn1
    $NS_EXEC ip link set dev vpn1 mtu 1492
    $NS_EXEC ip route add default via 10.200.200.1 dev vpn1

    # Configure the nameserver to use inside the namespace
    # TODO use VPN-provided DNS servers in order to prevent leaks
    mkdir -p /etc/netns/$NS_NAME
    cat >/etc/netns/$NS_NAME/resolv.conf <<EOF || exit 1
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF

    # IPv4 NAT, you may need to adjust the interface name prefixes 'eth' 'wlan'
    iptables -t nat -A POSTROUTING -o eth+ -m mark --mark 0x29a -j MASQUERADE
    iptables -t nat -A POSTROUTING -o wlan+ -m mark --mark 0x29a -j MASQUERADE
    iptables -t mangle -A PREROUTING -i vpn0 -j MARK --set-xmark 0x29a/0xffffffff

    # TODO create firewall rules for your specific application (torrent)
    # or just comment the line below
    $NS_EXEC iptables-restore < /etc/iptables/iptables-$NS_NAME.rules

    # we should have full network access in the namespace
    $NS_EXEC ping -c 3 www.google.com

    # start OpenVPN in the namespace
    echo "Starting VPN"
    cd /etc/openvpn
    # TODO create openvpn configuration in /etc/openvpn/$NS_NAME.conf
    $NS_EXEC openvpn --config $NS_NAME.conf &

    # wait for the tunnel interface to come up
    while ! $NS_EXEC ip link show dev tun0 >/dev/null 2>&1 ; do sleep .5 ; done
}

stop_vpn() {
    echo "Stopping VPN"
    ip netns pids $NS_NAME | xargs -rd'\n' kill
    # TODO wait for terminate

    # clear NAT
    iptables -t nat -D POSTROUTING -o eth+ -m mark --mark 0x29a -j MASQUERADE
    iptables -t nat -D POSTROUTING -o wlan+ -m mark --mark 0x29a -j MASQUERADE
    iptables -t mangle -D PREROUTING -i vpn0 -j MARK --set-xmark 0x29a/0xffffffff

    echo "Delete network interface"
    rm -rf /etc/netns/$NS_NAME

    ip netns delete $NS_NAME
    ip link delete vpn0
}

# stop VPN on exit (even when error occured)
trap stop_vpn EXIT

start_vpn

# TODO start your favorite torrent client
$NS_EXEC sudo -u $REGULAR_USER transmission-gtk

답변2

나는 약 6개월 동안 Felix가 제공한 분기 스크립트를 사용해 왔으며 지난 주까지 잘 작동했습니다. (Nehal J Wani가 제안한 sysctl -w net.ipv4.ip_forward=1 줄을 추가하고 파이프 실패에 대해 주석을 달았습니다. - 그렇지 않으면 충돌이 발생합니다).

그러나 나는 지난 주에 그것이 완전히 작동을 멈춘 것을 발견했습니다(데비안 9.4). 스크립트가 너무 잘 작동했기 때문에 무엇이 잘못되었는지 디버깅하는 데 많은 시간을 보냈습니다. 하지만 아무리 노력해도 다시 작동할 수 없었습니다.

이것은 매우 유용한 기능이므로 누구도 동일한 문제에 직면하지 않도록 하는 데 도움이 될 Schnouki 작업의 대체 포크를 제공하고 싶습니다.

https://github.com/crasm/vpnshift.sh

#!/bin/bash
#
# Copyright (c) 2016, crasm <[email protected]>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


usage="usage: vpnshift -c <config> [<command> [<arg>...]]
optional:
    -u <user>      Execute <command> as <user>
    -d             Toggle namespace debug shell

if not otherwise specified:
    - The command defaults to the user's shell (${SHELL}).
    - The user must be inferred from sudo.
"

quick_die() {
    format="$1"; shift
    >&2 printf "${format}\n" "$@"
    exit 1
}

die() {
    format="$1"; shift
    >&2 printf "${format}\n" "$@"
    clean_exit 1
}

hush() {
    eval "$@" > /dev/null 2> /dev/null
}

must() {
    eval "$@" || die "failed: %s" "$*"
}

is_running() {
    local pid="$1"
    hush kill -s 0 "${pid}"
}

sig_kill() {
    local pid="$1"
    hush kill -s KILL "${pid}"
}

sig_term() {
    local pid="$1"
    hush kill -s TERM "${pid}"
}

clean_exit() {
    local exit_code="$1"

    if is_running "${openvpn_pid}"; then
        # Kill openvpn.
        sig_term "${openvpn_pid}"
        >&2 printf "stopping openvpn (pid = %d)." "${openvpn_pid}"
        for i in {1..100}; do
            if is_running "${openvpn_pid}"; then
                sleep 0.1
                printf "."
            else
                break
            fi
        done
        printf "\n"

        if is_running "${openvpn_pid}"; then
            >&2 echo "forced to kill openvpn"
            sig_kill "${openvpn_pid}"
        fi
    else
        >&2 echo "openvpn exited"
    fi

    # don't start cleaning up until openvpn is gone
    hush ip netns delete "${namespace}"
    hush rm --recursive --force "${namespace_dir}"
    hush sysctl --quiet net.ipv4.ip_forward="${forward}"
    if hush ps -C 'firewalld'; then
        echo "[firewalld] clearing firewalld state"
        hush systemctl restart firewalld
    else
        echo "${rules}" | hush iptables-restore
    fi

    # Sometimes there's a lag for the veths to be deleted by linux, so we
    # delete it manually.
    hush ip link delete "${veth_default}"
    hush ip link delete "${veth_vpn}"

    exit "${exit_code}"
}

nsdo() {
    ip netns exec "${namespace}" "$@"
}

_debug=0

main() {
    local config=
    local user="${SUDO_USER}"
    while getopts "hdc:u:" opt; do
        case "${opt}" in
            h) quick_die "${usage}" ;;
            d) _debug=1 ;;
            c) config="$(realpath "${OPTARG}")" ;;
            u) user="${OPTARG}" ;;
            *) quick_die "unknown option: %s" "${opt}" ;;
        esac
    done
    shift $(( OPTIND - 1 ))

    if [[ -z "${config}" ]]; then
        quick_die "openvpn config is required"
    fi

    if [[ -z "${user}" ]]; then
        quick_die "user must be provided explicitly via '-u' or implicitly via SUDO_USER"
    fi

    local cmd="$1"; shift

    if [[ -z "${cmd}" ]]; then
        cmd="${SHELL}"
    fi

    must ip netns add vpnshift
    must mkdir --parents "${namespace_dir}"

    # Set up loopback interface

    must nsdo ip address add '127.0.0.1/8' dev lo
    must nsdo ip address add '::1/128' dev lo
    must nsdo ip link set lo up

    # Set up veth tunnel

    must ip link add "${veth_vpn}" type veth peer name "${veth_default}"
    must ip link set "${veth_vpn}" netns "${namespace}"

    must ip link set "${veth_default}" up
    must nsdo ip link set "${veth_vpn}" up

    must ip address add "10.10.10.10/31" dev "${veth_default}"
    must nsdo ip \
        address add "10.10.10.11/31" dev "${veth_vpn}"

    must nsdo ip \
        route add default via "10.10.10.10" dev "${veth_vpn}"

    # Set up NAT and IP forwarding
    must sysctl --quiet net.ipv4.ip_forward=1

    # check if we need to enable masquerading via firewalld for veth_default
    if hush ps -C 'firewalld'; then
        echo "[firewalld] enabling firewalld based masquerading for ${veth_default}"

        if [[ $(firewall-cmd --get-zones | grep "${namespace}") != *"${namespace}"* ]]
        then
            echo "[firewalld] creating permanent new zone ${namespace} with target default"
            must firewall-cmd -q --permanent --new-zone="${namespace}"
            must firewall-cmd -q --permanent --zone="${namespace}" --set-target="default"
            must firewall-cmd -q --reload
        fi

        # add interface to our zone
        echo "[firewalld] adding ${veth_default} and ${veth_vpn} to zone ${namespace}"
        must firewall-cmd -q --zone="${namespace}" --change-interface="${veth_default}"

        # apply our source range to our zone
        echo "[firewalld] adding 10.10.10.10/31 as source for ${namespace}"
        must firewall-cmd -q --zone="${namespace}" --add-source=10.10.10.10/31

        # enable masquerading from our new source range on the default zone
        default_zone=$(firewall-cmd --get-default-zone)
        echo "[firewalld] enabling masquerading on default zone: ${default_zone}"
        must firewall-cmd -q --zone="${default_zone}" --add-masquerade
        must firewall-cmd -q --zone="${default_zone}"  --add-rich-rule=\'rule family="ipv4" source address="10.10.10.10/31" masquerade\'

        # optionally allow ports, services, etc. on our zone

        # enabling desired ports
        #echo "enabling all port traffic on zone ${namespace}"
        #must firewall-cmd -q --zone="${namespace}" --add-port=1025-65535/udp
        #must firewall-cmd -q --zone="${namespace}" --add-port=1025-65535/tcp

        # enable services
        #echo "enabling dns on zone ${namespace}"
        #must firewall-cmd -q --zone="${namespace}" --add-service=dns

    else
        must iptables --table "nat" --append "POSTROUTING" --jump "MASQUERADE" --source "10.10.10.10/31"
    fi

    # Set up DNS inside the new namespace
    printf > "${namespace_dir}/resolv.conf" \
        "nameserver %s\nnameserver %s\n" \
        "108.62.19.131" \
        "104.238.194.235"

    # drop in a shell to debug namespace connectivity ... the exit trap will catch exit from this and clean up
    if [[ "$_debug" == 1 ]]; then
        nsdo "${SHELL}"
    fi

    # Launch openvpn
    local tun="tunvpn"
    nsdo openvpn \
        --cd "$(dirname "${config}")" \
        --config "${config}" \
        --dev "${tun}" \
        --errors-to-stderr &

    openvpn_pid=$(ps --ppid "$!" \
        --format "pid" \
        --no-headers
    )

    >&2 printf "waiting for openvpn (pid = %d)\n" "${openvpn_pid}"

    while ! hush nsdo ip link show "${tun}"; do
        if ! is_running "${openvpn_pid}"; then
            clean_exit 1
        fi
        sleep 0.2
    done

    # Removing the default route protects from exposure if openvpn exits
    # prematurely.
    must nsdo ip \
        route delete default via "10.10.10.10" dev "${veth_vpn}"

    nsdo sudo -u "${user}" "${cmd}" "$@"
}

if [[ $# == 0 ]]; then
    quick_die "${usage}"
elif [[ "$(id -u)" != 0 ]]; then
    sudo "$0" "$@"
    exit "$?"
fi

# Stuff needed by clean_exit() to restore previous state.
namespace="vpnshift"
namespace_dir="/etc/netns/${namespace}"
forward="$(sysctl --values "net.ipv4.ip_forward")"
rules="$(iptables-save -t nat)"
veth_default="veth_default"
veth_vpn="veth_vpn"

openvpn_pid= # This is set later.

# Enable cleanup routine.
trap 'clean_exit 1' INT TERM
trap 'clean_exit $?' EXIT

main "$@"

답변3

유선 이더넷을 통해 LAN에 연결된 컴퓨터가 있는데, 이를 통해 몇 가지 다른 전략을 채택할 수 있습니다: 브리징! 이를 통해 LAN의 DHCP 서버는 호스트와 VPN 네임스페이스에 별도의 주소를 부여하고 VPN 네임스페이스에 인터넷에 대한 직접 액세스를 부여할 수 있습니다! iptables를 수정하거나 IP 주소를 수동으로 지정할 필요가 없습니다! 여기에 내 전체 솔루션이 있습니다.

# Make the netns.
ip netns add vpn

ip link add br0 type bridge
ip link set br0 up

# Make the inter-namespace pipe and bridge the host end.
ip link add veth.host type veth peer veth.vpn
ip link set veth.host master br0 up
ip link set veth.vpn netns vpn up

# Bridge the wired ethernet.
ip link set eth0 master br0 up

# Start dhcpcd on the bridge for the host to use and on veth.vpn
# for the vpn netns to use. The router will grant separate IP
# addresses to both! (They have different MAC addresses.)
dhcpcd br0
ip netns exec vpn dhcpcd veth.vpn

# OK, now start the VPN.
ip netns exec vpn openvpn --config etc etc etc

설정은 매우 간단합니다.

관련 정보