2009-02-18 23 views
16

Esto podría considerarse una continuación de this earlier SO question.Linux: ¿Cómo forzar el uso de una interfaz de red específica?

Idealmente, me gustaría encarcelar un proceso para utilizar solo una determinada interfaz, sin importar qué. Hará conexiones TCP, enviará datagramas UDP y escuchará las transmisiones UDP. Actualmente, lo que estoy haciendo es:

  1. Determine la IP de la interfaz a usar.
  2. Crear una regla de política de propiedad intelectual para enrutar todos los paquetes procedentes de la interfaz a ese IP
  3. crear otra regla de política de propiedad intelectual para enrutar todos los paquetes que llegan desde esa IP a la interfaz de
  4. configurar una tabla de enrutamiento por defecto para cada regla

Ahora, esto funciona, en su mayoría, pero el proceso del cliente también debe estar dispuesto a seguirle el juego. Es decir, debe vincularse a la dirección IP específica de la interfaz que desea usar, y creo que también debo configurar SO_BINDTODEVICE. (Sin embargo, sigo leyendo información contradictoria sobre si SO_BINDTODEVICE realmente funciona cuando uso TCP o UDP.) Afortunadamente, la aplicación cliente es Python, y puedo extender la clase socket para hacer todo esto de forma transparente. Pero no estoy seguro de que sea una solución completa, especialmente con respecto a la recepción de transmisiones.

Mis preguntas son:

  1. ¿Se SO_BINDTODEVICE hacer lo que quiero aquí? ¿O solo es efectivo para tomas sin procesar? Alguien comentó que "SO_BINDTODEVICE en un zócalo no garantiza que el zócalo solo reciba los paquetes que llegaron a la antena/cable de esa interfaz física". Si esto es cierto, entonces ¿qué haceSO_BINDTODEVICE do?

  2. ¿Hay alguna manera de hacer esto para que la IP local no tenga que ser única? Esto no sería un problema más que el hecho de que el servidor DHCP en una interfaz puede asignarle una IP que está siendo utilizada por otra interfaz, confundiendo así la tabla de enrutamiento.

  3. ¿Cómo recibo transmisiones solo desde una interfaz específica? La vinculación a una IP específica parece hacer que ignore las transmisiones, lo que tiene sentido, pero no es exactamente lo que estoy buscando.

Funciono en Ubuntu 8.04 con Linux kernel 2.6.26. Ser capaz de acceder a la misma subred en dos redes diferentes a través de dos interfaces diferentes simultáneamente es un requisito no negociable, por lo que es (en su mayoría) inmune a "no hacer eso". :)

Respuesta

3

Después de un fin de semana muy reñido, me complace presentar una solución que soluciona la mayor parte de lo que he discutido con casi cero molestias.

Hay una llamada sysctl net.ipv4.conf.all.rp_filter que se pueden establecer en 0 para deshabilitar la validación del origen:

 
    rp_filter - INTEGER 
     2 - do source validation by reversed path, as specified in RFC1812 
      Recommended option for single homed hosts and stub network 
      routers. Could cause troubles for complicated (not loop free) 
      networks running a slow unreliable protocol (sort of RIP), 
      or using static routes. 

     1 - (DEFAULT) Weaker form of RP filtering: drop all the packets 
      that look as sourced at a directly connected interface, but 
      were input from another interface. 

     0 - No source validation. 

Esto también se puede configurar en función de cada interfaz utilizando/proc/sys/net/ipv4/conf/<interface>/rp_filter.

Como explicó un afiche, hace que el enrutamiento IP sea "menos determinista" en el sentido de que los paquetes provenientes de una subred no siempre saldrán por la misma interfaz. En este caso, esto es exactamente lo que se necesita. Haga una investigación adicional para determinar si esto es realmente lo que quiere.

Las emisiones siguen siendo problemáticas por razones que no entiendo, pero finalmente estoy satisfecho con este problema y espero que ayude a otros.

+0

No existe un equivalente de IPv6 para la variable sysctl 'net.ipv4.conf.all.rp_filter'; sin embargo, la funcionalidad correspondiente existe en la forma del módulo 'rpfilter' para netfilter. Está documentado en la página de comando man [iptables-extensions] (http://ipset.netfilter.org/iptables-extensions.man.html). [Moviendo rp_filter a netfilter] (http://www.strlen.de/talks/rpfilter.pdf) observa que es posible que 'rp_filter' sea eliminado del código de caché de enrutamiento completamente en un núcleo futuro. –

4

En cuanto a mi pregunta general, parece que hay algunas maneras de hacerlo:

  • La forma más complicada que involucra a los cambios de la tabla de enrutamiento y la cooperación de cada proceso. Esta es la forma que describí arriba. Una de sus ventajas es que funciona desde el espacio de usuario. He agregado algunas notas adicionales y he respondido a mis preguntas específicas a continuación.

  • Escriba un modelo de kernel personalizado que ignore completamente la tabla de enrutamiento, si SO_BINDTODEVICE está configurado. Sin embargo, aún es necesario que el proceso del cliente llame al setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev). Esta opción definitivamente no es para los débiles de corazón.

  • Virtualice el proceso. Esto probablemente no sea apropiado para mucha gente, y traerá dolores de cabeza propios, principalmente con la configuración. Pero vale la pena mencionarlo.

Las opciones 1 y 2 requieren procesos para habilitar el trabajo como nos gustaría. Esto puede aliviarse parcialmente creando una biblioteca dinámica que secuestra la llamada de socket() para crear el socket y luego vincularlo inmediatamente al dispositivo antes de devolver el descriptor. Esto se describe con más detalle en here.

Después de hacer algunas investigaciones y mucha búsqueda en Google, puedo sacar algunas conclusiones sobre cómo se comporta el kernel de Linux 2.6.26. Tenga en cuenta que estos son probablemente todos los comportamientos específicos de la implementación, y quizás incluso específicos del kernel. Pruebe su propia plataforma antes de decidir implementar funciones basadas en mi único punto de datos.

  1. SO_BINDTODEVICE hace de hecho lo que dice, al menos para UDP.

  2. IP únicas para cada interfaz parece ser necesario, ya que estamos utilizando las tablas de enrutamiento. Un módulo kernel personalizado podría eludir esta restricción.

  3. Para recibir transmisiones en una interfaz específica, conéctese primero al dispositivo usando SO_BINDTODEVICE, y luego vincule a la dirección de difusión con la llamada bind() habitual. El enlace del dispositivo debe hacerse antes que cualquier otra cosa. Entonces, el socket recibirá solo las transmisiones que lleguen a esa interfaz.

Lo probé creando primero un socket. Luego lo vinculé a una interfaz específica usando setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev). Finalmente, lo vinculé a la dirección de difusión. Desde otra computadora, envié una transmisión que se recibiría a través de una interfaz no vinculada. El socket de dispositivo no recibió esta transmisión, lo que tiene sentido. Elimine la llamada setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev) y se recibirá la transmisión.

También se debe mencionar que aquí también puede usar setsockopt(SOL_SOCKET, SO_REUSEADDR, 1). Tenga en cuenta que la semántica de SO_REUSEADDR cambia con las direcciones de difusión. Específicamente, es legal tener dos sockets vinculados a la dirección de difusión y el mismo puerto en la misma máquina si ambos tienen SO_REUSEADDR establecido.

ACTUALIZACIÓN: SO_BINDTODEVICE con transmisiones parece estar lleno de peligro, específicamente, la recepción de los marcos de transmisión. Estaba observando tramas de transmisión recibidas en una interfaz, pero desapareciendo en la otra al mismo tiempo. Parece que se ven afectados por la tabla de enrutamiento local, pero son inmunes a las reglas de la política de IP. Sin embargo, no estoy 100% seguro de esto y solo lo menciono como un punto de investigación si desea continuarlo. Todo esto para decir: use bajo su propio riesgo. En aras del tiempo, abrí un conector en bruto en la interfaz y analicé los encabezados Ethernet e IP yo mismo.

+0

Pregunta y respuesta muy informativas, ¡buen trabajo! –

2

No es una respuesta directa a su pregunta, pero solo un FYI. Como mencionó anteriormente, esta solución puede ser demasiado trabajo para lo que necesita/desea hacer.

Personalmente, me gusta la idea de crear un módulo kernel de conexión de red que me permita hacer esto. De esta forma, tengo control total sobre los marcos de multidifusión y de transmisión unitaria que van y vienen del espacio de usuario. Tendría que usar algo como netlink sockets para enviar/recibir datos hacia y desde su controlador y aplicación de espacio de usuario, pero funciona muy bien y es muy rápido.

También puede enganchar en cualquier nivel de la pila de esta manera ... Ethernet, o IP. Por lo tanto, tiene control total sobre lo que envía/recibe.

Aquí hay un ejemplo article que habla de engancharse en la pila netfilter.
Nota: este artículo se engancha en la pila IP, y también es antiguo. Sé que las API han cambiado, pero gran parte de este artículo todavía se aplica práctica y teóricamente. Si quería enganchar en la capa de puenteo, se usaría la de un mecanismo similar, pero especifique

BR_LOCAL_IN instead of NF_IP_LOCAL_IN 

Nota: Esto es muy similar a la apertura de un conector directo en la interfaz. Tendrás que construir tus cuadros tú mismo.

2

podría intentar limitar el espacio de nombres de la red del proceso a una sola interfaz. Necesita una compilación kernel con CONFIG_NETNS (la mayoría de los núcleos de las distribuciones modernas) y algún script para hacer la tarea por usted. Sample configuration

Cuestiones relacionadas