jueves, 17 de diciembre de 2009

Desconectar a un usuario de RRAS (usando MPRAPI y C#)

Lo más tedioso es definir las estructuras y los métodos externos;
Una vez hecho, solo hay que articular las llamadas.
No me he entretenido en el control de errores ni en hacer Dispose de las estructuras, para lo que habría que usar ¿Marshal.FreeHGlobal?;
Son aconsejables estos dos puntos si se quiere usar en producción.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Mprapi
{
public enum ROUTER_INTERFACE_TYPE : uint
{

ROUTER_IF_TYPE_CLIENT,

ROUTER_IF_TYPE_HOME_ROUTER,

ROUTER_IF_TYPE_FULL_ROUTER,

ROUTER_IF_TYPE_DEDICATED,

ROUTER_IF_TYPE_INTERNAL,

ROUTER_IF_TYPE_LOOPBACK,

ROUTER_IF_TYPE_DIALOUT

}

public enum ROUTER_CONNECTION_STATE : uint
{

ROUTER_IF_STATE_UNREACHABLE,

ROUTER_IF_STATE_DISCONNECTED,

ROUTER_IF_STATE_CONNECTING,

ROUTER_IF_STATE_CONNECTED

}
public enum RAS_PORT_CONDITION :uint
{
RAS_PORT_NON_OPERATIONAL,
RAS_PORT_DISCONNECTED,
RAS_PORT_CALLING_BACK,
RAS_PORT_LISTENING,
RAS_PORT_AUTHENTICATING,
RAS_PORT_AUTHENTICATED,
RAS_PORT_INITIALIZING
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct RAS_CONNECTION_0
{
public IntPtr hConnection;
public IntPtr hInterface;
public uint dwConnectDuration;
public ROUTER_INTERFACE_TYPE dwInterfaceType;
public uint dwConnectionFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)257)]
public string wszInterfaceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)257)]
public string wszUserName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)16)]
public string wszLogonDomain;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)17)]
public string wszRemoteComputer;

};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct RAS_PORT_0
{
public IntPtr hPort;
public IntPtr hConnection;
public RAS_PORT_CONDITION dwPortCondition;
public uint dwTotalNumberOfCalls;
public uint dwConnectDuration;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)17)]
public string wszPortname;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)17)]
public string wszMedianame;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)129)]
public string wszDevicename;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)17)]
public string wszDeviceType;

};



class Program
{
[DllImport("mprapi.dll", CharSet = CharSet.Unicode)]
static extern int MprAdminServerConnect(string lpwsServerName, ref IntPtr phMprServer);
//
[DllImport("Mprapi.dll")]
public static extern uint MprAdminConnectionEnum(IntPtr hRasServer,
uint dwLevel, ref IntPtr connectionsStartPoint , int dwPrefMaxLen,
ref IntPtr lpdwEntriesRead, ref IntPtr lpdwTotalEntries,
ref ulong lpdwResumeHandle

);
[DllImport("Mprapi.dll")]
public static extern uint MprAdminPortEnum(IntPtr hRasServer,
uint dwLevel, IntPtr hconnection, ref IntPtr buf,
int dwPrefMaxLen,
ref IntPtr lpdwEntriesRead, ref IntPtr lpdwTotalEntries,
ref ulong lpdwResumeHandle

);
[DllImport("Mprapi.dll")]
public static extern uint MprAdminPortDisconnect(IntPtr hRasServer,
IntPtr hport);
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Hay que pasar el usuario a desconectar.");
return;
}
try
{
string usuario_desconectar = args[0];
IntPtr phMprServer = IntPtr.Zero;
MprAdminServerConnect(null, ref phMprServer);
//Console.WriteLine(phMprServer.ToString());

IntPtr connectionsStartPoint = new IntPtr();
List connections = new List();


IntPtr EntriesRead = IntPtr.Zero;
IntPtr TotalEntries = IntPtr.Zero;
ulong ResumeHandle = 0;
uint retConEnum = MprAdminConnectionEnum(phMprServer, 0,
ref connectionsStartPoint, -1, ref EntriesRead, ref TotalEntries, ref ResumeHandle);

//Console.WriteLine(TotalEntries.ToInt32().ToString());
for (int i = 0; i < EntriesRead.ToInt32(); i++)
{
connections.Add((RAS_CONNECTION_0)Marshal.PtrToStructure(connectionsStartPoint, typeof(RAS_CONNECTION_0)));
connectionsStartPoint = new IntPtr(connectionsStartPoint.ToInt32() + Marshal.SizeOf(connections.First()));
//Console.WriteLine(connections[i].wszUserName);
}
foreach (var it in connections)
{
if (it.wszUserName == usuario_desconectar)
{
desconectar(it.hConnection, phMprServer);
//Console.WriteLine(it.hConnection);
}
}
}
catch (Exception e)
{
Console.WriteLine("Se produjo el error: " + e.Message);
}
}

private static void desconectar(IntPtr hcon, IntPtr hserver)
{
RAS_PORT_0 est = new RAS_PORT_0();
IntPtr buf = new IntPtr();
IntPtr EntriesRead = IntPtr.Zero;
IntPtr TotalEntries = IntPtr.Zero;
ulong ResumeHandle = 0;
uint retConEnum = MprAdminPortEnum(hserver,0,hcon,
ref buf, -1, ref EntriesRead, ref TotalEntries, ref ResumeHandle);
est = (RAS_PORT_0)Marshal.PtrToStructure(buf, typeof(RAS_PORT_0));
Console.WriteLine(est.wszDevicename + " -> " + est.hPort.ToString());
uint r = MprAdminPortDisconnect(hserver, est.hPort);
Console.WriteLine("Cerrar " + est.hPort.ToString() + ":" + r.ToString());
}
}
}

miércoles, 28 de enero de 2009

Clase timeout c# - mejoras

La clase para controlar el timeout expuesta hace casi un año (aquí) en este blog tiene un problemilla; el rendimiento no es bueno llegado el caso de que este sea un requerimiento ó se use de forma masiva. La razón es que se crea una nueva hebra por cada llamada. Para mejorarla se puede hacer un cambio en cada una de las clases envoltura; en lugar de crear una nueva hebra podemos hacer una llamada asíncrona, que utiliza en el fondo el ThreadPool de .net. De esta forma no tenemos la penalización de crear/destruir hebras, pues el framework recicla en cada llamada una hebra del ThreadPool para atender nuestra llamada.
Como no es cuestión de escribir aquí de nuevo todo el código, se expone como quedaría la clase envoltura para el caso de recibir un parámetro: Envoltura<T, U>
Habría que hacer el correspondiente cambio en las demás clases envoltura.

internal class Envoltura<T, U>
{
private Exception ex = null;
private U parametro;
private Func<T, U> metodo;
private T retorno;
Thread hebra_a_matar = null;

public Envoltura(U parametro)
{
this.parametro = parametro;
}
public T Run(Func<T, U> metodo, int milisegundos)
{
this.metodo = metodo;
ThreadStart delegado = run;
IAsyncResult result = delegado.BeginInvoke(null, null);
if (result.AsyncWaitHandle.WaitOne(milisegundos,true))
{
delegado.EndInvoke(result);
if (this.ex != null)
throw this.ex;
return this.retorno;
}
else
{
hebra_a_matar.Abort();
TimeoutSException to = new TimeoutSException();
to.Timeout = milisegundos;
throw to;
}
}
private void run()
{
try
{
hebra_a_matar = Thread.CurrentThread;
this.retorno = metodo(parametro);
}
catch (Exception e)
{
ex = e;
}
}
}