شغل برنامه نویسی شبکه چیست؟ معرفی زبان های برنامه نویسی مورد نیاز برای آن

مهندس شبکه: ادمین شبکه یا برنامه نویس شبکه؟

برنامه نویس شبکه کیست؟

تا به حال، افرادی که درحوزه شبکه فعالیت می‌کردند و ادمین‌های شبکه، نیازی به آشنایی چندان عمیقی در مورد برنامه نویسی نداشتند. اگر سیستم عامل بر روی سخت‌افزاری که تحت کنترل ما می‌باشد قرار گیرد، با یک محیط منسجم مواجه می‌شویم. اگر مجبور باشیم که یک شبکه برای یک کلاینت ایجاد کنیم، می‌دانیم که تجهیزات سیسکو، جونیپر یا اچ پی، دارای سیستم عاملی هستند که به صورت توکار در باکس‌های آن‌ها قرار  گرفته‌اند و خاص خود آن سخت افزار محسوب می‌شوند و تمامی این سیستم‌ها دارای قوانین و ساختار و کامندهای مختص به خود هستند. در این راستا، یک مهندس شبکه بیشتر شبیه یک مهندس سیستم است با این تفاوت که به جای استفاده از سیستم عاملی مانند ویندوز، از یک سیستم عامل دیگر مانند سیسکو استفاده می‌کند.
اما با ظهور SDN، کنترلرهایی پیاده سازی شدند که قادر به مدیریت ده‌ها هزار پورت با توانایی فراهم‌ نمودن مجازی سازی شبکه است. شغل جدیدی که حرکت به سمت SDN ارمغان می‌آورد، برنامه نویسی شبکه است. برنامه نویس شبکه، می‌بایست دانش وسیع و عمیقی در مورد مهندسی شبکه داشته باشد و نیز از دانش عمیقی در حداقل یکی از زبان‌های برنامه نویسی قدرتمند مشابه زبان C (مانند C، C++، C#، java، Objective-C) برخوردار باشد. برنامه نویس شبکه مسئول برنامه نویسی کنترلرهای SDN، اینترفیس و سایر مؤلفه‌های مربوطه است.

تفاوت برنامه نویس شبکه با ادمین شبکه چیست؟

پس ادمین شبکه شخصی است که توانایی استفاده و پیکربندی تجهیزاتی از جمله سیسکو، جونیپر، اچ پی و … را داشته باشد ولی برنامه نویس شبکه شخصی است که با مفاهیم پایه‌ای و همچنین مفاهیم جدید شبکه و ساختارهایی همچون SDN آشنا است و در عین حال از توانایی برنامه نویسی قابل قبولی برخوردار می‌باشد و می‌تواند برای کنترلرهای مختلف اپلیکیشن، اینترفیس و مؤلفه‌های مورد نیاز شبکه را توسعه دهد و همچنین می‌تواند بستر شبکه را طوری برنامه ریزی کند تا مدیریت پویا، هزینه کم، نوآوری، چابکی و سایر مزایایی که نمی‌توان از آن‌ها به سادگی گذشت را فراهم کند. کدامیک دانش بیشتری نیاز دارد؟ ادمین شبکه و یا برنامه نویس شبکه؟ قضاوت با شما 🙂

معرفی زبان های برنامه نویسی مورد نیاد برای شغل برنامه نویسی شبکه

یک برنامه نویس شبکه می‌بایست به زبان‌هایی از جمله C++، Java، Python و همچنین با JSON، XML، REST API، Socket Programming آشنایی کامل داشته باشد. برای شروع یادگیری زبان برنامه نویسی پایتون که ساده تر از سایر زبان‌ها می‌باشد پیشنهاد می‌شود. در ادامه مثال‌هایی ساده از مواردی که مطرح شده است ذکر می‌شود تا اهمیت آن را به روشنی دریابید.

استفاده از curl برای دسترسی به REST API:

مثال زیر نحوه استفاده از دستور curl برای افزودن flow entry به سوئیچ با Data Path ID=”00:00:00:00:00:00:00:01” را در کنترلر Floodlight نشان می‌دهد.

curl -X POST -d '{"switch": "00:00:00:00:00:00:00:01", "name":"flow-mod-1", "cookie":"0", "priority":"32768", "in_port":"1","active":"true", "eth_type":"0x800", "actions":"output=2"}' http://<controller_ip>:8080/wm/staticflowpusher/json

این ساختار برای کنترلر OpenDaylight به صورت زیر خواهد بود:

curl -u admin:admin -H 'Content-type: application/json' -X PUT -d '{"installInHw":"true", "name":"flow1", "node": {"id":"00:00:00:00:00:00:00:01", "type":"OF"}, "ingressPort":"1", "etherType": "0x800", "priority":"32768","actions":["OUTPUT=2"]}' 'http://localhost:8080/controller/nb/v2/flowprogrammer/default/node/OF/00:00:00:00:00:00:00:01/staticFlow/flow1'

درک چنین ساختاری نیازمند آشنایی با متدهای HTTP، REST API، JSON و مفاهیم پایه‌ای شبکه نرم افزار محور می‌باشد.

استفاده از پایتون برای دسترسی به REST API:

برنامه نویسی شبکه یعنی درک تمامی موارد مطرح شده در بالا. شما می‌توانید با یک برنامه به زبان پایتون و یا هر زبان دیگر این ساختار JSON را به کنترلر ارسال نمایید تا کنترلر اقدام به نصب flow entry نماید. برای مثال ساختار برنامه نویسی REST برای کنترلر Floodlight به صورت زیر می باشد:


import httplib

import json



class StaticFlowPusher(object):



def __init__(self, server):

self.server = server



def get(self, data):

ret = self.rest_call({}, 'GET')

return json.loads(ret[2])



def set(self, data):

ret = self.rest_call(data, 'POST')

return ret[0] == 200



def remove(self, objtype, data):

ret = self.rest_call(data, 'DELETE')

return ret[0] == 200



def rest_call(self, data, action):

path = '/wm/staticflowpusher/json'

headers = {

'Content-type': 'application/json',

'Accept': 'application/json',

}

body = json.dumps(data)

conn = httplib.HTTPConnection(self.server, 8080)

conn.request(action, path, body, headers)

response = conn.getresponse()

ret = (response.status, response.reason, response.read())

print ret

conn.close()

return ret



pusher = StaticFlowPusher('<insert_controller_ip')



flow1 = {

'switch':"00:00:00:00:00:00:00:01",

"name":"flow_mod_1",

"cookie":"0",

"priority":"32768",

"in_port":"1",

"eth_type":"0x800",

"active":"true",

"actions":"output=2"

}

pusher.set(flow1)

استفاده از پایتون در کنترلر OpenDaylight:

 

h = httplib2.Http(".cache")
h.add_credentials('admin', 'admin')
resp, content = h.request('http://127.0.0.1:8080/controller/nb/v2/statistics/default/flow', "GET")
allFlowStats = json.loads(content)
flowStats = allFlowStats['flowStatistics']
url = 'http://localhost:8080/controller/nb/v2/flow/default/node/OF/' + nodeid + '/staticFlow/flowtest'
flowtest = {"installInHw":"true","name":"flowtest","node":{"id":"00:00:00:00:00:00:00:01","type":"OF"},"IngressPort":"1","priority":"32768","etherType":"0x800","actions":"OUTPUT=2"}
resp, content = h.request(
      uri = url,
      method = 'PUT',
      headers={'Content-Type' : 'application/json'},
      body=json.dumps(flowtest)
    )

استفاده از جاوا در کنترلر Floodlight:

علاوه بر استفاده از REST API یک برنامه نویس شبکه می‌بایست قادر به ایجاد کلاس و ماژول در کنترلر باشد. بطور مثال برای کنترلرهای Floodlight و OpenDaylight چنین کاری نیاز به درک عمیقی از برنامه نویسی جاوا دارد. یک مثال برای ماژول Floodlight در زیر نشان داده شده است:

/**
*    Copyright 2011, Big Switch Networks, Inc.
*    Originally created by David Erickson, Stanford University

*

*    Licensed under the Apache License, Version 2.0 (the "License"); you may

*    not use this file except in compliance with the License. You may obtain

*    a copy of the License at

*

*         http://www.apache.org/licenses/LICENSE-2.0

*

*    Unless required by applicable law or agreed to in writing, software

*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

*    License for the specific language governing permissions and limitations

*    under the License.

**/

package net.floodlightcontroller.forwarding;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collection;

import java.util.List;

import java.util.Map;

import net.floodlightcontroller.core.FloodlightContext;

import net.floodlightcontroller.core.IFloodlightProviderService;

import net.floodlightcontroller.core.IOFSwitch;

import net.floodlightcontroller.devicemanager.IDevice;

import net.floodlightcontroller.devicemanager.IDeviceService;

import net.floodlightcontroller.devicemanager.SwitchPort;

import net.floodlightcontroller.core.annotations.LogMessageCategory;

import net.floodlightcontroller.core.annotations.LogMessageDoc;

import net.floodlightcontroller.core.annotations.LogMessageDocs;

import net.floodlightcontroller.core.internal.IOFSwitchService;

import net.floodlightcontroller.core.module.FloodlightModuleContext;

import net.floodlightcontroller.core.module.FloodlightModuleException;

import net.floodlightcontroller.core.module.IFloodlightModule;

import net.floodlightcontroller.core.module.IFloodlightService;

import net.floodlightcontroller.core.util.AppCookie;

import net.floodlightcontroller.debugcounter.IDebugCounterService;

import net.floodlightcontroller.packet.Ethernet;

import net.floodlightcontroller.packet.IPv4;

import net.floodlightcontroller.packet.TCP;

import net.floodlightcontroller.packet.UDP;

import net.floodlightcontroller.routing.ForwardingBase;

import net.floodlightcontroller.routing.IRoutingDecision;

import net.floodlightcontroller.routing.IRoutingService;

import net.floodlightcontroller.routing.Route;

import net.floodlightcontroller.topology.ITopologyService;

import org.projectfloodlight.openflow.protocol.OFFlowMod;

import org.projectfloodlight.openflow.protocol.match.Match;

import org.projectfloodlight.openflow.protocol.match.MatchField;

import org.projectfloodlight.openflow.protocol.OFFlowModCommand;

import org.projectfloodlight.openflow.protocol.OFPacketIn;

import org.projectfloodlight.openflow.protocol.OFPacketOut;

import org.projectfloodlight.openflow.protocol.OFVersion;

import org.projectfloodlight.openflow.types.DatapathId;

import org.projectfloodlight.openflow.types.EthType;

import org.projectfloodlight.openflow.types.IPv4Address;

import org.projectfloodlight.openflow.types.IpProtocol;

import org.projectfloodlight.openflow.types.MacAddress;

import org.projectfloodlight.openflow.types.OFBufferId;

import org.projectfloodlight.openflow.types.OFPort;

import org.projectfloodlight.openflow.types.OFVlanVidMatch;

import org.projectfloodlight.openflow.types.U64;

import org.projectfloodlight.openflow.types.VlanVid;

import org.projectfloodlight.openflow.protocol.action.OFAction;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

@LogMessageCategory("Flow Programming")

public class Forwarding extends ForwardingBase implements IFloodlightModule {

protected static Logger log = LoggerFactory.getLogger(Forwarding.class);

@Override

@LogMessageDoc(level="ERROR",

message="Unexpected decision made for this packet-in={}",

explanation="An unsupported PacketIn decision has been " +

"passed to the flow programming component",

recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)

public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {

Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);

// We found a routing decision (i.e. Firewall is enabled... it's the only thing that makes RoutingDecisions)

if (decision != null) {

if (log.isTraceEnabled()) {

log.trace("Forwaring decision={} was made for PacketIn={}", decision.getRoutingAction().toString(), pi);

}

switch(decision.getRoutingAction()) {

case NONE:

// don't do anything

return Command.CONTINUE;

case FORWARD_OR_FLOOD:

case FORWARD:

doForwardFlow(sw, pi, cntx, false);

return Command.CONTINUE;

case MULTICAST:

// treat as broadcast

doFlood(sw, pi, cntx);

return Command.CONTINUE;

case DROP:

doDropFlow(sw, pi, decision, cntx);

return Command.CONTINUE;

default:

log.error("Unexpected decision made for this packet-in={}", pi, decision.getRoutingAction());

return Command.CONTINUE;

}

} else { // No routing decision was found. Forward to destination or flood if bcast or mcast.

if (log.isTraceEnabled()) {

log.trace("No decision was made for PacketIn={}, forwarding", pi);

}

if (eth.isBroadcast() || eth.isMulticast()) {

doFlood(sw, pi, cntx);

} else {

doForwardFlow(sw, pi, cntx, false);

}

}

return Command.CONTINUE;

}

@LogMessageDoc(level="ERROR",

message="Failure writing drop flow mod",

explanation="An I/O error occured while trying to write a " +

"drop flow mod to a switch",

recommendation=LogMessageDoc.CHECK_SWITCH)

protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {

// initialize match structure and populate it based on the packet in's match

Match.Builder mb = null;

if (decision.getMatch() != null) {

/* This routing decision should be a match object with all appropriate fields set,

* not just masked. If it's a decision that matches the packet we received, then simply setting

* the masks to the new match will create the same match in the end. We can just use the routing

* match object instead.

*

* The Firewall is currently the only module/service that sets routing decisions in the context

* store (or instantiates any for that matter). It's disabled by default, so as-is a decision's

* match should always be null, meaning this will never be true.

*/

mb = decision.getMatch().createBuilder();

} else {

mb = pi.getMatch().createBuilder(); // otherwise no route is known so go based on packet's match object

}

OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); // this will be a drop-flow; a flow that will not output to any ports

List<OFAction> actions = new ArrayList<OFAction>(); // set no action to drop

U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);

fmb.setCookie(cookie)

.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)

.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)

.setBufferId(OFBufferId.NO_BUFFER)

.setMatch(mb.build())

.setActions(actions) // empty list

.setPriority(FLOWMOD_DEFAULT_PRIORITY);

try {

if (log.isDebugEnabled()) {

log.debug("write drop flow-mod sw={} match={} flow-mod={}",

new Object[] { sw, mb.build(), fmb.build() });

}

boolean dampened = messageDamper.write(sw, fmb.build());

log.debug("OFMessage dampened: {}", dampened);

} catch (IOException e) {

log.error("Failure writing drop flow mod", e);

}

}

protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, boolean requestFlowRemovedNotifn) {

OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));

// Check if we have the location of the destination

IDevice dstDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE);

if (dstDevice != null) {

IDevice srcDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);

DatapathId srcIsland = topologyService.getL2DomainId(sw.getId());

if (srcDevice == null) {

log.debug("No device entry found for source device");

return;

}

if (srcIsland == null) {

log.debug("No openflow island found for source {}/{}",

sw.getId().toString(), inPort);

return;

}

// Validate that we have a destination known on the same island

// Validate that the source and destination are not on the same switchport

boolean on_same_island = false;

boolean on_same_if = false;

for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) {

DatapathId dstSwDpid = dstDap.getSwitchDPID();

DatapathId dstIsland = topologyService.getL2DomainId(dstSwDpid);

if ((dstIsland != null) && dstIsland.equals(srcIsland)) {

on_same_island = true;

if (sw.getId().equals(dstSwDpid) && inPort.equals(dstDap.getPort())) {

on_same_if = true;

}

break;

}

}

if (!on_same_island) {

// Flood since we don't know the dst device

if (log.isTraceEnabled()) {

log.trace("No first hop island found for destination " +

"device {}, Action = flooding", dstDevice);

}

doFlood(sw, pi, cntx);

return;

}

if (on_same_if) {

if (log.isTraceEnabled()) {

log.trace("Both source and destination are on the same " +

"switch/port {}/{}, Action = NOP",

sw.toString(), inPort);

}

return;

}

// Install all the routes where both src and dst have attachment

// points.  Since the lists are stored in sorted order we can

// traverse the attachment points in O(m+n) time

SwitchPort[] srcDaps = srcDevice.getAttachmentPoints();

Arrays.sort(srcDaps, clusterIdComparator);

SwitchPort[] dstDaps = dstDevice.getAttachmentPoints();

Arrays.sort(dstDaps, clusterIdComparator);

int iSrcDaps = 0, iDstDaps = 0;

while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) {

SwitchPort srcDap = srcDaps[iSrcDaps];

SwitchPort dstDap = dstDaps[iDstDaps];

// srcCluster and dstCluster here cannot be null as

// every switch will be at least in its own L2 domain.

DatapathId srcCluster = topologyService.getL2DomainId(srcDap.getSwitchDPID());

DatapathId dstCluster = topologyService.getL2DomainId(dstDap.getSwitchDPID());

int srcVsDest = srcCluster.compareTo(dstCluster);

if (srcVsDest == 0) {

if (!srcDap.equals(dstDap)) {

Route route =

routingEngineService.getRoute(srcDap.getSwitchDPID(),

srcDap.getPort(),

dstDap.getSwitchDPID(),

dstDap.getPort(), U64.of(0)); //cookie = 0, i.e., default route

if (route != null) {

if (log.isTraceEnabled()) {

log.trace("pushRoute inPort={} route={} " +

"destination={}:{}",

new Object[] { inPort, route,

dstDap.getSwitchDPID(),

dstDap.getPort()});

}

U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);

// if there is prior routing decision use route's match

Match routeMatch = null;

IRoutingDecision decision = null;

if (cntx != null) {

decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);

}

if (decision != null) {

routeMatch = decision.getMatch();

} else {

// The packet in match will only contain the port number.

// We need to add in specifics for the hosts we're routing between.

Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);

VlanVid vlan = VlanVid.ofVlan(eth.getVlanID());

MacAddress srcMac = eth.getSourceMACAddress();

MacAddress dstMac = eth.getDestinationMACAddress();

// A retentive builder will remember all MatchFields of the parent the builder was generated from

// With a normal builder, all parent MatchFields will be lost if any MatchFields are added, mod, del

// TODO (This is a bug in Loxigen and the retentive builder is a workaround.)

Match.Builder mb = sw.getOFFactory().buildMatch();

mb.setExact(MatchField.IN_PORT, inPort)

.setExact(MatchField.ETH_SRC, srcMac)

.setExact(MatchField.ETH_DST, dstMac);

if (!vlan.equals(VlanVid.ZERO)) {

mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan));

}

// TODO Detect switch type and match to create hardware-implemented flow

// TODO Set option in config file to support specific or MAC-only matches

if (eth.getEtherType() == Ethernet.TYPE_IPv4) {

IPv4 ip = (IPv4) eth.getPayload();

IPv4Address srcIp = ip.getSourceAddress();

IPv4Address dstIp = ip.getDestinationAddress();

mb.setExact(MatchField.IPV4_SRC, srcIp)

.setExact(MatchField.IPV4_DST, dstIp)

.setExact(MatchField.ETH_TYPE, EthType.IPv4);

if (ip.getProtocol().equals(IpProtocol.TCP)) {

TCP tcp = (TCP) ip.getPayload();

mb.setExact(MatchField.IP_PROTO, IpProtocol.TCP)

.setExact(MatchField.TCP_SRC, tcp.getSourcePort())

.setExact(MatchField.TCP_DST, tcp.getDestinationPort());

} else if (ip.getProtocol().equals(IpProtocol.UDP)) {

UDP udp = (UDP) ip.getPayload();

mb.setExact(MatchField.IP_PROTO, IpProtocol.UDP)

.setExact(MatchField.UDP_SRC, udp.getSourcePort())

.setExact(MatchField.UDP_DST, udp.getDestinationPort());

}

} else if (eth.getEtherType() == Ethernet.TYPE_ARP) {

mb.setExact(MatchField.ETH_TYPE, EthType.ARP);

}

routeMatch = mb.build();

}

pushRoute(route, routeMatch, pi, sw.getId(), cookie,

cntx, requestFlowRemovedNotifn, false,

OFFlowModCommand.ADD);

}

}

iSrcDaps++;

iDstDaps++;

} else if (srcVsDest < 0) {

iSrcDaps++;

} else {

iDstDaps++;

}

}

} else {

// Flood since we don't know the dst device

doFlood(sw, pi, cntx);

}

}

/**

* Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless

* the port is blocked, in which case the packet will be dropped.

* @param sw The switch that receives the OFPacketIn

* @param pi The OFPacketIn that came to the switch

* @param cntx The FloodlightContext associated with this OFPacketIn

*/

@LogMessageDoc(level="ERROR",

message="Failure writing PacketOut " +

"switch={switch} packet-in={packet-in} " +

"packet-out={packet-out}",

explanation="An I/O error occured while writing a packet %2

شغل برنامه نویسی شبکه چیست؟ معرفی زبان های برنامه نویسی مورد نیاز برای آن
میانگین 5 امتیاز از 2 رای